@dxos/client-services 0.8.4-main.937b3ca → 0.8.4-main.9be5663bfe

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 (123) hide show
  1. package/dist/lib/browser/{chunk-UDCUM4WV.mjs → chunk-CK3KJB3B.mjs} +1737 -1199
  2. package/dist/lib/browser/chunk-CK3KJB3B.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +33 -16
  4. package/dist/lib/browser/index.mjs.map +3 -3
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/testing/index.mjs +14 -7
  7. package/dist/lib/browser/testing/index.mjs.map +3 -3
  8. package/dist/lib/node-esm/{chunk-Q56HAUWS.mjs → chunk-WHBWCIEN.mjs} +1737 -1199
  9. package/dist/lib/node-esm/chunk-WHBWCIEN.mjs.map +7 -0
  10. package/dist/lib/node-esm/index.mjs +33 -16
  11. package/dist/lib/node-esm/index.mjs.map +3 -3
  12. package/dist/lib/node-esm/meta.json +1 -1
  13. package/dist/lib/node-esm/testing/index.mjs +14 -7
  14. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  15. package/dist/types/src/index.d.ts +1 -0
  16. package/dist/types/src/index.d.ts.map +1 -1
  17. package/dist/types/src/packlets/agents/edge-agent-manager.d.ts +3 -2
  18. package/dist/types/src/packlets/agents/edge-agent-manager.d.ts.map +1 -1
  19. package/dist/types/src/packlets/agents/edge-agent-service.d.ts.map +1 -1
  20. package/dist/types/src/packlets/devtools/devtools.d.ts.map +1 -1
  21. package/dist/types/src/packlets/identity/contacts-service.d.ts.map +1 -1
  22. package/dist/types/src/packlets/identity/identity-manager.d.ts +3 -3
  23. package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
  24. package/dist/types/src/packlets/identity/identity-recovery-manager.d.ts +5 -4
  25. package/dist/types/src/packlets/identity/identity-recovery-manager.d.ts.map +1 -1
  26. package/dist/types/src/packlets/identity/identity-service.d.ts +1 -6
  27. package/dist/types/src/packlets/identity/identity-service.d.ts.map +1 -1
  28. package/dist/types/src/packlets/identity/identity.d.ts +6 -9
  29. package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
  30. package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts.map +1 -1
  31. package/dist/types/src/packlets/invitations/edge-invitation-handler.d.ts.map +1 -1
  32. package/dist/types/src/packlets/invitations/invitations-manager.d.ts +3 -3
  33. package/dist/types/src/packlets/invitations/invitations-manager.d.ts.map +1 -1
  34. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts.map +1 -1
  35. package/dist/types/src/packlets/logging/logging-service.d.ts +4 -0
  36. package/dist/types/src/packlets/logging/logging-service.d.ts.map +1 -1
  37. package/dist/types/src/packlets/network/network-service.d.ts.map +1 -1
  38. package/dist/types/src/packlets/services/feed-syncer.d.ts +59 -0
  39. package/dist/types/src/packlets/services/feed-syncer.d.ts.map +1 -0
  40. package/dist/types/src/packlets/services/feed-syncer.test.d.ts +2 -0
  41. package/dist/types/src/packlets/services/feed-syncer.test.d.ts.map +1 -0
  42. package/dist/types/src/packlets/services/platform.d.ts.map +1 -1
  43. package/dist/types/src/packlets/services/service-context.d.ts +3 -4
  44. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  45. package/dist/types/src/packlets/services/service-host.d.ts +1 -1
  46. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  47. package/dist/types/src/packlets/space-export/space-archive-reader.d.ts +9 -1
  48. package/dist/types/src/packlets/space-export/space-archive-reader.d.ts.map +1 -1
  49. package/dist/types/src/packlets/space-export/space-archive-writer.d.ts +6 -0
  50. package/dist/types/src/packlets/space-export/space-archive-writer.d.ts.map +1 -1
  51. package/dist/types/src/packlets/space-export/space-archive.test.d.ts +2 -0
  52. package/dist/types/src/packlets/space-export/space-archive.test.d.ts.map +1 -0
  53. package/dist/types/src/packlets/spaces/data-space-manager.d.ts +17 -10
  54. package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
  55. package/dist/types/src/packlets/spaces/data-space.d.ts +22 -6
  56. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  57. package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts.map +1 -1
  58. package/dist/types/src/packlets/spaces/genesis.d.ts +2 -1
  59. package/dist/types/src/packlets/spaces/genesis.d.ts.map +1 -1
  60. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -1
  61. package/dist/types/src/packlets/spaces/spaces-service.d.ts +2 -2
  62. package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
  63. package/dist/types/src/packlets/testing/invitation-utils.d.ts.map +1 -1
  64. package/dist/types/src/packlets/worker/worker-runtime.d.ts +11 -3
  65. package/dist/types/src/packlets/worker/worker-runtime.d.ts.map +1 -1
  66. package/dist/types/src/version.d.ts +1 -1
  67. package/dist/types/src/version.d.ts.map +1 -1
  68. package/dist/types/tsconfig.tsbuildinfo +1 -1
  69. package/package.json +43 -43
  70. package/src/index.ts +1 -0
  71. package/src/packlets/agents/edge-agent-manager.ts +8 -5
  72. package/src/packlets/agents/edge-agent-service.ts +2 -1
  73. package/src/packlets/devices/devices-service.test.ts +0 -1
  74. package/src/packlets/devtools/devtools.ts +0 -1
  75. package/src/packlets/identity/contacts-service.ts +0 -1
  76. package/src/packlets/identity/identity-manager.test.ts +5 -5
  77. package/src/packlets/identity/identity-manager.ts +16 -13
  78. package/src/packlets/identity/identity-recovery-manager.ts +20 -16
  79. package/src/packlets/identity/identity-service.test.ts +6 -27
  80. package/src/packlets/identity/identity-service.ts +5 -76
  81. package/src/packlets/identity/identity.test.ts +2 -2
  82. package/src/packlets/identity/identity.ts +7 -30
  83. package/src/packlets/invitations/device-invitation-protocol.ts +0 -1
  84. package/src/packlets/invitations/edge-invitation-handler.ts +4 -3
  85. package/src/packlets/invitations/invitations-handler.test.ts +4 -5
  86. package/src/packlets/invitations/invitations-handler.ts +3 -3
  87. package/src/packlets/invitations/invitations-manager.ts +37 -14
  88. package/src/packlets/invitations/invitations-service.ts +4 -4
  89. package/src/packlets/invitations/space-invitation-protocol.test.ts +17 -16
  90. package/src/packlets/invitations/space-invitation-protocol.ts +3 -2
  91. package/src/packlets/logging/logging-service.ts +4 -0
  92. package/src/packlets/network/network-service.test.ts +0 -1
  93. package/src/packlets/network/network-service.ts +5 -4
  94. package/src/packlets/services/feed-syncer.test.ts +340 -0
  95. package/src/packlets/services/feed-syncer.ts +337 -0
  96. package/src/packlets/services/platform.ts +7 -1
  97. package/src/packlets/services/service-context.test.ts +3 -2
  98. package/src/packlets/services/service-context.ts +105 -31
  99. package/src/packlets/services/service-host.test.ts +8 -8
  100. package/src/packlets/services/service-host.ts +10 -9
  101. package/src/packlets/services/service-registry.test.ts +0 -1
  102. package/src/packlets/space-export/space-archive-reader.ts +64 -3
  103. package/src/packlets/space-export/space-archive-writer.ts +36 -1
  104. package/src/packlets/space-export/space-archive.test.ts +287 -0
  105. package/src/packlets/spaces/data-space-manager.test.ts +79 -13
  106. package/src/packlets/spaces/data-space-manager.ts +71 -104
  107. package/src/packlets/spaces/data-space.ts +46 -24
  108. package/src/packlets/spaces/edge-feed-replicator.test.ts +1 -1
  109. package/src/packlets/spaces/edge-feed-replicator.ts +8 -7
  110. package/src/packlets/spaces/epoch-migrations.ts +3 -3
  111. package/src/packlets/spaces/genesis.ts +6 -1
  112. package/src/packlets/spaces/notarization-plugin.ts +2 -1
  113. package/src/packlets/spaces/spaces-service.test.ts +9 -7
  114. package/src/packlets/spaces/spaces-service.ts +30 -9
  115. package/src/packlets/testing/invitation-utils.ts +3 -2
  116. package/src/packlets/worker/worker-runtime.ts +14 -7
  117. package/src/packlets/worker/worker-session.ts +4 -4
  118. package/src/version.ts +1 -1
  119. package/dist/lib/browser/chunk-UDCUM4WV.mjs.map +0 -7
  120. package/dist/lib/node-esm/chunk-Q56HAUWS.mjs.map +0 -7
  121. package/dist/types/src/packlets/identity/default-space-state-machine.d.ts +0 -19
  122. package/dist/types/src/packlets/identity/default-space-state-machine.d.ts.map +0 -1
  123. package/src/packlets/identity/default-space-state-machine.ts +0 -44
