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

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 (41) hide show
  1. package/dist/lib/browser/{chunk-J33W6T4Q.mjs → chunk-SVH323CC.mjs} +688 -124
  2. package/dist/lib/browser/chunk-SVH323CC.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +23 -15
  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 +1 -1
  7. package/dist/lib/node-esm/{chunk-34HKLADW.mjs → chunk-XCY7TTXS.mjs} +688 -124
  8. package/dist/lib/node-esm/chunk-XCY7TTXS.mjs.map +7 -0
  9. package/dist/lib/node-esm/index.mjs +23 -15
  10. package/dist/lib/node-esm/index.mjs.map +3 -3
  11. package/dist/lib/node-esm/meta.json +1 -1
  12. package/dist/lib/node-esm/testing/index.mjs +1 -1
  13. package/dist/types/src/packlets/logging/logging-service.d.ts +4 -0
  14. package/dist/types/src/packlets/logging/logging-service.d.ts.map +1 -1
  15. package/dist/types/src/packlets/services/feed-syncer.d.ts +59 -0
  16. package/dist/types/src/packlets/services/feed-syncer.d.ts.map +1 -0
  17. package/dist/types/src/packlets/services/feed-syncer.test.d.ts +2 -0
  18. package/dist/types/src/packlets/services/feed-syncer.test.d.ts.map +1 -0
  19. package/dist/types/src/packlets/services/platform.d.ts.map +1 -1
  20. package/dist/types/src/packlets/services/service-context.d.ts +1 -2
  21. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  22. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  23. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  24. package/dist/types/src/packlets/worker/worker-runtime.d.ts +11 -3
  25. package/dist/types/src/packlets/worker/worker-runtime.d.ts.map +1 -1
  26. package/dist/types/src/version.d.ts +1 -1
  27. package/dist/types/src/version.d.ts.map +1 -1
  28. package/dist/types/tsconfig.tsbuildinfo +1 -1
  29. package/package.json +43 -43
  30. package/src/packlets/logging/logging-service.ts +4 -0
  31. package/src/packlets/services/feed-syncer.test.ts +340 -0
  32. package/src/packlets/services/feed-syncer.ts +330 -0
  33. package/src/packlets/services/platform.ts +7 -1
  34. package/src/packlets/services/service-context.ts +84 -11
  35. package/src/packlets/services/service-host.ts +5 -3
  36. package/src/packlets/spaces/data-space.ts +3 -0
  37. package/src/packlets/worker/worker-runtime.ts +13 -5
  38. package/src/packlets/worker/worker-session.ts +4 -4
  39. package/src/version.ts +1 -1
  40. package/dist/lib/browser/chunk-J33W6T4Q.mjs.map +0 -7
  41. package/dist/lib/node-esm/chunk-34HKLADW.mjs.map +0 -7
