@dxos/client-services 0.8.4-main.bc674ce → 0.8.4-main.bcb3aa67d6

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