@@ -3,10 +3,10 @@
3
3
  //
4
4
 
5
5
  import { type Doc } from '@automerge/automerge';
6
- import { type AutomergeUrl, type DocHandle, type DocumentId, interpretAsDocumentId } from '@automerge/automerge-repo';
6
+ import { type AutomergeUrl, type DocumentId, interpretAsDocumentId } from '@automerge/automerge-repo';
7
7
 
8
8
  import { Event, synchronized, trackLeaks } from '@dxos/async';
9
- import { SpaceProperties } from '@dxos/client-protocol';
9
+ import { LegacySpaceProperties, SpaceProperties } from '@dxos/client-protocol';
10
10
  import { Context, LifecycleState, Resource, cancelWithContext } from '@dxos/context';
11
11
  import {
12
12
  type CredentialSigner,
@@ -16,7 +16,6 @@ import {
16
16
  getCredentialAssertion,
17
17
  } from '@dxos/credentials';
18
18
  import { Type } from '@dxos/echo';
19
- import { getSchemaDXN } from '@dxos/echo/internal';
20
19
  import {
21
20
  AuthStatus,
22
21
  CredentialServerExtension,
@@ -31,45 +30,40 @@ import {
31
30
  type SpaceProtocolSession,
32
31
  findInlineObjectOfType,
33
32
  } from '@dxos/echo-pipeline';
34
- import {
35
- type DatabaseDirectory,
36
- EncodedReference,
37
- type ObjectStructure,
38
- SpaceDocVersion,
39
- createIdFromSpaceKey,
40
- } from '@dxos/echo-protocol';
33
+ import { type DatabaseDirectory, createIdFromSpaceKey } from '@dxos/echo-protocol';
41
34
  import type { EdgeConnection, EdgeHttpClient } from '@dxos/edge-client';
42
35
  import { type FeedStore, writeMessages } from '@dxos/feed-store';
43
36
  import { assertArgument, assertState, failedInvariant, invariant } from '@dxos/invariant';
44
37
  import { type Keyring } from '@dxos/keyring';
45
- import { ObjectId, PublicKey, type SpaceId } from '@dxos/keys';
38
+ import { PublicKey, type SpaceId } from '@dxos/keys';
46
39
  import { log } from '@dxos/log';
47
40
  import { AlreadyJoinedError, trace as Trace } from '@dxos/protocols';
48
41
  import { Invitation, SpaceState } from '@dxos/protocols/proto/dxos/client/services';
49
42
  import { type Runtime } from '@dxos/protocols/proto/dxos/config';
50
43
  import { type FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
51
44
  import { EdgeReplicationSetting, type SpaceMetadata } from '@dxos/protocols/proto/dxos/echo/metadata';
52
- import { type Credential, type ProfileDocument, SpaceMember } from '@dxos/protocols/proto/dxos/halo/credentials';
45
+ import {
46
+ type Credential,
47
+ MembershipPolicy,
48
+ type ProfileDocument,
49
+ SpaceMember,
50
+ } from '@dxos/protocols/proto/dxos/halo/credentials';
53
51
  import { type DelegateSpaceInvitation } from '@dxos/protocols/proto/dxos/halo/invitations';
54
52
  import { type PeerState } from '@dxos/protocols/proto/dxos/mesh/presence';
55
53
  import { type Teleport } from '@dxos/teleport';
56
54
  import { Gossip, Presence } from '@dxos/teleport-extension-gossip';
57
55
  import { type Timeframe } from '@dxos/timeframe';
58
56
  import { trace } from '@dxos/tracing';
59
- import { ComplexMap, deferFunction, forEachAsync, setDeep } from '@dxos/util';
57
+ import { ComplexMap, deferFunction, forEachAsync } from '@dxos/util';
60
58
 
61
59
  import { createAuthProvider } from '../identity';
62
60
  import { type InvitationsManager } from '../invitations';
63
-
64
61
  import { DataSpace } from './data-space';
65
62
  import { spaceGenesis } from './genesis';
66
63
 
67
64
  const PRESENCE_ANNOUNCE_INTERVAL = 10_000;
68
65
  const PRESENCE_OFFLINE_TIMEOUT = 20_000;
69
66
 
70
- // Space properties key for default metadata.
71
- const DEFAULT_SPACE_KEY = '__DEFAULT__';
72
-
73
67
  export interface SigningContext {
74
68
  identityKey: PublicKey;
75
69
  deviceKey: PublicKey;
@@ -94,6 +88,9 @@ export type AcceptSpaceOptions = {
94
88
  * We will try to catch up to this timeframe before initializing the database.
95
89
  */
96
90
  dataTimeframe?: Timeframe;
91
+
92
+ /** Tags assigned to the space member. */
93
+ tags?: string[];
97
94
  };
98
95
 
99
96
  export type AdmitMemberOptions = {
@@ -102,6 +99,7 @@ export type AdmitMemberOptions = {
102
99
  role: SpaceMember.Role;
103
100
  profile?: ProfileDocument;
104
101
  delegationCredentialId?: PublicKey;
102
+ tags?: string[];
105
103
  };
106
104
 
107
105
  export type DataSpaceManagerProps = {
@@ -135,9 +133,12 @@ export type DataSpaceManagerRuntimeProps = {
135
133
  export type CreateSpaceOptions = {
136
134
  rootUrl?: AutomergeUrl;
137
135
  documents?: Record<DocumentId, Uint8Array>;
136
+ tags?: string[];
137
+ membershipPolicy?: MembershipPolicy;
138
138
  };
139
139
 
140
140
  @trackLeaks('open', 'close')
141
+ @trace.resource()
141
142
  export class DataSpaceManager extends Resource {
142
143
  public readonly updated = new Event();
143
144
 
@@ -184,12 +185,15 @@ export class DataSpaceManager extends Resource {
184
185
  Array.from(this._spaces.values()).map(async (space) => {
185
186
  const rootUrl = space.automergeSpaceState.rootUrl;
186
187
  const rootHandle = rootUrl
187
- ? await this._echoHost.loadDoc<Doc<DatabaseDirectory>>(Context.default(), rootUrl as AutomergeUrl)
188
+ ? await this._echoHost.loadDoc<Doc<DatabaseDirectory>>(this._ctx, rootUrl as AutomergeUrl)
188
189
  : undefined;
189
190
  await rootHandle?.whenReady();
190
191
  const rootDoc = rootHandle?.doc();
191
192
 
192
- const properties = rootDoc && findInlineObjectOfType(rootDoc, Type.getTypename(SpaceProperties));
193
+ const properties =
194
+ rootDoc &&
195
+ (findInlineObjectOfType(rootDoc, Type.getTypename(SpaceProperties)) ??
196
+ findInlineObjectOfType(rootDoc, Type.getTypename(LegacySpaceProperties)));
193
197
 
194
198
  return {
195
199
  key: space.key.toHex(),
@@ -226,7 +230,7 @@ export class DataSpaceManager extends Resource {
226
230
  await forEachAsync(this._metadataStore.spaces, async (spaceMetadata) => {
227
231
  try {
228
232
  log('load space', { spaceMetadata });
229
- const space = await this._constructSpace(spaceMetadata);
233
+ const space = await this._constructSpace(this._ctx, spaceMetadata);
230
234
  // Track spaces that were previously active for auto-activation (used in dedicated worker mode).
231
235
  if (this._runtimeProps?.autoActivateSpaces && spaceMetadata.state === SpaceState.SPACE_ACTIVE) {
232
236
  spacesToActivate.push(space);
@@ -239,7 +243,7 @@ export class DataSpaceManager extends Resource {
239
243
  // Auto-activate spaces that were previously active (used in dedicated worker mode after leader changeover).
240
244
  for (const space of spacesToActivate) {
241
245
  log('auto-activating space', { spaceKey: space.key });
242
- space.activate().catch((err) => {
246
+ space.activate(this._ctx).catch((err) => {
243
247
  log.error('Error auto-activating space', { spaceKey: space.key, err });
244
248
  });
245
249
  }
@@ -250,10 +254,10 @@ export class DataSpaceManager extends Resource {
250
254
  }
251
255
 
252
256
  @synchronized
253
- protected override async _close(): Promise<void> {
257
+ protected override async _close(ctx: Context): Promise<void> {
254
258
  log('close');
255
259
  for (const space of this._spaces.values()) {
256
- await space.close();
260
+ await space.close(ctx);
257
261
  }
258
262
  this._spaces.clear();
259
263
  }
@@ -262,7 +266,8 @@ export class DataSpaceManager extends Resource {
262
266
  * Creates a new space writing the genesis credentials to the control feed.
263
267
  */
264
268
  @synchronized
265
- async createSpace(options: CreateSpaceOptions = {}): Promise<DataSpace> {
269
+ @trace.span({ showInBrowserTimeline: true })
270
+ async createSpace(ctx: Context, options: CreateSpaceOptions = {}): Promise<DataSpace> {
266
271
  assertArgument(
267
272
  !!options.rootUrl === !!options.documents,
268
273
  'options',
@@ -270,6 +275,8 @@ export class DataSpaceManager extends Resource {
270
275
  );
271
276
 
272
277
  assertState(this._lifecycleState === LifecycleState.OPEN, 'Not open.');
278
+
279
+ const tags = options.tags ? Array.from(options.tags) : [];
273
280
  const spaceKey = await this._keyring.createKey();
274
281
  const controlFeedKey = await this._keyring.createKey();
275
282
  const dataFeedKey = await this._keyring.createKey();
@@ -282,6 +289,7 @@ export class DataSpaceManager extends Resource {
282
289
  controlFeedKey,
283
290
  dataFeedKey,
284
291
  state: SpaceState.SPACE_ACTIVE,
292
+ tags,
285
293
  };
286
294
 
287
295
  log('creating space...', { spaceId, spaceKey });
@@ -319,30 +327,37 @@ export class DataSpaceManager extends Resource {
319
327
  let root: DatabaseRoot;
320
328
  if (options.rootUrl) {
321
329
  const newRootDocId = documentIdMapping[interpretAsDocumentId(options.rootUrl)] ?? failedInvariant();
322
- const rootDocHandle = await this._echoHost.loadDoc<DatabaseDirectory>(Context.default(), newRootDocId);
330
+ const rootDocHandle = await this._echoHost.loadDoc<DatabaseDirectory>(ctx, newRootDocId);
323
331
  DatabaseRoot.mapLinks(rootDocHandle, documentIdMapping);
324
332
 
325
- root = await this._echoHost.openSpaceRoot(spaceId, `automerge:${newRootDocId}` as AutomergeUrl);
333
+ root = await this._echoHost.openSpaceRoot(ctx, spaceId, `automerge:${newRootDocId}` as AutomergeUrl);
326
334
  } else {
327
- root = await this._echoHost.createSpaceRoot(spaceKey);
335
+ root = await this._echoHost.createSpaceRoot(ctx, spaceKey);
328
336
  }
329
- await this._echoHost.flush();
337
+ await this._echoHost.flush(ctx);
330
338
 
331
339
  log('constructing space...', { spaceKey });
332
340
 
333
- const space = await this._constructSpace(metadata);
334
- await space.open();
341
+ const space = await this._constructSpace(ctx, metadata);
342
+ await space.open(ctx);
335
343
 
336
344
  log('adding space...', { spaceKey });
337
345
 
338
- const credentials = await spaceGenesis(this._keyring, this._signingContext, space.inner, root.url);
346
+ const credentials = await spaceGenesis(
347
+ this._keyring,
348
+ this._signingContext,
349
+ space.inner,
350
+ root.url,
351
+ tags,
352
+ options.membershipPolicy,
353
+ );
339
354
  await this._metadataStore.addSpace(metadata);
340
355
 
341
356
  const memberCredential = credentials[1];
342
357
  invariant(getCredentialAssertion(memberCredential)['@type'] === 'dxos.halo.credentials.SpaceMember');
343
358
  await this._signingContext.recordCredential(memberCredential);
344
359
 
345
- await space.initializeDataPipeline();
360
+ await space.initializeDataPipeline(ctx);
346
361
 
347
362
  log('space ready.', { spaceId, spaceKey });
348
363
 
@@ -350,84 +365,30 @@ export class DataSpaceManager extends Resource {
350
365
  return space;
351
366
  }
352
367
 
353
- async isDefaultSpace(space: DataSpace): Promise<boolean> {
354
- if (!space.databaseRoot) {
355
- return false;
356
- }
357
- switch (space.databaseRoot.getVersion()) {
358
- case SpaceDocVersion.CURRENT: {
359
- if (!space.databaseRoot.handle.isReady()) {
360
- log.warn('waiting for space root to be ready', { spaceId: space.id });
361
- await space.databaseRoot.handle.whenReady();
362
- }
363
-
364
- const [_, properties] =
365
- findInlineObjectOfType(space.databaseRoot.doc()!, Type.getTypename(SpaceProperties)) ?? [];
366
- return properties?.data?.[DEFAULT_SPACE_KEY] === this._signingContext.identityKey.toHex();
367
- }
368
-
369
- case SpaceDocVersion.LEGACY: {
370
- throw new Error('Legacy space version is not supported');
371
- }
372
-
373
- default:
374
- log.warn('unknown space version', { version: space.databaseRoot.getVersion(), spaceId: space.id });
375
- return false;
376
- }
377
- }
378
-
379
- async createDefaultSpace(): Promise<DataSpace> {
380
- const space = await this.createSpace();
381
- const document = await this._getSpaceRootDocument(space);
382
-
383
- // TODO(dmaretskyi): Better API for low-level data access.
384
- const properties: ObjectStructure = {
385
- system: {
386
- type: EncodedReference.fromDXN(getSchemaDXN(SpaceProperties)!),
387
- },
388
- data: {
389
- [DEFAULT_SPACE_KEY]: this._signingContext.identityKey.toHex(),
390
- },
391
- meta: {
392
- keys: [],
393
- },
394
- };
395
-
396
- const propertiesId = ObjectId.random();
397
- document.change((doc: DatabaseDirectory) => {
398
- setDeep(doc, ['objects', propertiesId], properties);
399
- });
400
-
401
- await this._echoHost.flush();
402
- return space;
403
- }
404
-
405
- private async _getSpaceRootDocument(space: DataSpace): Promise<DocHandle<DatabaseDirectory>> {
406
- const automergeIndex = space.automergeSpaceState.rootUrl;
407
- invariant(automergeIndex);
408
- const document = await this._echoHost.loadDoc<DatabaseDirectory>(Context.default(), automergeIndex as any, {
409
- fetchFromNetwork: true,
410
- });
411
- await document.whenReady();
412
- return document;
413
- }
414
-
368
+ /**
369
+ * Accepts an existing space by joining its swarm and initializing the data pipeline.
370
+ * @param ctx - Caller context for cancellation and tracing.
371
+ * @param opts - Space keys and optional timeframes for catch-up.
372
+ */
415
373
  // TODO(burdon): Rename join space.
416
374
  @synchronized
417
- async acceptSpace(opts: AcceptSpaceOptions): Promise<DataSpace> {
375
+ @trace.span({ showInBrowserTimeline: true })
376
+ async acceptSpace(ctx: Context, opts: AcceptSpaceOptions): Promise<DataSpace> {
418
377
  log('accept space', { opts });
419
378
  invariant(this._lifecycleState === LifecycleState.OPEN, 'Not open.');
420
379
  invariant(!this._spaces.has(opts.spaceKey), 'Space already exists.');
421
380
 
381
+ const tags = opts.tags ? Array.from(opts.tags) : [];
422
382
  const metadata: SpaceMetadata = {
423
383
  key: opts.spaceKey,
424
384
  genesisFeedKey: opts.genesisFeedKey,
425
385
  controlTimeframe: opts.controlTimeframe,
426
386
  dataTimeframe: opts.dataTimeframe,
387
+ tags,
427
388
  };
428
389
 
429
- const space = await this._constructSpace(metadata);
430
- await space.open();
390
+ const space = await this._constructSpace(ctx, metadata);
391
+ await space.open(ctx);
431
392
  await this._metadataStore.addSpace(metadata);
432
393
  space.initializeDataPipelineAsync();
433
394
 
@@ -453,6 +414,7 @@ export class DataSpaceManager extends Resource {
453
414
  space.spaceState.membershipChainHeads,
454
415
  options.profile,
455
416
  options.delegationCredentialId,
417
+ space.spaceState.tags,
456
418
  );
457
419
 
458
420
  // TODO(dmaretskyi): Refactor.
@@ -479,8 +441,8 @@ export class DataSpaceManager extends Resource {
479
441
  );
480
442
  }
481
443
 
482
- public async requestSpaceAdmissionCredential(spaceKey: PublicKey): Promise<Credential> {
483
- return this._spaceManager.requestSpaceAdmissionCredential({
444
+ public async requestSpaceAdmissionCredential(ctx: Context, spaceKey: PublicKey): Promise<Credential> {
445
+ return this._spaceManager.requestSpaceAdmissionCredential(ctx, {
484
446
  spaceKey,
485
447
  identityKey: this._signingContext.identityKey,
486
448
  timeout: 15_000,
@@ -493,7 +455,11 @@ export class DataSpaceManager extends Resource {
493
455
  });
494
456
  }
495
457
 
496
- async setSpaceEdgeReplicationSetting(spaceKey: PublicKey, setting: EdgeReplicationSetting): Promise<void> {
458
+ async setSpaceEdgeReplicationSetting(
459
+ ctx: Context,
460
+ spaceKey: PublicKey,
461
+ setting: EdgeReplicationSetting,
462
+ ): Promise<void> {
497
463
  const space = this._spaces.get(spaceKey);
498
464
  invariant(space, 'Space not found.');
499
465
 
@@ -505,7 +471,7 @@ export class DataSpaceManager extends Resource {
505
471
  await this._echoEdgeReplicator?.disconnectFromSpace(space.id);
506
472
  break;
507
473
  case EdgeReplicationSetting.ENABLED:
508
- await this._echoEdgeReplicator?.connectToSpace(space.id);
474
+ await this._echoEdgeReplicator?.connectToSpace(ctx, space.id);
509
475
  break;
510
476
  }
511
477
  }
@@ -513,7 +479,7 @@ export class DataSpaceManager extends Resource {
513
479
  space.stateUpdate.emit();
514
480
  }
515
481
 
516
- private async _constructSpace(metadata: SpaceMetadata): Promise<DataSpace> {
482
+ private async _constructSpace(ctx: Context, metadata: SpaceMetadata): Promise<DataSpace> {
517
483
  log('construct space', { metadata });
518
484
  const gossip = new Gossip({
519
485
  localPeerId: this._signingContext.deviceKey,
@@ -603,6 +569,7 @@ export class DataSpaceManager extends Resource {
603
569
  },
604
570
  },
605
571
  cache: metadata.cache,
572
+ tags: metadata.tags,
606
573
  edgeConnection: this._edgeConnection,
607
574
  edgeHttpClient: this._edgeHttpClient,
608
575
  edgeFeatures: this._edgeFeatures,
@@ -611,7 +578,7 @@ export class DataSpaceManager extends Resource {
611
578
  dataSpace.postOpen.append(async () => {
612
579
  const setting = dataSpace.getEdgeReplicationSetting();
613
580
  if (!setting || setting === EdgeReplicationSetting.ENABLED) {
614
- await this._echoEdgeReplicator?.connectToSpace(dataSpace.id);
581
+ await this._echoEdgeReplicator?.connectToSpace(ctx, dataSpace.id);
615
582
  } else if (this._echoEdgeReplicator) {
616
583
  log('not connecting EchoEdgeReplicator because of EdgeReplicationSetting', { spaceId: dataSpace.id });
617
584
  }
@@ -708,7 +675,7 @@ export class DataSpaceManager extends Resource {
708
675
  invitations: Array<[PublicKey, DelegateSpaceInvitation]>,
709
676
  ): Promise<void> {
710
677
  const tasks = invitations.map(([credentialId, invitation]) => {
711
- return this._invitationsManager.createInvitation({
678
+ return this._invitationsManager.createInvitation(this._ctx, {
712
679
  type: Invitation.Type.DELEGATED,
713
680
  kind: Invitation.Kind.SPACE,
714
681
  spaceKey: space.key,
@@ -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 })
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 })
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);
@@ -309,7 +321,7 @@ export class DataSpace {
309
321
  scheduleTask(this._ctx, async () => {
310
322
  try {
311
323
  this.metrics.pipelineInitBegin = new Date();
312
- await this.initializeDataPipeline();
324
+ await this.initializeDataPipeline(this._ctx);
313
325
  } catch (err) {
314
326
  if (err instanceof CancelledError || err instanceof ContextDisposedError) {
315
327
  log('data pipeline initialization cancelled', err);
@@ -328,7 +340,7 @@ export class DataSpace {
328
340
  }
329
341
 
330
342
  @trace.span({ showInBrowserTimeline: true })
331
- async initializeDataPipeline(): Promise<void> {
343
+ async initializeDataPipeline(ctx: Context): Promise<void> {
332
344
  if (this._state !== SpaceState.SPACE_CONTROL_ONLY) {
333
345
  throw new SystemError({ message: 'Invalid operation' });
334
346
  }
@@ -337,7 +349,7 @@ export class DataSpace {
337
349
  log('new state', { state: SpaceState[this._state] });
338
350
 
339
351
  log('initializing control pipeline');
340
- await this._initializeAndReadControlPipeline();
352
+ await this._initializeAndReadControlPipeline(ctx);
341
353
 
342
354
  // Allow other tasks to run before loading the data pipeline.
343
355
  await sleep(1);
@@ -360,11 +372,19 @@ export class DataSpace {
360
372
  yield [this._databaseRoot.documentId, root];
361
373
 
362
374
  for (const documentUrl of this._databaseRoot.getAllLinkedDocuments()) {
363
- const data = await this._echoHost.exportDoc(Context.default(), documentUrl);
375
+ const data = await this._echoHost.exportDoc(documentUrl);
364
376
  yield [documentUrl.replace(/^automerge:/, ''), data];
365
377
  }
366
378
  }
367
379
 
380
+ /**
381
+ * Get all feeds and their blocks for this space.
382
+ * Used for space archive export.
383
+ */
384
+ async getAllFeeds(): Promise<Array<{ feedId: string; feedNamespace: string; blocks: FeedProtocol.Block[] }>> {
385
+ return this._echoHost.getAllFeedsForSpace(this.id);
386
+ }
387
+
368
388
  private async _enterReadyState(): Promise<void> {
369
389
  await this._callbacks.beforeReady?.();
370
390
 
@@ -376,9 +396,9 @@ export class DataSpace {
376
396
  }
377
397
 
378
398
  @trace.span({ showInBrowserTimeline: true })
379
- private async _initializeAndReadControlPipeline(): Promise<void> {
399
+ private async _initializeAndReadControlPipeline(ctx: Context): Promise<void> {
380
400
  await this._inner.controlPipeline.state.waitUntilReachedTargetTimeframe({
381
- ctx: this._ctx,
401
+ ctx,
382
402
  timeout: 10_000,
383
403
  breakOnStall: false,
384
404
  });
@@ -450,6 +470,9 @@ export class DataSpace {
450
470
 
451
471
  log('credentials notarized');
452
472
  } catch (err) {
473
+ if (err instanceof ContextDisposedError) {
474
+ return;
475
+ }
453
476
  log.error('error notarizing credentials for feed admission', err);
454
477
  throw err;
455
478
  }
@@ -470,7 +493,7 @@ export class DataSpace {
470
493
  await warnAfterTimeout(5_000, 'Automerge root doc load timeout (DataSpace)', async () => {
471
494
  handle = await cancelWithContext(
472
495
  this._ctx,
473
- this._echoHost.loadDoc<DatabaseDirectory>(Context.default(), rootUrl as AutomergeUrl, {
496
+ this._echoHost.loadDoc<DatabaseDirectory>(this._ctx, rootUrl as AutomergeUrl, {
474
497
  fetchFromNetwork: true,
475
498
  }),
476
499
  );
@@ -493,7 +516,7 @@ export class DataSpace {
493
516
 
494
517
  // TODO(dmaretskyi): Close roots.
495
518
  // TODO(dmaretskyi): How do we handle changing to the next EPOCH?
496
- const root = await this._echoHost.openSpaceRoot(this.id, handle.url);
519
+ const root = await this._echoHost.openSpaceRoot(this._ctx, this.id, handle.url);
497
520
 
498
521
  // NOTE: Make sure this assignment happens synchronously together with the state change.
499
522
  this._databaseRoot = root;
@@ -570,25 +593,24 @@ export class DataSpace {
570
593
  }
571
594
 
572
595
  @synchronized
573
- async activate(): Promise<void> {
596
+ async activate(ctx: Context): Promise<void> {
574
597
  if (![SpaceState.SPACE_CLOSED, SpaceState.SPACE_INACTIVE].includes(this._state)) {
575
598
  return;
576
599
  }
577
600
 
578
601
  await this._metadataStore.setSpaceState(this.key, SpaceState.SPACE_ACTIVE);
579
- await this._open();
602
+ await this._open(ctx);
580
603
  this.initializeDataPipelineAsync();
581
604
  }
582
605
 
583
606
  @synchronized
584
- async deactivate(): Promise<void> {
607
+ async deactivate(ctx: Context): Promise<void> {
585
608
  if (this._state === SpaceState.SPACE_INACTIVE) {
586
609
  return;
587
610
  }
588
- // Unregister from data service.
589
611
  await this._metadataStore.setSpaceState(this.key, SpaceState.SPACE_INACTIVE);
590
612
  if (this._state !== SpaceState.SPACE_CLOSED) {
591
- await this._close();
613
+ await this._close(ctx);
592
614
  }
593
615
  this._state = SpaceState.SPACE_INACTIVE;
594
616
  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
  });