@@ -0,0 +1,330 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import type * as SqlClient from '@effect/sql/SqlClient';
6
+ import { Encoder, decode as cborXdecode } from 'cbor-x';
7
+ import * as Effect from 'effect/Effect';
8
+ import * as Schema from 'effect/Schema';
9
+
10
+ import { AsyncTask, scheduleTask } from '@dxos/async';
11
+ import { Resource } from '@dxos/context';
12
+ import { type EdgeConnection, MessageSchema } from '@dxos/edge-client';
13
+ import { RuntimeProvider } from '@dxos/effect';
14
+ import { type FeedStore, SyncClient } from '@dxos/feed';
15
+ import { invariant } from '@dxos/invariant';
16
+ import { SpaceId } from '@dxos/keys';
17
+ import { FeedProtocol } from '@dxos/protocols';
18
+ import { EdgeService } from '@dxos/protocols';
19
+ import { createBuf } from '@dxos/protocols/buf';
20
+ import { type Message as RouterMessage } from '@dxos/protocols/buf/dxos/edge/messenger_pb';
21
+ import type { SqlTransaction } from '@dxos/sql-sqlite';
22
+ import { bufferToArray } from '@dxos/util';
23
+
24
+ const encoder = new Encoder({ tagUint8Array: false, useRecords: false });
25
+
26
+ const DEFAULT_MESSAGE_BLOCKS_LIMIT = 50;
27
+ const DEFAULT_SYNC_CONCURRENCY = 5;
28
+ const DEFAULT_POLLING_INTERVAL = 5_000;
29
+ const DEFAULT_POLL_REQUEST_THROTTLE_MS = 250;
30
+ const MAX_BLOCKING_SYNC_ITERATIONS = 100;
31
+
32
+ interface FeedSyncerOptions {
33
+ runtime: RuntimeProvider.RuntimeProvider<SqlClient.SqlClient | SqlTransaction.SqlTransaction>;
34
+ feedStore: FeedStore;
35
+ edgeClient: EdgeConnection;
36
+ peerId: string;
37
+ getSpaceIds: () => SpaceId[];
38
+
39
+ /**
40
+ * Namespaces to sync.
41
+ */
42
+ syncNamespaces: string[];
43
+
44
+ /**
45
+ * Maximum number of blocks to sync in a single message.
46
+ * @default 50
47
+ */
48
+ messageBlocksLimit?: number;
49
+
50
+ /**
51
+ * Maximum number of spaces to sync concurrently.
52
+ * @default 5
53
+ */
54
+ syncConcurrency?: number;
55
+
56
+ /**
57
+ * Interval between full polls.
58
+ * @default 10 seconds
59
+ */
60
+ pollingInterval?: number;
61
+
62
+ /**
63
+ * Minimum delay between externally requested best-effort polls.
64
+ * @default 250 ms
65
+ */
66
+ pollRequestThrottleMs?: number;
67
+ }
68
+
69
+ export class FeedSyncer extends Resource {
70
+ readonly #syncNamespaces: string[];
71
+ readonly #messageBlocksLimit: number;
72
+ readonly #syncConcurrency: number;
73
+ readonly #pollingInterval: number;
74
+ readonly #pollRequestThrottleMs: number;
75
+
76
+ readonly #runtime: RuntimeProvider.RuntimeProvider<SqlClient.SqlClient | SqlTransaction.SqlTransaction>;
77
+ readonly #feedStore: FeedStore;
78
+ readonly #edgeClient: EdgeConnection;
79
+ readonly #syncClient: SyncClient;
80
+ readonly #getSpaceIds: () => SpaceId[];
81
+
82
+ #spacesToPoll = new Set<SpaceId>();
83
+ /** Last time full poll was completed. */
84
+ #lastFullPoll: number | null = null;
85
+ #throttledPollScheduled = false;
86
+ #lastRequestedPollAt: number | null = null;
87
+
88
+ constructor(options: FeedSyncerOptions) {
89
+ super();
90
+ this.#runtime = options.runtime;
91
+ this.#feedStore = options.feedStore;
92
+ this.#edgeClient = options.edgeClient;
93
+ this.#syncClient = new SyncClient({
94
+ peerId: options.peerId,
95
+ feedStore: options.feedStore,
96
+ sendMessage: this.#sendMessage.bind(this),
97
+ });
98
+ this.#getSpaceIds = options.getSpaceIds;
99
+ this.#syncNamespaces = options.syncNamespaces;
100
+ this.#messageBlocksLimit = options.messageBlocksLimit ?? DEFAULT_MESSAGE_BLOCKS_LIMIT;
101
+ this.#syncConcurrency = options.syncConcurrency ?? DEFAULT_SYNC_CONCURRENCY;
102
+ this.#pollingInterval = options.pollingInterval ?? DEFAULT_POLLING_INTERVAL;
103
+ this.#pollRequestThrottleMs = options.pollRequestThrottleMs ?? DEFAULT_POLL_REQUEST_THROTTLE_MS;
104
+ }
105
+
106
+ protected override async _open(): Promise<void> {
107
+ this._ctx.onDispose(
108
+ this.#edgeClient.onMessage((msg: RouterMessage) => {
109
+ if (!msg.serviceId) {
110
+ return;
111
+ }
112
+ const service = msg.serviceId.split(':')[0];
113
+ if (service !== EdgeService.QUEUE_REPLICATOR) {
114
+ return;
115
+ }
116
+ const handleMessageEffect = Effect.gen(this, function* () {
117
+ const decoded = yield* Effect.try({
118
+ try: () => cborXdecode(msg.payload!.value),
119
+ catch: (error) => new Error(`Failed to decode feed sync message: ${error}`),
120
+ });
121
+ const payload = yield* Schema.validate(FeedProtocol.ProtocolMessage)(decoded);
122
+ yield* this.#syncClient.handleMessage(payload);
123
+ });
124
+
125
+ void RuntimeProvider.runPromise(this.#runtime)(handleMessageEffect);
126
+ }),
127
+ );
128
+
129
+ this._ctx.onDispose(
130
+ // NOTE: This will fire immediately if the connection is already open.
131
+ this.#edgeClient.onReconnected(async () => {}),
132
+ );
133
+
134
+ this.#feedStore.onNewBlocks.on(this._ctx, () => {
135
+ this.#pushTask.schedule();
136
+ });
137
+
138
+ await this.#pollTask.open();
139
+ await this.#pushTask.open();
140
+
141
+ this.#resetSpacesToPoll();
142
+ this.#pollTask.schedule();
143
+ }
144
+
145
+ protected override async _close(): Promise<void> {
146
+ await this.#pollTask.close();
147
+ await this.#pushTask.close();
148
+ }
149
+
150
+ /**
151
+ * Schedules a best-effort pull without blocking the caller.
152
+ */
153
+ schedulePoll(): void {
154
+ this.#resetSpacesToPoll();
155
+ if (this.#throttledPollScheduled) {
156
+ return;
157
+ }
158
+
159
+ const now = Date.now();
160
+ const delay =
161
+ this.#lastRequestedPollAt == null
162
+ ? 0
163
+ : Math.max(this.#pollRequestThrottleMs - (now - this.#lastRequestedPollAt), 0);
164
+ this.#throttledPollScheduled = true;
165
+ scheduleTask(
166
+ this._ctx,
167
+ () => {
168
+ this.#throttledPollScheduled = false;
169
+ this.#lastRequestedPollAt = Date.now();
170
+ this.#pollTask.schedule();
171
+ },
172
+ delay,
173
+ );
174
+ }
175
+
176
+ /**
177
+ * Performs queue sync and blocks until there are no pending sync batches.
178
+ */
179
+ async syncBlocking({
180
+ spaceId,
181
+ subspaceTag,
182
+ shouldPush = true,
183
+ shouldPull = true,
184
+ }: {
185
+ spaceId: SpaceId;
186
+ subspaceTag: string;
187
+ shouldPush?: boolean;
188
+ shouldPull?: boolean;
189
+ }): Promise<void> {
190
+ invariant(SpaceId.isValid(spaceId));
191
+ invariant(FeedProtocol.isWellKnownNamespace(subspaceTag));
192
+ if (!shouldPush && !shouldPull) {
193
+ return;
194
+ }
195
+
196
+ await RuntimeProvider.runPromise(this.#runtime)(
197
+ Effect.gen(this, function* () {
198
+ let done = false;
199
+ let iterations = 0;
200
+ while (!done) {
201
+ done = true;
202
+ if (shouldPull) {
203
+ const pullResult = yield* this.#syncClient.pull({
204
+ spaceId,
205
+ feedNamespace: subspaceTag,
206
+ limit: this.#messageBlocksLimit,
207
+ });
208
+ done &&= pullResult.done;
209
+ }
210
+
211
+ if (shouldPush) {
212
+ const pushResult = yield* this.#syncClient.push({
213
+ spaceId,
214
+ feedNamespace: subspaceTag,
215
+ limit: this.#messageBlocksLimit,
216
+ });
217
+ done &&= pushResult.done;
218
+ }
219
+ iterations++;
220
+ if (iterations > MAX_BLOCKING_SYNC_ITERATIONS) {
221
+ throw new Error('Blocking sync exceeded max iterations.');
222
+ }
223
+ }
224
+ }),
225
+ );
226
+ }
227
+
228
+ #resetSpacesToPoll(): void {
229
+ this.#spacesToPoll.clear();
230
+ this.#getSpaceIds().forEach((spaceId) => {
231
+ this.#spacesToPoll.add(spaceId);
232
+ });
233
+ this.#lastFullPoll = Date.now();
234
+ }
235
+
236
+ #sendMessage(message: FeedProtocol.QueryRequest | FeedProtocol.AppendRequest): Effect.Effect<void, unknown, never> {
237
+ return Effect.gen(this, function* () {
238
+ const encoded = encoder.encode(message);
239
+ yield* Effect.tryPromise(async () =>
240
+ this.#edgeClient.send(
241
+ createBuf(MessageSchema, {
242
+ source: {
243
+ identityKey: this.#edgeClient.identityKey,
244
+ peerKey: this.#edgeClient.peerKey,
245
+ },
246
+ serviceId: this.#getTargetServiceId(message),
247
+ payload: { value: bufferToArray(encoded) },
248
+ }),
249
+ ),
250
+ );
251
+ });
252
+ }
253
+
254
+ #getTargetServiceId(message: FeedProtocol.QueryRequest | FeedProtocol.AppendRequest): string {
255
+ // TODO(dmaretskyi): Perhaps in the future we will want to include the queue namespace here as well.
256
+ // This would require putting it at the top level of the message.
257
+ // For now, we let the edge router handle it.
258
+ return FeedProtocol.encodeServiceId(message.feedNamespace, message.spaceId as SpaceId);
259
+ }
260
+
261
+ readonly #pollTask = new AsyncTask(async () =>
262
+ Effect.gen(this, function* () {
263
+ yield* Effect.forEach(
264
+ this.#spacesToPoll,
265
+ (spaceId) =>
266
+ Effect.gen(this, function* () {
267
+ let doneForAllNamespaces = true;
268
+ for (const feedNamespace of this.#syncNamespaces) {
269
+ const { done } = yield* this.#syncClient.pull({
270
+ spaceId,
271
+ feedNamespace,
272
+ limit: this.#messageBlocksLimit,
273
+ });
274
+ if (!done) {
275
+ doneForAllNamespaces = false;
276
+ }
277
+ }
278
+ if (doneForAllNamespaces) {
279
+ this.#spacesToPoll.delete(spaceId);
280
+ }
281
+ }),
282
+ { concurrency: this.#syncConcurrency },
283
+ );
284
+
285
+ // If its time to do a full poll, reset the spaces to poll and schedule the next poll immediately.
286
+ if (this.#lastFullPoll == null || Date.now() - this.#lastFullPoll > this.#pollingInterval) {
287
+ this.#resetSpacesToPoll();
288
+ this.#pollTask.schedule();
289
+ } else if (this.#spacesToPoll.size > 0) {
290
+ // If there are some spaces still syncing, poll them immediately.
291
+ this.#pollTask.schedule();
292
+ } else {
293
+ // All spaces sync, and there's time before the next full poll, schedule it later.
294
+ this.#resetSpacesToPoll();
295
+ scheduleTask(
296
+ this._ctx,
297
+ () => this.#pollTask.schedule(),
298
+ Math.max(this.#pollingInterval - (Date.now() - (this.#lastFullPoll ?? 0)), 0),
299
+ );
300
+ }
301
+ }).pipe(RuntimeProvider.runPromise(this.#runtime)),
302
+ );
303
+
304
+ readonly #pushTask = new AsyncTask(async () =>
305
+ Effect.gen(this, function* () {
306
+ yield* Effect.forEach(
307
+ this.#getSpaceIds(),
308
+ (spaceId) =>
309
+ Effect.gen(this, function* () {
310
+ let doneForAllNamespaces = true;
311
+ for (const feedNamespace of this.#syncNamespaces) {
312
+ const { done } = yield* this.#syncClient.push({
313
+ spaceId,
314
+ feedNamespace,
315
+ limit: this.#messageBlocksLimit,
316
+ });
317
+ if (!done) {
318
+ doneForAllNamespaces = false;
319
+ }
320
+ }
321
+ if (!doneForAllNamespaces) {
322
+ // Keep pushing until all blocks are pushed.
323
+ this.#pushTask.schedule();
324
+ }
325
+ }),
326
+ { concurrency: this.#syncConcurrency },
327
+ );
328
+ }).pipe(RuntimeProvider.runPromise(this.#runtime)),
329
+ );
330
+ }
@@ -14,12 +14,18 @@ export const getPlatform = (): Platform => {
14
14
  userAgent,
15
15
  uptime: Math.floor((Date.now() - window.performance.timeOrigin) / 1_000),
16
16
  };
