@dxos/echo-pipeline 0.7.4 → 0.7.5-main.9cb18ac

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 (39) hide show
  1. package/dist/lib/browser/{chunk-LZK5YFYE.mjs → chunk-KY5AZOEF.mjs} +26 -8
  2. package/dist/lib/browser/{chunk-LZK5YFYE.mjs.map → chunk-KY5AZOEF.mjs.map} +3 -3
  3. package/dist/lib/browser/index.mjs +113 -64
  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/{chunk-MACQJ2EP.cjs → chunk-SXCQEIFA.cjs} +29 -11
  8. package/dist/lib/node/{chunk-MACQJ2EP.cjs.map → chunk-SXCQEIFA.cjs.map} +3 -3
  9. package/dist/lib/node/index.cjs +129 -87
  10. package/dist/lib/node/index.cjs.map +3 -3
  11. package/dist/lib/node/meta.json +1 -1
  12. package/dist/lib/node/testing/index.cjs +10 -10
  13. package/dist/lib/node-esm/{chunk-JIZPSASG.mjs → chunk-ZIPZXXS7.mjs} +26 -8
  14. package/dist/lib/node-esm/{chunk-JIZPSASG.mjs.map → chunk-ZIPZXXS7.mjs.map} +3 -3
  15. package/dist/lib/node-esm/index.mjs +113 -64
  16. package/dist/lib/node-esm/index.mjs.map +3 -3
  17. package/dist/lib/node-esm/meta.json +1 -1
  18. package/dist/lib/node-esm/testing/index.mjs +1 -1
  19. package/dist/types/src/automerge/automerge-host.d.ts +5 -1
  20. package/dist/types/src/automerge/automerge-host.d.ts.map +1 -1
  21. package/dist/types/src/automerge/collection-synchronizer.d.ts +1 -0
  22. package/dist/types/src/automerge/collection-synchronizer.d.ts.map +1 -1
  23. package/dist/types/src/automerge/echo-network-adapter.d.ts +1 -0
  24. package/dist/types/src/automerge/echo-network-adapter.d.ts.map +1 -1
  25. package/dist/types/src/automerge/leveldb-storage-adapter.d.ts +1 -1
  26. package/dist/types/src/db-host/echo-host.d.ts +3 -2
  27. package/dist/types/src/db-host/echo-host.d.ts.map +1 -1
  28. package/dist/types/src/edge/echo-edge-replicator.d.ts.map +1 -1
  29. package/dist/types/src/space/space-manager.d.ts +1 -0
  30. package/dist/types/src/space/space-manager.d.ts.map +1 -1
  31. package/dist/types/tsconfig.tsbuildinfo +1 -0
  32. package/package.json +34 -34
  33. package/src/automerge/automerge-host.ts +49 -14
  34. package/src/automerge/collection-synchronizer.ts +8 -4
  35. package/src/automerge/echo-network-adapter.ts +7 -0
  36. package/src/automerge/mesh-echo-replicator.ts +2 -2
  37. package/src/db-host/echo-host.ts +4 -1
  38. package/src/edge/echo-edge-replicator.ts +47 -19
  39. package/src/space/space-manager.ts +17 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/echo-pipeline",
3
- "version": "0.7.4",
3
+ "version": "0.7.5-main.9cb18ac",
4
4
  "description": "ECHO database.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -37,44 +37,44 @@
37
37
  "crc-32": "^1.2.2",
38
38
  "level-transcoder": "^1.0.1",
39
39
  "lodash.isequal": "^4.5.0",
