@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
@@ -4,6 +4,7 @@
4
4
 
5
5
  import { describe, test } from 'vitest';
6
6
 
7
+ import { Context } from '@dxos/context';
7
8
  import { MemorySignalManager, MemorySignalManagerContext } from '@dxos/messaging';
8
9
  import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
9
10
  import { openAndClose } from '@dxos/test-utils';
@@ -19,7 +20,7 @@ describe('services/ServiceContext', () => {
19
20
  const device2 = await createOpenServiceContext(networkContext);
20
21
  await Promise.all(performInvitation({ host: device1, guest: device2, options: { kind: Invitation.Kind.DEVICE } }));
21
22
 
22
- const space1 = await device1.dataSpaceManager!.createSpace();
23
+ const space1 = await device1.dataSpaceManager!.createSpace(new Context());
23
24
  await device2.dataSpaceManager!.waitUntilSpaceReady(space1!.key);
24
25
  const space2 = await device2.dataSpaceManager!.spaces.get(space1.key);
25
26
  await space2!.inner.controlPipeline.state.waitUntilTimeframe(space1.inner.controlPipeline.state.timeframe);
@@ -35,7 +36,7 @@ describe('services/ServiceContext', () => {
35
36
 
36
37
  const identity2 = await createOpenServiceContext(networkContext);
37
38
  await identity2.createIdentity();
38
- const space1 = await identity2.dataSpaceManager!.createSpace();
39
+ const space1 = await identity2.dataSpaceManager!.createSpace(new Context());
39
40
  await Promise.all(
40
41
  performInvitation({
41
42
  host: identity2,
@@ -22,12 +22,13 @@ import { type RuntimeProvider } from '@dxos/effect';
22
22
  import { FeedFactory, FeedStore } from '@dxos/feed-store';
23
23
  import { invariant } from '@dxos/invariant';
24
24
  import { Keyring } from '@dxos/keyring';
25
- import { PublicKey } from '@dxos/keys';
25
+ import { PublicKey, type SpaceId } from '@dxos/keys';
26
26
  import { type LevelDB } from '@dxos/kv-store';
27
27
  import { log } from '@dxos/log';
28
28
  import { type SignalManager } from '@dxos/messaging';
29
29
  import { type SwarmNetworkManager } from '@dxos/network-manager';
30
30
  import { InvalidStorageVersionError, STORAGE_VERSION, trace } from '@dxos/protocols';
31
+ import { FeedProtocol } from '@dxos/protocols';
31
32
  import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
32
33
  import { type Runtime } from '@dxos/protocols/proto/dxos/config';
33
34
  import type { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
@@ -41,6 +42,7 @@ import { safeInstanceof } from '@dxos/util';
41
42
  import { EdgeAgentManager } from '../agents';
42
43
  import {
43
44
  type CreateIdentityOptions,
45
+ type Identity,
44
46
  IdentityManager,
45
47
  type IdentityManagerProps,
46
48
  type JoinIdentityProps,
@@ -55,6 +57,7 @@ import {
55
57
  SpaceInvitationProtocol,
56
58
  } from '../invitations';
57
59
  import { DataSpaceManager, type DataSpaceManagerRuntimeProps, type SigningContext } from '../spaces';
60
+ import { FeedSyncer } from './feed-syncer';
58
61
 
59
62
  export type ServiceContextRuntimeProps = Pick<
60
63
  IdentityManagerProps,
@@ -64,8 +67,6 @@ export type ServiceContextRuntimeProps = Pick<
64
67
  invitationConnectionDefaultProps?: InvitationConnectionProps;
65
68
  disableP2pReplication?: boolean;
66
69
  enableVectorIndexing?: boolean;
67
- enableSqlite?: boolean;
68
- enableLocalQueues?: boolean;
69
70
  };
70
71
  /**
71
72
  * Shared backend for all client services.
@@ -90,6 +91,7 @@ export class ServiceContext extends Resource {
90
91
  public readonly echoHost: EchoHost;
91
92
  private readonly _meshReplicator?: MeshEchoReplicator = undefined;
92
93
  private readonly _echoEdgeReplicator?: EchoEdgeReplicator = undefined;
94
+ private readonly _feedSyncer?: FeedSyncer = undefined;
93
95
 
94
96
  // Initialized after identity is initialized.
95
97
  public dataSpaceManager?: DataSpaceManager;
@@ -166,12 +168,15 @@ export class ServiceContext extends Resource {
166
168
  kv: this.level,
167
169
  peerIdProvider: () => this.identityManager.identity?.deviceKey?.toHex(),
168
170
  getSpaceKeyByRootDocumentId: (documentId) => this.spaceManager.findSpaceByRootDocumentId(documentId)?.key,
169
- indexing: {
170
- vector: this._runtimeProps?.enableVectorIndexing,
171
- sqlIndex: this._runtimeProps?.enableSqlite,
172
- },
173
171
  runtime: this._runtime,
174
- localQueues: this._runtimeProps?.enableLocalQueues,
172
+ syncQueue: async (ctx, request) => {
173
+ return this._feedSyncer?.syncBlocking(ctx, {
174
+ spaceId: request.spaceId as SpaceId,
175
+ subspaceTag: request.subspaceTag,
176
+ shouldPush: request.shouldPush,
177
+ shouldPull: request.shouldPull,
178
+ });
179
+ },
175
180
  });
176
181
 
177
182
  this.invitations = new InvitationsHandler(
@@ -206,6 +211,17 @@ export class ServiceContext extends Resource {
206
211
  edgeHttpClient: this._edgeHttpClient,
207
212
  });
208
213
  }
214
+
215
+ if (this.echoHost.feedStore && this._edgeConnection) {
216
+ this._feedSyncer = new FeedSyncer({
217
+ runtime: this._runtime,
218
+ feedStore: this.echoHost.feedStore,
219
+ edgeClient: this._edgeConnection,
220
+ peerId: this.identityManager.identity?.deviceKey?.toHex() ?? '',
221
+ getSpaceIds: () => this.echoHost!.spaceIds,
222
+ syncNamespaces: [FeedProtocol.WellKnownNamespaces.data, FeedProtocol.WellKnownNamespaces.trace],
223
+ });
224
+ }
209
225
  }
210
226
 
211
227
  @Trace.span()
@@ -215,32 +231,67 @@ export class ServiceContext extends Resource {
215
231
  log('opening...');
216
232
  log.trace('dxos.sdk.service-context.open', trace.begin({ id: this._instanceId }));
217
233
 
234
+ log('opening identityManager...');
218
235
  await this.identityManager.open(ctx);
236
+ log('identityManager opened', { hasIdentity: !!this.identityManager.identity });
219
237
 
220
- await this._setNetworkIdentity();
238
+ log('setting network identity...');
239
+ await this._setNetworkIdentity({ identity: this.identityManager.identity });
240
+ log('network identity set');
221
241
 
242
+ log('opening edge connection...');
222
243
  await this._edgeConnection?.open();
244
+ log('edge connection opened');
245
+
246
+ log('opening signal manager...');
223
247
  await this.signalManager.open();
248
+ log('signal manager opened');
249
+
250
+ log('opening network manager...');
224
251
  await this.networkManager.open();
252
+ log('network manager opened');
225
253
 
254
+ log('opening echo host...');
226
255
  await this.echoHost.open(ctx);
256
+ log('echo host opened');
227
257
 
228
258
  if (this._meshReplicator) {
229
- await this.echoHost.addReplicator(this._meshReplicator);
259
+ log('adding mesh replicator...');
260
+ await this.echoHost.addReplicator(ctx, this._meshReplicator);
261
+ log('mesh replicator added');
230
262
  }
231
263
  if (this._echoEdgeReplicator) {
232
- await this.echoHost.addReplicator(this._echoEdgeReplicator);
264
+ log('adding edge replicator...');
265
+ await this.echoHost.addReplicator(ctx, this._echoEdgeReplicator);
266
+ log('edge replicator added');
233
267
  }
234
268
 
269
+ log('loading metadata store...');
235
270
  await this.metadataStore.load();
271
+ log('metadata store loaded');
272
+
273
+ log('opening space manager...');
236
274
  await this.spaceManager.open();
275
+ log('space manager opened');
237
276
 
238
277
  if (this.identityManager.identity) {
239
- await this.identityManager.identity.joinNetwork();
278
+ log('joining network...');
279
+ await this.identityManager.identity.joinNetwork(ctx);
280
+ log('network joined');
281
+
282
+ log('initializing spaces...(calling _initialize)');
240
283
  await this._initialize(ctx);
284
+ log('spaces initialized');
285
+ } else {
286
+ log('no identity, skipping network join and space initialization');
241
287
  }
242
288
 
243
- const loadedInvitations = await this.invitationsManager.loadPersistentInvitations();
289
+ log('opening feed syncer...');
290
+ await this._feedSyncer?.open();
291
+ log('feed syncer opened');
292
+
293
+ log('loading persistent invitations...');
294
+ const loadedInvitations = await this.invitationsManager.loadPersistentInvitations(ctx);
244
295
  log('loaded persistent invitations', { count: loadedInvitations.invitations?.length });
245
296
 
246
297
  log.trace('dxos.sdk.service-context.open', trace.end({ id: this._instanceId }));
@@ -249,16 +300,19 @@ export class ServiceContext extends Resource {
249
300
 
250
301
  protected override async _close(ctx: Context): Promise<void> {
251
302
  log('closing...');
303
+
304
+ await this._feedSyncer?.close();
305
+
252
306
  if (this._deviceSpaceSync && this.identityManager.identity) {
253
307
  await this.identityManager.identity.space.spaceState.removeCredentialProcessor(this._deviceSpaceSync);
254
308
  }
255
- await this.dataSpaceManager?.close();
309
+ await this.dataSpaceManager?.close(ctx);
256
310
  await this.edgeAgentManager?.close();
257
- await this.identityManager.close();
311
+ await this.identityManager.close(ctx);
258
312
  await this.spaceManager.close();
259
313
  await this.echoHost.close(ctx);
260
314
 
261
- await this.networkManager.close();
315
+ await this.networkManager.close(ctx);
262
316
  await this.signalManager.close();
263
317
  await this._edgeConnection?.close();
264
318
  await this.feedStore.close();
@@ -268,10 +322,11 @@ export class ServiceContext extends Resource {
268
322
  }
269
323
 
270
324
  async createIdentity(params: CreateIdentityOptions = {}) {
271
- const identity = await this.identityManager.createIdentity(params);
272
- await this._setNetworkIdentity();
273
- await identity.joinNetwork();
274
- await this._initialize(new Context());
325
+ const ctx = Context.default();
326
+ const identity = await this.identityManager.createIdentity(params, ctx);
327
+ await this._setNetworkIdentity({ identity });
328
+ await identity.joinNetwork(ctx);
329
+ await this._initialize(ctx);
275
330
  return identity;
276
331
  }
277
332
 
@@ -295,11 +350,11 @@ export class ServiceContext extends Resource {
295
350
  }
296
351
 
297
352
  private async _acceptIdentity(params: JoinIdentityProps) {
298
- const { identity, identityRecord } = await this.identityManager.prepareIdentity(params);
299
- await this._setNetworkIdentity({ deviceCredential: params.authorizedDeviceCredential! });
300
- await identity.joinNetwork();
353
+ const { identity, identityRecord } = await this.identityManager.prepareIdentity(params, this._ctx);
354
+ await this._setNetworkIdentity({ deviceCredential: params.authorizedDeviceCredential!, identity });
355
+ await identity.joinNetwork(this._ctx);
301
356
  await this.identityManager.acceptIdentity(identity, identityRecord, params.deviceProfile);
302
- await this._initialize(new Context());
357
+ await this._initialize(this._ctx);
303
358
  return identity;
304
359
  }
305
360
 
@@ -314,7 +369,7 @@ export class ServiceContext extends Resource {
314
369
  // Called when identity is created.
315
370
  @Trace.span()
316
371
  private async _initialize(ctx: Context): Promise<void> {
317
- log('initializing spaces...');
372
+ log('_initialize: start');
318
373
  const identity = this.identityManager.identity ?? failUndefined();
319
374
  const signingContext: SigningContext = {
320
375
  credentialSigner: identity.getIdentityCredentialSigner(),
@@ -326,6 +381,7 @@ export class ServiceContext extends Resource {
326
381
  },
327
382
  };
328
383
 
384
+ log('_initialize: creating DataSpaceManager');
329
385
  this.dataSpaceManager = new DataSpaceManager({
330
386
  spaceManager: this.spaceManager,
331
387
  metadataStore: this.metadataStore,
@@ -341,7 +397,9 @@ export class ServiceContext extends Resource {
341
397
  runtimeProps: this._runtimeProps as DataSpaceManagerRuntimeProps,
342
398
  edgeFeatures: this._edgeFeatures,
343
399
  });
400
+ log('_initialize: opening DataSpaceManager...');
344
401
  await this.dataSpaceManager.open();
402
+ log('_initialize: DataSpaceManager opened');
345
403
 
346
404
  this.edgeAgentManager = new EdgeAgentManager(
347
405
  this._edgeFeatures,
@@ -349,13 +407,16 @@ export class ServiceContext extends Resource {
349
407
  this.dataSpaceManager,
350
408
  identity,
351
409
  );
410
+ log('_initialize: opening EdgeAgentManager...');
352
411
  await this.edgeAgentManager.open();
412
+ log('_initialize: EdgeAgentManager opened');
353
413
 
354
414
  this._handlerFactories.set(Invitation.Kind.SPACE, (invitation) => {
355
415
  invariant(this.dataSpaceManager, 'dataSpaceManager not initialized yet');
356
416
  return new SpaceInvitationProtocol(this.dataSpaceManager, signingContext, this.keyring, invitation.spaceKey);
357
417
  });
358
418
  this.initialized.wake();
419
+ log('_initialize: initialized.wake() called');
359
420
 
360
421
  this._deviceSpaceSync = {
361
422
  processCredential: async (credential: Credential) => {
@@ -378,7 +439,7 @@ export class ServiceContext extends Resource {
378
439
 
379
440
  try {
380
441
  log('accepting space recorded in halo', { details: assertion });
381
- await this.dataSpaceManager.acceptSpace({
442
+ await this.dataSpaceManager.acceptSpace(this._ctx, {
382
443
  spaceKey: assertion.spaceKey,
383
444
  genesisFeedKey: assertion.genesisFeedKey,
384
445
  });
@@ -391,33 +452,42 @@ export class ServiceContext extends Resource {
391
452
  await identity.space.spaceState.addCredentialProcessor(this._deviceSpaceSync);
392
453
  }
393
454
 
394
- private async _setNetworkIdentity(params?: { deviceCredential: Credential }): Promise<void> {
455
+ private async _setNetworkIdentity(params?: { deviceCredential?: Credential; identity?: Identity }): Promise<void> {
456
+ log('_setNetworkIdentity: acquiring mutex...');
395
457
  using _ = await this._edgeIdentityUpdateMutex.acquire();
458
+ log('_setNetworkIdentity: mutex acquired');
396
459
 
397
460
  let edgeIdentity: EdgeIdentity;
398
- const identity = this.identityManager.identity;
461
+ const identity = params?.identity;
399
462
  if (identity) {
400
- log('setting identity on edge connection', {
463
+ log('_setNetworkIdentity: has identity', {
401
464
  identity: identity.identityKey.toHex(),
402
- swarms: this.networkManager.topics,
465
+ hasDeviceCredential: !!params?.deviceCredential,
403
466
  });
404
467
 
405
468
  if (params?.deviceCredential) {
469
+ log('_setNetworkIdentity: creating chain edge identity with device credential...');
406
470
  edgeIdentity = await createChainEdgeIdentity(
407
471
  identity.signer,
408
472
  identity.identityKey,
409
473
  identity.deviceKey,
410
- params?.deviceCredential && { credential: params.deviceCredential },
474
+ { credential: params.deviceCredential },
411
475
  [], // TODO(dmaretskyi): Service access credentials.
412
476
  );
477
+ log('_setNetworkIdentity: chain edge identity created');
413
478
  } else {
479
+ log('_setNetworkIdentity: waiting for identity.ready()...');
414
480
  // TODO: throw here or from identity if device chain can't be loaded, to avoid indefinite hangup
415
481
  await warnAfterTimeout(10_000, 'Waiting for identity to be ready for edge connection', async () => {
416
482
  await identity.ready();
417
483
  });
484
+ log('_setNetworkIdentity: identity.ready() resolved', {
485
+ hasDeviceCredentialChain: !!identity.deviceCredentialChain,
486
+ });
418
487
 
419
488
  invariant(identity.deviceCredentialChain);
420
489
 
490
+ log('_setNetworkIdentity: creating chain edge identity...');
421
491
  edgeIdentity = await createChainEdgeIdentity(
422
492
  identity.signer,
423
493
  identity.identityKey,
@@ -425,9 +495,12 @@ export class ServiceContext extends Resource {
425
495
  identity.deviceCredentialChain,
426
496
  [], // TODO(dmaretskyi): Service access credentials.
427
497
  );
498
+ log('_setNetworkIdentity: chain edge identity created');
428
499
  }
429
500
  } else {
501
+ log('_setNetworkIdentity: no identity, creating ephemeral edge identity...');
430
502
  edgeIdentity = await createEphemeralEdgeIdentity();
503
+ log('_setNetworkIdentity: ephemeral edge identity created');
431
504
  }
432
505
 
433
506
  this._edgeConnection?.setIdentity(edgeIdentity);
@@ -436,5 +509,6 @@ export class ServiceContext extends Resource {
436
509
  identityKey: edgeIdentity.identityKey,
437
510
  peerKey: edgeIdentity.peerKey,
438
511
  });
512
+ log('_setNetworkIdentity: done');
439
513
  }
440
514
  }
@@ -3,7 +3,6 @@
3
3
  //
4
4
 
5
5
  import { rmSync } from 'node:fs';
6
-
7
6
  import { afterEach, describe, expect, onTestFinished, test } from 'vitest';
8
7
 
9
8
  import { Trigger, asyncTimeout, latch } from '@dxos/async';
@@ -13,6 +12,7 @@ import { verifyPresentation } from '@dxos/credentials';
13
12
  import { type PublicKey } from '@dxos/keys';
14
13
  import { MemorySignalManagerContext } from '@dxos/messaging';
15
14
  import { type Identity } from '@dxos/protocols/proto/dxos/client/services';
15
+ import { MembershipPolicy } from '@dxos/protocols/proto/dxos/halo/credentials';
16
16
  import { type Credential } from '@dxos/protocols/proto/dxos/halo/credentials';
17
17
  import { isNode } from '@dxos/util';
18
18
 
@@ -29,16 +29,16 @@ describe('ClientServicesHost', () => {
29
29
  test('open and close', async () => {
30
30
  const host = createServiceHost(new Config(), new MemorySignalManagerContext());
31
31
  await host.open(new Context());
32
- await host.close();
32
+ await host.close(Context.default());
33
33
  });
34
34
 
35
35
  test('queryCredentials', async () => {
36
36
  const host = createServiceHost(new Config(), new MemorySignalManagerContext());
37
37
  await host.open(new Context());
38
- onTestFinished(() => host.close());
38
+ onTestFinished(() => host.close(Context.default()));
39
39
 
40
40
  await host.services.IdentityService!.createIdentity({});
41
- const { spaceKey } = await host.services.SpacesService!.createSpace();
41
+ const { spaceKey } = await host.services.SpacesService!.createSpace({ membershipPolicy: MembershipPolicy.INVITE });
42
42
 
43
43
  const stream = host.services.SpacesService!.queryCredentials({ spaceKey });
44
44
  const [done, tick] = latch({ count: 3 });
@@ -54,7 +54,7 @@ describe('ClientServicesHost', () => {
54
54
  test('write and query credentials', async () => {
55
55
  const host = createServiceHost(new Config(), new MemorySignalManagerContext());
56
56
  await host.open(new Context());
57
- onTestFinished(() => host.close());
57
+ onTestFinished(() => host.close(Context.default()));
58
58
 
59
59
  await host.services.IdentityService!.createIdentity({});
60
60
 
@@ -91,7 +91,7 @@ describe('ClientServicesHost', () => {
91
91
  test('sign presentation', async () => {
92
92
  const host = createServiceHost(new Config(), new MemorySignalManagerContext());
93
93
  await host.open(new Context());
94
- onTestFinished(() => host.close());
94
+ onTestFinished(() => host.close(Context.default()));
95
95
 
96
96
  await host.services.IdentityService!.createIdentity({});
97
97
 
@@ -128,7 +128,7 @@ describe('ClientServicesHost', () => {
128
128
  expect(host.context.storage.size).to.exist;
129
129
 
130
130
  await asyncTimeout(host.reset(), 1000);
131
- await host.close();
131
+ await host.close(Context.default());
132
132
  }
133
133
 
134
134
  {
@@ -146,7 +146,7 @@ describe('ClientServicesHost', () => {
146
146
  });
147
147
  await expect(asyncTimeout(trigger.wait(), 200)).rejects.toBeInstanceOf(Error);
148
148
  await stream?.close();
149
- await host.close();
149
+ await host.close(Context.default());
150
150
  }
151
151
  });
152
152
  });
@@ -47,7 +47,6 @@ import { NetworkServiceImpl } from '../network';
47
47
  import { SpacesServiceImpl } from '../spaces';
48
48
  import { createLevel, createStorageObjects } from '../storage';
49
49
  import { SystemServiceImpl } from '../system';
50
-
51
50
  import { ServiceContext, type ServiceContextRuntimeProps } from './service-context';
52
51
  import { ServiceRegistry } from './service-registry';
53
52
 
@@ -147,7 +146,7 @@ export class ClientServicesHost {
147
146
  void this.open(new Context());
148
147
  }
149
148
  },
150
- onRelease: () => this.close(),
149
+ onRelease: () => this.close(Context.default()),
151
150
  });
152
151
  }
153
152
 
@@ -244,9 +243,6 @@ export class ClientServicesHost {
244
243
  if (this._runtimeProps.enableVectorIndexing === undefined) {
245
244
  this._runtimeProps.enableVectorIndexing = config?.get('runtime.client.enableVectorIndexing', false);
246
245
  }
247
- if (this._runtimeProps.enableLocalQueues === undefined) {
248
- this._runtimeProps.enableLocalQueues = config?.get('runtime.client.enableLocalQueues', false);
249
- }
250
246
 
251
247
  invariant(!this._config, 'config already set');
252
248
  this._config = config;
@@ -262,8 +258,9 @@ export class ClientServicesHost {
262
258
 
263
259
  const endpoint = config?.get('runtime.services.edge.url');
264
260
  if (endpoint) {
265
- this._edgeConnection = new EdgeClient(createStubEdgeIdentity(), { socketEndpoint: endpoint });
266
- this._edgeHttpClient = new EdgeHttpClient(endpoint);
261
+ const clientTag = config?.get('runtime.app.env.DX_EDGE_CLIENT_TAG');
262
+ this._edgeConnection = new EdgeClient(createStubEdgeIdentity(), { socketEndpoint: endpoint, clientTag });
263
+ this._edgeHttpClient = new EdgeHttpClient(endpoint, { clientTag });
267
264
  }
268
265
 
269
266
  const {
@@ -348,7 +345,6 @@ export class ClientServicesHost {
348
345
  this._serviceContext.identityManager,
349
346
  this._serviceContext.recoveryManager,
350
347
  this._serviceContext.keyring,
351
- () => this._serviceContext.dataSpaceManager!,
352
348
  (params) => this._createIdentity(params),
353
349
  (profile) => this._serviceContext.broadcastProfileUpdate(profile),
354
350
  );
@@ -395,8 +391,13 @@ export class ClientServicesHost {
395
391
  EdgeAgentService: new EdgeAgentServiceImpl(agentManagerProvider, this._edgeConnection),
396
392
  });
397
393
 
394
+ log('service-host: opening service context...');
398
395
  await this._serviceContext.open(ctx);
396
+ log('service-host: service context opened');
397
+
398
+ log('service-host: opening identity service...');
399
399
  await identityService.open();
400
+ log('service-host: identity service opened');
400
401
 
401
402
  const devtoolsProxy = this._config?.get('runtime.client.devtoolsProxy');
402
403
  if (devtoolsProxy) {
@@ -420,7 +421,7 @@ export class ClientServicesHost {
420
421
 
421
422
  @synchronized
422
423
  @Trace.span()
423
- async close(): Promise<void> {
424
+ async close(ctx: Context): Promise<void> {
424
425
  if (!this._open) {
425
426
  return;
426
427
  }
@@ -15,7 +15,6 @@ import { createLinkedPorts, createProtoRpcPeer, createServiceBundle } from '@dxo
15
15
 
16
16
  import { SystemServiceImpl } from '../system';
17
17
  import { createServiceContext } from '../testing';
18
-
19
18
  import { ServiceRegistry } from './service-registry';
20
19
 
21
20
  // TODO(burdon): Create TestService (that doesn't require peers).
@@ -6,12 +6,26 @@ import type { DocumentId } from '@automerge/automerge-repo';
6
6
 
7
7
  import { assertArgument, failedInvariant, invariant } from '@dxos/invariant';
8
8
  import { log } from '@dxos/log';
9
- import { SpaceArchiveFileStructure, type SpaceArchiveMetadata } from '@dxos/protocols';
9
+ import {
10
+ type FeedArchiveBlock,
11
+ type FeedArchiveMetadata,
12
+ SpaceArchiveFileStructure,
13
+ type SpaceArchiveMetadata,
14
+ } from '@dxos/protocols';
10
15
  import type { SpaceArchive } from '@dxos/protocols/proto/dxos/client/services';
11
16
 
17
+ /**
18
+ * Extracted feed data from the archive.
19
+ */
20
+ export type ExtractedFeed = {
21
+ metadata: FeedArchiveMetadata;
22
+ blocks: FeedArchiveBlock[];
23
+ };
24
+
12
25
  export type ExtractedSpaceArchive = {
13
26
  metadata: SpaceArchiveMetadata;
14
27
  documents: Record<DocumentId, Uint8Array>;
28
+ feeds: Record<string, ExtractedFeed>;
15
29
  };
16
30
 
17
31
  export const extractSpaceArchive = async (archive: SpaceArchive): Promise<ExtractedSpaceArchive> => {
@@ -29,6 +43,53 @@ export const extractSpaceArchive = async (archive: SpaceArchive): Promise<Extrac
29
43
  documents[documentId] = entry.content ?? failedInvariant();
30
44
  }
31
45
 
32
- log('extracted space archive', { metadata, documents });
33
- return { metadata, documents };
46
+ const feeds: Record<string, ExtractedFeed> = {};
47
+ const feedsPrefix = `${SpaceArchiveFileStructure.feeds}/`;
48
+ const feedEntries = entries.filter((entry) => entry.fileName.startsWith(feedsPrefix));
49
+
50
+ const feedMetadataByFeedId = new Map<string, FeedArchiveMetadata>();
51
+ const feedBlocksByFeedId = new Map<string, Map<number, FeedArchiveBlock[]>>();
52
+
53
+ for (const entry of feedEntries) {
54
+ const relativePath = entry.fileName.slice(feedsPrefix.length);
55
+ const pathParts = relativePath.split('/');
56
+ if (pathParts.length !== 2) {
57
+ continue;
58
+ }
59
+
60
+ const [feedId, fileName] = pathParts;
61
+ invariant(feedId, 'Feed ID is required');
62
+ invariant(fileName, 'File name is required');
63
+
64
+ if (fileName === SpaceArchiveFileStructure.feedMetadata) {
65
+ const feedMetadata = JSON.parse(entry.getContentAsText()) as FeedArchiveMetadata;
66
+ feedMetadataByFeedId.set(feedId, feedMetadata);
67
+ } else if (fileName.startsWith(SpaceArchiveFileStructure.feedBlocksPrefix) && fileName.endsWith('.json')) {
68
+ const chunkIndexStr = fileName.slice(SpaceArchiveFileStructure.feedBlocksPrefix.length).replace(/\.json$/, '');
69
+ const chunkIndex = parseInt(chunkIndexStr, 10);
70
+ invariant(!isNaN(chunkIndex), `Invalid chunk index: ${chunkIndexStr}`);
71
+
72
+ const blocks = JSON.parse(entry.getContentAsText()) as FeedArchiveBlock[];
73
+ if (!feedBlocksByFeedId.has(feedId)) {
74
+ feedBlocksByFeedId.set(feedId, new Map());
75
+ }
76
+ feedBlocksByFeedId.get(feedId)!.set(chunkIndex, blocks);
77
+ }
78
+ }
79
+
80
+ for (const [feedId, feedMetadata] of feedMetadataByFeedId) {
81
+ const blockChunks = feedBlocksByFeedId.get(feedId) ?? new Map<number, FeedArchiveBlock[]>();
82
+ const sortedChunkIndices = Array.from(blockChunks.keys()).sort((a, b) => a - b);
83
+ const allBlocks: FeedArchiveBlock[] = [];
84
+ for (const chunkIndex of sortedChunkIndices) {
85
+ allBlocks.push(...blockChunks.get(chunkIndex)!);
86
+ }
87
+ feeds[feedId] = {
88
+ metadata: feedMetadata,
89
+ blocks: allBlocks,
90
+ };
91
+ }
92
+
93
+ log('extracted space archive', { metadata, documents, feedCount: Object.keys(feeds).length });
94
+ return { metadata, documents, feeds };
34
95
  };
@@ -7,7 +7,14 @@ import type * as tar from '@obsidize/tar-browserify';
7
7
  import { type Context, Resource } from '@dxos/context';
8
8
  import { assertArgument, assertState } from '@dxos/invariant';
9
9
  import type { IdentityDid, SpaceId } from '@dxos/keys';
10
- import { SpaceArchiveFileStructure, type SpaceArchiveMetadata, SpaceArchiveVersion } from '@dxos/protocols';
10
+ import {
11
+ FEED_ARCHIVE_BLOCKS_PER_CHUNK,
12
+ type FeedArchiveBlock,
13
+ type FeedArchiveMetadata,
14
+ SpaceArchiveFileStructure,
15
+ type SpaceArchiveMetadata,
16
+ SpaceArchiveVersion,
17
+ } from '@dxos/protocols';
11
18
  import type { SpaceArchive } from '@dxos/protocols/proto/dxos/client/services';
12
19
  import { createFilename } from '@dxos/util';
13
20
 
@@ -57,6 +64,34 @@ export class SpaceArchiveWriter extends Resource {
57
64
  this._archive.addBinaryFile(`${SpaceArchiveFileStructure.documents}/${documentId}.bin`, data);
58
65
  }
59
66
 
67
+ /**
68
+ * Writes a feed with its metadata and blocks to the archive.
69
+ * Blocks are written in chunks of {@link FEED_ARCHIVE_BLOCKS_PER_CHUNK}.
70
+ */
71
+ async writeFeed(feedId: string, namespace: string, blocks: FeedArchiveBlock[]): Promise<void> {
72
+ assertArgument(feedId, 'feedId', 'Feed ID is required');
73
+ assertArgument(namespace, 'namespace', 'Namespace is required');
74
+ assertState(this._archive, 'Not open');
75
+
76
+ const feedPath = `${SpaceArchiveFileStructure.feeds}/${feedId}`;
77
+
78
+ const metadata: FeedArchiveMetadata = {
79
+ id: feedId,
80
+ namespace,
81
+ };
82
+ this._archive.addTextFile(`${feedPath}/${SpaceArchiveFileStructure.feedMetadata}`, JSON.stringify(metadata));
83
+
84
+ for (let chunkIndex = 0; chunkIndex * FEED_ARCHIVE_BLOCKS_PER_CHUNK < blocks.length; chunkIndex++) {
85
+ const start = chunkIndex * FEED_ARCHIVE_BLOCKS_PER_CHUNK;
86
+ const end = Math.min(start + FEED_ARCHIVE_BLOCKS_PER_CHUNK, blocks.length);
87
+ const chunk = blocks.slice(start, end);
88
+ this._archive.addTextFile(
89
+ `${feedPath}/${SpaceArchiveFileStructure.feedBlocksPrefix}${chunkIndex}.json`,
90
+ JSON.stringify(chunk),
91
+ );
92
+ }
93
+ }
94
+
60
95
  async finish(): Promise<SpaceArchive> {
61
96
  assertState(this._archive, 'Not open');
62
97
  assertState(this._meta, 'Not started');