17
- } else {
17
+ } else if (typeof SharedWorkerGlobalScope !== 'undefined') {
18
18
  // Shared worker.
19
19
  return {
20
20
  type: Platform.PLATFORM_TYPE.SHARED_WORKER,
21
21
  uptime: Math.floor((Date.now() - performance.timeOrigin) / 1_000),
22
22
  };
23
+ } else {
24
+ // Dedicated worker.
25
+ return {
26
+ type: Platform.PLATFORM_TYPE.DEDICATED_WORKER,
27
+ uptime: Math.floor((Date.now() - performance.timeOrigin) / 1_000),
28
+ };
23
29
  }
24
30
  } else {
25
31
  // Node.
@@ -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';
@@ -56,6 +57,8 @@ import {
56
57
  } from '../invitations';
57
58
  import { DataSpaceManager, type DataSpaceManagerRuntimeProps, type SigningContext } from '../spaces';
58
59
 
60
+ import { FeedSyncer } from './feed-syncer';
61
+
59
62
  export type ServiceContextRuntimeProps = Pick<
60
63
  IdentityManagerProps,
61
64
  'devicePresenceOfflineTimeout' | 'devicePresenceAnnounceInterval'
@@ -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 (request) => {
173
+ return this._feedSyncer?.syncBlocking({
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,31 +231,66 @@ 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
 
238
+ log('setting network identity...');
220
239
  await this._setNetworkIdentity();
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) {
259
+ log('adding mesh replicator...');
229
260
  await this.echoHost.addReplicator(this._meshReplicator);
261
+ log('mesh replicator added');
230
262
  }
231
263
  if (this._echoEdgeReplicator) {
264
+ log('adding edge replicator...');
232
265
  await this.echoHost.addReplicator(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) {
278
+ log('joining network...');
239
279
  await this.identityManager.identity.joinNetwork();
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
 
289
+ log('opening feed syncer...');
290
+ await this._feedSyncer?.open();
291
+ log('feed syncer opened');
292
+
293
+ log('loading persistent invitations...');
243
294
  const loadedInvitations = await this.invitationsManager.loadPersistentInvitations();
244
295
  log('loaded persistent invitations', { count: loadedInvitations.invitations?.length });
245
296
 
@@ -249,6 +300,9 @@ 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
  }
@@ -314,7 +368,7 @@ export class ServiceContext extends Resource {
314
368
  // Called when identity is created.
315
369
  @Trace.span()
316
370
  private async _initialize(ctx: Context): Promise<void> {
317
- log('initializing spaces...');
371
+ log('_initialize: start');
318
372
  const identity = this.identityManager.identity ?? failUndefined();
319
373
  const signingContext: SigningContext = {
320
374
  credentialSigner: identity.getIdentityCredentialSigner(),
@@ -326,6 +380,7 @@ export class ServiceContext extends Resource {
326
380
  },
327
381
  };
328
382
 
383
+ log('_initialize: creating DataSpaceManager');
329
384
  this.dataSpaceManager = new DataSpaceManager({
330
385
  spaceManager: this.spaceManager,
331
386
  metadataStore: this.metadataStore,
@@ -341,7 +396,9 @@ export class ServiceContext extends Resource {
341
396
  runtimeProps: this._runtimeProps as DataSpaceManagerRuntimeProps,
342
397
  edgeFeatures: this._edgeFeatures,
343
398
  });
399
+ log('_initialize: opening DataSpaceManager...');
344
400
  await this.dataSpaceManager.open();
401
+ log('_initialize: DataSpaceManager opened');
345
402
 
346
403
  this.edgeAgentManager = new EdgeAgentManager(
347
404
  this._edgeFeatures,
@@ -349,13 +406,16 @@ export class ServiceContext extends Resource {
349
406
  this.dataSpaceManager,
350
407
  identity,
351
408
  );
409
+ log('_initialize: opening EdgeAgentManager...');
352
410
  await this.edgeAgentManager.open();
411
+ log('_initialize: EdgeAgentManager opened');
353
412
 
354
413
  this._handlerFactories.set(Invitation.Kind.SPACE, (invitation) => {
355
414
  invariant(this.dataSpaceManager, 'dataSpaceManager not initialized yet');
356
415
  return new SpaceInvitationProtocol(this.dataSpaceManager, signingContext, this.keyring, invitation.spaceKey);
357
416
  });
358
417
  this.initialized.wake();
418
+ log('_initialize: initialized.wake() called');
359
419
 
360
420
  this._deviceSpaceSync = {
361
421
  processCredential: async (credential: Credential) => {
@@ -392,17 +452,20 @@ export class ServiceContext extends Resource {
392
452
  }
393
453
 
394
454
  private async _setNetworkIdentity(params?: { deviceCredential: Credential }): Promise<void> {
455
+ log('_setNetworkIdentity: acquiring mutex...');
395
456
  using _ = await this._edgeIdentityUpdateMutex.acquire();
457
+ log('_setNetworkIdentity: mutex acquired');
396
458
 
397
459
  let edgeIdentity: EdgeIdentity;
398
460
  const identity = this.identityManager.identity;
399
461
  if (identity) {
400
- log('setting identity on edge connection', {
462
+ log('_setNetworkIdentity: has identity', {
401
463
  identity: identity.identityKey.toHex(),
402
- swarms: this.networkManager.topics,
464
+ hasDeviceCredential: !!params?.deviceCredential,
403
465
  });
404
466
 
405
467
  if (params?.deviceCredential) {
468
+ log('_setNetworkIdentity: creating chain edge identity with device credential...');
406
469
  edgeIdentity = await createChainEdgeIdentity(
407
470
  identity.signer,
408
471
  identity.identityKey,
@@ -410,14 +473,20 @@ export class ServiceContext extends Resource {
410
473
  params?.deviceCredential && { credential: params.deviceCredential },
411
474
  [], // TODO(dmaretskyi): Service access credentials.
412
475
  );
476
+ log('_setNetworkIdentity: chain edge identity created');
413
477
  } else {
478
+ log('_setNetworkIdentity: waiting for identity.ready()...');
414
479
  // TODO: throw here or from identity if device chain can't be loaded, to avoid indefinite hangup
415
480
  await warnAfterTimeout(10_000, 'Waiting for identity to be ready for edge connection', async () => {
416
481
  await identity.ready();
417
482
  });
483
+ log('_setNetworkIdentity: identity.ready() resolved', {
484
+ hasDeviceCredentialChain: !!identity.deviceCredentialChain,
485
+ });
418
486
 
419
487
  invariant(identity.deviceCredentialChain);
420
488
 
489
+ log('_setNetworkIdentity: creating chain edge identity...');
421
490
  edgeIdentity = await createChainEdgeIdentity(
422
491
  identity.signer,
423
492
  identity.identityKey,
@@ -425,9 +494,12 @@ export class ServiceContext extends Resource {
425
494
  identity.deviceCredentialChain,
426
495
  [], // TODO(dmaretskyi): Service access credentials.
427
496
  );
497
+ log('_setNetworkIdentity: chain edge identity created');
428
498
  }
429
499
  } else {
500
+ log('_setNetworkIdentity: no identity, creating ephemeral edge identity...');
430
501
  edgeIdentity = await createEphemeralEdgeIdentity();
502
+ log('_setNetworkIdentity: ephemeral edge identity created');
431
503
  }
432
504
 
433
505
  this._edgeConnection?.setIdentity(edgeIdentity);
@@ -436,5 +508,6 @@ export class ServiceContext extends Resource {
436
508
  identityKey: edgeIdentity.identityKey,
437
509
  peerKey: edgeIdentity.peerKey,
438
510
  });
511
+ log('_setNetworkIdentity: done');
439
512
  }
440
513
  }
@@ -244,9 +244,6 @@ export class ClientServicesHost {
244
244
  if (this._runtimeProps.enableVectorIndexing === undefined) {
245
245
  this._runtimeProps.enableVectorIndexing = config?.get('runtime.client.enableVectorIndexing', false);
246
246
  }
247
- if (this._runtimeProps.enableLocalQueues === undefined) {
248
- this._runtimeProps.enableLocalQueues = config?.get('runtime.client.enableLocalQueues', false);
249
- }
250
247
 
251
248
  invariant(!this._config, 'config already set');
252
249
  this._config = config;
@@ -395,8 +392,13 @@ export class ClientServicesHost {
395
392
  EdgeAgentService: new EdgeAgentServiceImpl(agentManagerProvider, this._edgeConnection),
396
393
  });
397
394
 
395
+ log('service-host: opening service context...');
398
396
  await this._serviceContext.open(ctx);
397
+ log('service-host: service context opened');
398
+
399
+ log('service-host: opening identity service...');
399
400
  await identityService.open();
401
+ log('service-host: identity service opened');
400
402
 
401
403
  const devtoolsProxy = this._config?.get('runtime.client.devtoolsProxy');
402
404
  if (devtoolsProxy) {
@@ -450,6 +450,9 @@ export class DataSpace {
450
450
 
451
451
  log('credentials notarized');
452
452
  } catch (err) {
453
+ if (err instanceof ContextDisposedError) {
454
+ return;
455
+ }
453
456
  log.error('error notarizing credentials for feed admission', err);
454
457
  throw err;
455
458
  }