40
- "@dxos/async": "0.7.4",
41
- "@dxos/automerge": "0.7.4",
42
- "@dxos/context": "0.7.4",
43
- "@dxos/codec-protobuf": "0.7.4",
44
- "@dxos/credentials": "0.7.4",
45
- "@dxos/crypto": "0.7.4",
46
- "@dxos/debug": "0.7.4",
47
- "@dxos/feed-store": "0.7.4",
48
- "@dxos/hypercore": "0.7.4",
49
- "@dxos/echo-protocol": "0.7.4",
50
- "@dxos/echo-schema": "0.7.4",
51
- "@dxos/edge-client": "0.7.4",
52
- "@dxos/invariant": "0.7.4",
53
- "@dxos/indexing": "0.7.4",
54
- "@dxos/keyring": "0.7.4",
55
- "@dxos/kv-store": "0.7.4",
56
- "@dxos/log": "0.7.4",
57
- "@dxos/keys": "0.7.4",
58
- "@dxos/messaging": "0.7.4",
59
- "@dxos/node-std": "0.7.4",
60
- "@dxos/protocols": "0.7.4",
61
- "@dxos/random-access-storage": "0.7.4",
62
- "@dxos/network-manager": "0.7.4",
63
- "@dxos/teleport": "0.7.4",
64
- "@dxos/teleport-extension-automerge-replicator": "0.7.4",
65
- "@dxos/teleport-extension-object-sync": "0.7.4",
66
- "@dxos/teleport-extension-gossip": "0.7.4",
67
- "@dxos/timeframe": "0.7.4",
68
- "@dxos/teleport-extension-replicator": "0.7.4",
69
- "@dxos/typings": "0.7.4",
70
- "@dxos/tracing": "0.7.4",
71
- "@dxos/util": "0.7.4"
40
+ "@dxos/async": "0.7.5-main.9cb18ac",
41
+ "@dxos/codec-protobuf": "0.7.5-main.9cb18ac",
42
+ "@dxos/automerge": "0.7.5-main.9cb18ac",
43
+ "@dxos/context": "0.7.5-main.9cb18ac",
44
+ "@dxos/credentials": "0.7.5-main.9cb18ac",
45
+ "@dxos/crypto": "0.7.5-main.9cb18ac",
46
+ "@dxos/debug": "0.7.5-main.9cb18ac",
47
+ "@dxos/echo-protocol": "0.7.5-main.9cb18ac",
48
+ "@dxos/echo-schema": "0.7.5-main.9cb18ac",
49
+ "@dxos/edge-client": "0.7.5-main.9cb18ac",
50
+ "@dxos/feed-store": "0.7.5-main.9cb18ac",
51
+ "@dxos/indexing": "0.7.5-main.9cb18ac",
52
+ "@dxos/invariant": "0.7.5-main.9cb18ac",
53
+ "@dxos/hypercore": "0.7.5-main.9cb18ac",
54
+ "@dxos/keyring": "0.7.5-main.9cb18ac",
55
+ "@dxos/keys": "0.7.5-main.9cb18ac",
56
+ "@dxos/log": "0.7.5-main.9cb18ac",
57
+ "@dxos/messaging": "0.7.5-main.9cb18ac",
58
+ "@dxos/node-std": "0.7.5-main.9cb18ac",
59
+ "@dxos/network-manager": "0.7.5-main.9cb18ac",
60
+ "@dxos/kv-store": "0.7.5-main.9cb18ac",
61
+ "@dxos/protocols": "0.7.5-main.9cb18ac",
62
+ "@dxos/teleport-extension-automerge-replicator": "0.7.5-main.9cb18ac",
63
+ "@dxos/teleport-extension-gossip": "0.7.5-main.9cb18ac",
64
+ "@dxos/random-access-storage": "0.7.5-main.9cb18ac",
65
+ "@dxos/teleport": "0.7.5-main.9cb18ac",
66
+ "@dxos/teleport-extension-object-sync": "0.7.5-main.9cb18ac",
67
+ "@dxos/teleport-extension-replicator": "0.7.5-main.9cb18ac",
68
+ "@dxos/tracing": "0.7.5-main.9cb18ac",
69
+ "@dxos/typings": "0.7.5-main.9cb18ac",
70
+ "@dxos/timeframe": "0.7.5-main.9cb18ac",
71
+ "@dxos/util": "0.7.5-main.9cb18ac"
72
72
  },
73
73
  "devDependencies": {
74
74
  "@types/lodash.isequal": "^4.5.0",
75
75
  "fast-check": "^3.19.0",
76
76
  "get-port-please": "^3.1.1",
77
- "@dxos/test-utils": "0.7.4"
77
+ "@dxos/test-utils": "0.7.5-main.9cb18ac"
78
78
  },
79
79
  "publishConfig": {
80
80
  "access": "public"
@@ -44,6 +44,8 @@ import { LevelDBStorageAdapter, type BeforeSaveParams } from './leveldb-storage-
44
44
 
45
45
  export type PeerIdProvider = () => string | undefined;
46
46
 
47
+ export type RootDocumentSpaceKeyProvider = (documentId: string) => PublicKey | undefined;
48
+
47
49
  export type AutomergeHostParams = {
48
50
  db: LevelDB;
49
51
 
@@ -54,6 +56,7 @@ export type AutomergeHostParams = {
54
56
  * Used for creating stable ids. A random key is generated on open, if no value is provided.
55
57
  */
56
58
  peerIdProvider?: PeerIdProvider;
59
+ getSpaceKeyByRootDocumentId?: RootDocumentSpaceKeyProvider;
57
60
  };
58
61
 
59
62
  export type LoadDocOptions = {
@@ -90,10 +93,17 @@ export class AutomergeHost extends Resource {
90
93
  private _peerId!: PeerId;
91
94
 
92
95
  private readonly _peerIdProvider?: PeerIdProvider;
96
+ private readonly _getSpaceKeyByRootDocumentId?: RootDocumentSpaceKeyProvider;
93
97
 
94
98
  public readonly collectionStateUpdated = new Event<{ collectionId: CollectionId }>();
95
99
 
96
- constructor({ db, indexMetadataStore, dataMonitor, peerIdProvider }: AutomergeHostParams) {
100
+ constructor({
101
+ db,
102
+ indexMetadataStore,
103
+ dataMonitor,
104
+ peerIdProvider,
105
+ getSpaceKeyByRootDocumentId,
106
+ }: AutomergeHostParams) {
97
107
  super();
98
108
  this._db = db;
99
109
  this._storage = new LevelDBStorageAdapter({
@@ -114,6 +124,7 @@ export class AutomergeHost extends Resource {
114
124
  this._headsStore = new HeadsStore({ db: db.sublevel('heads') });
115
125
  this._indexMetadataStore = indexMetadataStore;
116
126
  this._peerIdProvider = peerIdProvider;
127
+ this._getSpaceKeyByRootDocumentId = getSpaceKeyByRootDocumentId;
117
128
  }
118
129
 
119
130
  protected override async _open() {
@@ -132,14 +143,28 @@ export class AutomergeHost extends Resource {
132
143
  ],
133
144
  });
134
145
 
135
- Event.wrap(this._echoNetworkAdapter, 'peer-candidate').on(this._ctx, ((e: PeerCandidatePayload) =>
136
- this._onPeerConnected(e.peerId)) as any);
137
- Event.wrap(this._echoNetworkAdapter, 'peer-disconnected').on(this._ctx, ((e: PeerDisconnectedPayload) =>
138
- this._onPeerDisconnected(e.peerId)) as any);
146
+ let updatingAuthScope = false;
147
+ Event.wrap(this._echoNetworkAdapter, 'peer-candidate').on(
148
+ this._ctx,
149
+ ((e: PeerCandidatePayload) => !updatingAuthScope && this._onPeerConnected(e.peerId)) as any,
150
+ );
151
+ Event.wrap(this._echoNetworkAdapter, 'peer-disconnected').on(
152
+ this._ctx,
153
+ ((e: PeerDisconnectedPayload) => !updatingAuthScope && this._onPeerDisconnected(e.peerId)) as any,
154
+ );
139
155
 
140
- this._collectionSynchronizer.remoteStateUpdated.on(this._ctx, ({ collectionId, peerId }) => {
156
+ this._collectionSynchronizer.remoteStateUpdated.on(this._ctx, ({ collectionId, peerId, newDocsAppeared }) => {
141
157
  this._onRemoteCollectionStateUpdated(collectionId, peerId);
142
158
  this.collectionStateUpdated.emit({ collectionId: collectionId as CollectionId });
159
+ // We use collection lookups during share policy check, so we might need to update share policy for the new doc
160
+ if (newDocsAppeared) {
161
+ updatingAuthScope = true;
162
+ try {
163
+ this._echoNetworkAdapter.onConnectionAuthScopeChanged(peerId);
164
+ } finally {
165
+ updatingAuthScope = false;
166
+ }
167
+ }
143
168
  });
144
169
 
145
170
  await this._echoNetworkAdapter.open();
@@ -351,16 +376,23 @@ export class AutomergeHost extends Resource {
351
376
 
352
377
  private async _getContainingSpaceForDocument(documentId: string): Promise<PublicKey | null> {
353
378
  const doc = this._repo.handles[documentId as any]?.docSync();
354
- if (!doc) {
355
- return null;
379
+ if (doc) {
380
+ const spaceKeyHex = getSpaceKeyFromDoc(doc);
381
+ if (spaceKeyHex) {
382
+ return PublicKey.from(spaceKeyHex);
383
+ }
356
384
  }
357
-
358
- const spaceKeyHex = getSpaceKeyFromDoc(doc);
359
- if (!spaceKeyHex) {
360
- return null;
385
+ /**
386
+ * Edge case on the initial space setup.
387
+ * A peer is maybe trying to share space root document with us after a successful invitation.
388
+ * We don't have a document to check access block locally, so we need to rely on external sources (space metada).
389
+ */
390
+ const rootDocSpaceKey = this._getSpaceKeyByRootDocumentId?.(documentId);
391
+ if (rootDocSpaceKey) {
392
+ return rootDocSpaceKey;
361
393
  }
362
394
 
363
- return PublicKey.from(spaceKeyHex);
395
+ return null;
364
396
  }
365
397
 
366
398
  /**
@@ -496,7 +528,10 @@ export class AutomergeHost extends Resource {
496
528
  return;
497
529
  }
498
530
 
499
- log.info('replication documents after collection sync', {
531
+ log.info('replicating documents after collection sync', {
532
+ collectionId,
533
+ peerId,
534
+ toReplicate,
500
535
  count: toReplicate.length,
501
536
  });
502
537
 
@@ -35,7 +35,7 @@ export class CollectionSynchronizer extends Resource {
35
35
 
36
36
  private readonly _connectedPeers = new Set<PeerId>();
37
37
 
38
- public readonly remoteStateUpdated = new Event<{ collectionId: string; peerId: PeerId }>();
38
+ public readonly remoteStateUpdated = new Event<{ collectionId: string; peerId: PeerId; newDocsAppeared: boolean }>();
39
39
 
40
40
  constructor(params: CollectionSynchronizerParams) {
41
41
  super();
@@ -121,7 +121,7 @@ export class CollectionSynchronizer extends Resource {
121
121
  return;
122
122
  }
123
123
  for (const [collectionId, state] of this._perCollectionStates.entries()) {
124
- if (this._shouldSyncCollection(collectionId, peerId)) {
124
+ if (this._activeCollections.has(collectionId) && this._shouldSyncCollection(collectionId, peerId)) {
125
125
  state.interestedPeers.add(peerId);
126
126
  state.lastQueried.set(peerId, Date.now());
127
127
  this._queryCollectionState(collectionId, peerId);
@@ -159,8 +159,12 @@ export class CollectionSynchronizer extends Resource {
159
159
  log('onRemoteStateReceived', { collectionId, peerId, state });
160
160
  validateCollectionState(state);
161
161
  const perCollectionState = this._getOrCreatePerCollectionState(collectionId);
162
- perCollectionState.remoteStates.set(peerId, state);
163
- this.remoteStateUpdated.emit({ peerId, collectionId });
162
+ const existingState = perCollectionState.remoteStates.get(peerId) ?? { documents: {} };
163
+ const diff = diffCollectionState(existingState, state);
164
+ if (diff.missingOnLocal.length > 0 || diff.different.length > 0) {
165
+ perCollectionState.remoteStates.set(peerId, state);
166
+ this.remoteStateUpdated.emit({ peerId, collectionId, newDocsAppeared: diff.missingOnLocal.length > 0 });
167
+ }
164
168
  }
165
169
 
166
170
  private _getOrCreatePerCollectionState(collectionId: string): PerCollectionState {
@@ -246,6 +246,13 @@ export class EchoNetworkAdapter extends NetworkAdapter {
246
246
  this._params.monitor?.recordMessageReceived(message);
247
247
  }
248
248
 
249
+ public onConnectionAuthScopeChanged(peer: PeerId) {
250
+ const entry = this._connections.get(peer);
251
+ if (entry) {
252
+ this._onConnectionAuthScopeChanged(entry.connection);
253
+ }
254
+ }
255
+
249
256
  /**
250
257
  * Trigger doc-synchronizer shared documents set recalculation. Happens on peer-candidate.
251
258
  * TODO(y): replace with a proper API call when sharePolicy update becomes supported by automerge-repo
@@ -88,10 +88,10 @@ export class MeshEchoReplicator implements EchoReplicator {
88
88
  documentId: params.documentId,
89
89
  peerId: connection.peerId,
90
90
  });
91
- log('document not found locally for share policy check, accepting the remote document', {
91
+ log('document not found locally for share policy check', {
92
92
  peerId: connection.peerId,
93
93
  documentId: params.documentId,
94
- remoteDocumentExists,
94
+ acceptDocument: remoteDocumentExists,
95
95
  });
96
96
  // If a document is not present locally return true if another peer claims to have it.
97
97
  // Simply returning true will add the peer to "generous peers list" for this document which will
@@ -33,6 +33,7 @@ import {
33
33
  type EchoReplicator,
34
34
  type EchoDataStats,
35
35
  type PeerIdProvider,
36
+ type RootDocumentSpaceKeyProvider,
36
37
  } from '../automerge';
37
38
 
38
39
  const INDEXER_CONFIG: IndexConfig = {
@@ -43,6 +44,7 @@ const INDEXER_CONFIG: IndexConfig = {
43
44
  export type EchoHostParams = {
44
45
  kv: LevelDB;
45
46
  peerIdProvider?: PeerIdProvider;
47
+ getSpaceKeyByRootDocumentId?: RootDocumentSpaceKeyProvider;
46
48
  };
47
49
 
48
50
  /**
@@ -60,7 +62,7 @@ export class EchoHost extends Resource {
60
62
  private readonly _spaceStateManager = new SpaceStateManager();
61
63
  private readonly _echoDataMonitor: EchoDataMonitor;
62
64
 
63
- constructor({ kv, peerIdProvider }: EchoHostParams) {
65
+ constructor({ kv, peerIdProvider, getSpaceKeyByRootDocumentId }: EchoHostParams) {
64
66
  super();
65
67
 
66
68
  this._indexMetadataStore = new IndexMetadataStore({ db: kv.sublevel('index-metadata') });
@@ -72,6 +74,7 @@ export class EchoHost extends Resource {
72
74
  dataMonitor: this._echoDataMonitor,
73
75
  indexMetadataStore: this._indexMetadataStore,
74
76
  peerIdProvider,
77
+ getSpaceKeyByRootDocumentId,
75
78
  });
76
79
 
77
80
  this._indexer = new Indexer({
@@ -2,8 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { Mutex, scheduleTask, scheduleMicroTask } from '@dxos/async';
6
- import * as A from '@dxos/automerge/automerge';
5
+ import { Mutex, scheduleTask, scheduleMicroTask, Trigger } from '@dxos/async';
7
6
  import { cbor } from '@dxos/automerge/automerge-repo';
8
7
  import { Context, Resource } from '@dxos/context';
9
8
  import { randomUUID } from '@dxos/crypto';
@@ -184,9 +183,11 @@ type EdgeReplicatorConnectionsParams = {
184
183
  onRestartRequested: () => Promise<void>;
185
184
  };
186
185
 
186
+ const MAX_INFLIGHT_REQUESTS = 5;
187
+
187
188
  class EdgeReplicatorConnection extends Resource implements ReplicatorConnection {
188
189
  private readonly _edgeConnection: EdgeConnection;
189
- private _remotePeerId: string | null = null;
190
+ private readonly _remotePeerId: string | null = null;
190
191
  private readonly _targetServiceId: string;
191
192
  private readonly _spaceId: SpaceId;
192
193
  private readonly _context: EchoReplicatorContext;
@@ -195,6 +196,16 @@ class EdgeReplicatorConnection extends Resource implements ReplicatorConnection
195
196
  private readonly _onRemoteDisconnected: () => Promise<void>;
196
197
  private readonly _onRestartRequested: () => void;
197
198
 
199
+ /**
200
+ * Prevents sending too many messages to edge over this connection so that we don't overwhelm
201
+ * a replicator durable object.
202
+ * inflightRequests counter is incremented on outgoing sync messages and decremented on incoming messages.
203
+ * The trigger is waiting while the counter is above MAX_INFLIGHT_REQUESTS.
204
+ * The counter can go negative because we receive edge-initiated sync messages on doc change broadcasts.
205
+ */
206
+ private _outgoingRequestsBarrier = new Trigger();
207
+ private _inflightRequests = 0;
208
+
198
209
  private _readableStreamController!: ReadableStreamDefaultController<AutomergeProtocolMessage>;
199
210
 
200
211
  public readable: ReadableStream<AutomergeProtocolMessage>;
@@ -213,7 +224,6 @@ class EdgeReplicatorConnection extends Resource implements ReplicatorConnection
213
224
  this._edgeConnection = edgeConnection;
214
225
  this._spaceId = spaceId;
215
226
  this._context = context;
216
-
217
227
  // Generate a unique peer id for every connection.
218
228
  // This way automerge-repo will have separate sync states for every connection.
219
229
  // This is important because the previous connection might have had some messages that failed to deliver
@@ -225,6 +235,8 @@ class EdgeReplicatorConnection extends Resource implements ReplicatorConnection
225
235
  this._onRemoteDisconnected = onRemoteDisconnected;
226
236
  this._onRestartRequested = onRestartRequested;
227
237
 
238
+ this._outgoingRequestsBarrier.wake();
239
+
228
240
  this.readable = new ReadableStream<AutomergeProtocolMessage>({
229
241
  start: (controller) => {
230
242
  this._readableStreamController = controller;
@@ -233,6 +245,13 @@ class EdgeReplicatorConnection extends Resource implements ReplicatorConnection
233
245
 
234
246
  this.writable = new WritableStream<AutomergeProtocolMessage>({
235
247
  write: async (message: AutomergeProtocolMessage, controller) => {
248
+ await this._outgoingRequestsBarrier.wait();
249
+
250
+ this._inflightRequests++;
251
+ if (this._inflightRequests === MAX_INFLIGHT_REQUESTS) {
252
+ this._outgoingRequestsBarrier.reset();
253
+ }
254
+
236
255
  await this._sendMessage(message);
237
256
  },
238
257
  });
@@ -253,6 +272,9 @@ class EdgeReplicatorConnection extends Resource implements ReplicatorConnection
253
272
  protected override async _close(): Promise<void> {
254
273
  log('close');
255
274
  this._readableStreamController.close();
275
+
276
+ this._outgoingRequestsBarrier.throw(new Error('Connection closed.'));
277
+
256
278
  await this._onRemoteDisconnected();
257
279
  }
258
280
 
@@ -272,9 +294,10 @@ class EdgeReplicatorConnection extends Resource implements ReplicatorConnection
272
294
  peerId: this._remotePeerId as PeerId,
273
295
  });
274
296
 
275
- log.info('document not found locally for share policy check, accepting the remote document', {
297
+ log.verbose('edge-replicator document not found locally for share policy check', {
276
298
  documentId: params.documentId,
277
- remoteDocumentExists,
299
+ acceptDocument: remoteDocumentExists,
300
+ remoteId: this._remotePeerId,
278
301
  });
279
302
 
280
303
  // If a document is not present locally return true only if it already exists on edge.
@@ -290,7 +313,8 @@ class EdgeReplicatorConnection extends Resource implements ReplicatorConnection
290
313
  return true;
291
314
  }
292
315
  const spaceId = getSpaceIdFromCollectionId(params.collectionId as CollectionId);
293
- return spaceId === this._spaceId;
316
+ // Only sync collections of form space:id:rootDoc, edge ignores legacy space:id collections
317
+ return spaceId === this._spaceId && params.collectionId.split(':').length === 3;
294
318
  }
295
319
 
296
320
  private _onMessage(message: RouterMessage) {
@@ -299,15 +323,12 @@ class EdgeReplicatorConnection extends Resource implements ReplicatorConnection
299
323
  }
300
324
 
301
325
  const payload = cbor.decode(message.payload!.value) as AutomergeProtocolMessage;
302
- log('recv', () => {
303
- const decodedData =
304
- payload.type === 'sync' && payload.data
305
- ? A.decodeSyncMessage(payload.data)
306
- : payload.type === 'collection-state'
307
- ? (payload as any).state
308
- : payload;
309
- return { from: message.serviceId, type: payload.type, decodedData };
326
+ log.verbose('edge replicator receive', {
327
+ type: payload.type,
328
+ documentId: payload.type === 'sync' && payload.documentId,
329
+ remoteId: this._remotePeerId,
310
330
  });
331
+
311
332
  // Fix the peer id.
312
333
  payload.senderId = this._remotePeerId! as PeerId;
313
334
  this._processMessage(payload);
@@ -322,6 +343,13 @@ class EdgeReplicatorConnection extends Resource implements ReplicatorConnection
322
343
  return;
323
344
  }
324
345
 
346
+ if (message.type === 'sync') {
347
+ this._inflightRequests--;
348
+ if (this._inflightRequests === MAX_INFLIGHT_REQUESTS - 1) {
349
+ this._outgoingRequestsBarrier.wake();
350
+ }
351
+ }
352
+
325
353
  this._readableStreamController.enqueue(message);
326
354
  }
327
355
 
@@ -329,12 +357,12 @@ class EdgeReplicatorConnection extends Resource implements ReplicatorConnection
329
357
  // Fix the peer id.
330
358
  (message as any).targetId = this._targetServiceId as PeerId;
331
359
 
332
- log('send', {
360
+ log.verbose('edge replicator send', {
333
361
  type: message.type,
334
- senderId: message.senderId,
335
- targetId: (message as any).targetId,
336
- documentId: (message as any).documentId,
362
+ documentId: message.type === 'sync' && message.documentId,
363
+ remoteId: this._remotePeerId,
337
364
  });
365
+
338
366
  const encoded = cbor.encode(message);
339
367
 
340
368
  await this._edgeConnection.send(
@@ -3,7 +3,8 @@
3
3
  //
4
4
 
5
5
  import { synchronized, trackLeaks, Trigger } from '@dxos/async';
6
- import { type DelegateInvitationCredential, type MemberInfo } from '@dxos/credentials';
6
+ import { type AutomergeUrl, parseAutomergeUrl } from '@dxos/automerge/automerge-repo';
7
+ import { getCredentialAssertion, type DelegateInvitationCredential, type MemberInfo } from '@dxos/credentials';
7
8
  import { failUndefined } from '@dxos/debug';
8
9
  import { type FeedStore } from '@dxos/feed-store';
9
10
  import { PublicKey } from '@dxos/keys';
@@ -168,4 +169,19 @@ export class SpaceManager {
168
169
  await protocol.stop();
169
170
  }
170
171
  }
172
+
173
+ public findSpaceByRootDocumentId(documentId: string): Space | undefined {
174
+ return [...this._spaces.values()].find((space) => {
175
+ return space.spaceState.credentials.some((credential) => {
176
+ const assertion = getCredentialAssertion(credential);
177
+ if (assertion['@type'] !== 'dxos.halo.credentials.Epoch') {
178
+ return false;
179
+ }
180
+ if (!assertion?.automergeRoot) {
181
+ return false;
182
+ }
183
+ return parseAutomergeUrl(assertion.automergeRoot as AutomergeUrl).documentId === documentId;
184
+ });
185
+ });
186
+ }
171
187
  }