@dxos/echo-pipeline 0.7.3 → 0.7.4-staging.99db212

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 (31) hide show
  1. package/dist/lib/browser/index.mjs +76 -37
  2. package/dist/lib/browser/index.mjs.map +3 -3
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node/index.cjs +78 -37
  5. package/dist/lib/node/index.cjs.map +3 -3
  6. package/dist/lib/node/meta.json +1 -1
  7. package/dist/lib/node-esm/index.mjs +76 -37
  8. package/dist/lib/node-esm/index.mjs.map +3 -3
  9. package/dist/lib/node-esm/meta.json +1 -1
  10. package/dist/types/src/automerge/automerge-host.d.ts +1 -0
  11. package/dist/types/src/automerge/automerge-host.d.ts.map +1 -1
  12. package/dist/types/src/automerge/collection-synchronizer.d.ts +3 -1
  13. package/dist/types/src/automerge/collection-synchronizer.d.ts.map +1 -1
  14. package/dist/types/src/automerge/space-collection.d.ts +2 -1
  15. package/dist/types/src/automerge/space-collection.d.ts.map +1 -1
  16. package/dist/types/src/db-host/data-service.d.ts +3 -0
  17. package/dist/types/src/db-host/data-service.d.ts.map +1 -1
  18. package/dist/types/src/db-host/echo-host.d.ts +1 -2
  19. package/dist/types/src/db-host/echo-host.d.ts.map +1 -1
  20. package/dist/types/src/db-host/index.d.ts +1 -0
  21. package/dist/types/src/db-host/index.d.ts.map +1 -1
  22. package/dist/types/src/db-host/space-state-manager.d.ts +4 -1
  23. package/dist/types/src/db-host/space-state-manager.d.ts.map +1 -1
  24. package/package.json +34 -34
  25. package/src/automerge/automerge-host.ts +4 -0
  26. package/src/automerge/collection-synchronizer.ts +24 -13
  27. package/src/automerge/space-collection.ts +4 -2
  28. package/src/db-host/data-service.ts +17 -3
  29. package/src/db-host/echo-host.ts +9 -6
  30. package/src/db-host/index.ts +1 -0
  31. package/src/db-host/space-state-manager.ts +9 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/echo-pipeline",
3
- "version": "0.7.3",
3
+ "version": "0.7.4-staging.99db212",
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.3",
41
- "@dxos/automerge": "0.7.3",
42
- "@dxos/context": "0.7.3",
43
- "@dxos/credentials": "0.7.3",
44
- "@dxos/codec-protobuf": "0.7.3",
45
- "@dxos/echo-protocol": "0.7.3",
46
- "@dxos/crypto": "0.7.3",
47
- "@dxos/debug": "0.7.3",
48
- "@dxos/echo-schema": "0.7.3",
49
- "@dxos/edge-client": "0.7.3",
50
- "@dxos/feed-store": "0.7.3",
51
- "@dxos/indexing": "0.7.3",
52
- "@dxos/hypercore": "0.7.3",
53
- "@dxos/invariant": "0.7.3",
54
- "@dxos/keys": "0.7.3",
55
- "@dxos/kv-store": "0.7.3",
56
- "@dxos/log": "0.7.3",
57
- "@dxos/network-manager": "0.7.3",
58
- "@dxos/messaging": "0.7.3",
59
- "@dxos/node-std": "0.7.3",
60
- "@dxos/protocols": "0.7.3",
61
- "@dxos/random-access-storage": "0.7.3",
62
- "@dxos/teleport": "0.7.3",
63
- "@dxos/teleport-extension-gossip": "0.7.3",
64
- "@dxos/teleport-extension-replicator": "0.7.3",
65
- "@dxos/teleport-extension-object-sync": "0.7.3",
66
- "@dxos/timeframe": "0.7.3",
67
- "@dxos/typings": "0.7.3",
68
- "@dxos/teleport-extension-automerge-replicator": "0.7.3",
69
- "@dxos/util": "0.7.3",
70
- "@dxos/tracing": "0.7.3",
71
- "@dxos/keyring": "0.7.3"
40
+ "@dxos/automerge": "0.7.4-staging.99db212",
41
+ "@dxos/async": "0.7.4-staging.99db212",
42
+ "@dxos/codec-protobuf": "0.7.4-staging.99db212",
43
+ "@dxos/context": "0.7.4-staging.99db212",
44
+ "@dxos/credentials": "0.7.4-staging.99db212",
45
+ "@dxos/debug": "0.7.4-staging.99db212",
46
+ "@dxos/echo-protocol": "0.7.4-staging.99db212",
47
+ "@dxos/echo-schema": "0.7.4-staging.99db212",
48
+ "@dxos/crypto": "0.7.4-staging.99db212",
49
+ "@dxos/edge-client": "0.7.4-staging.99db212",
50
+ "@dxos/feed-store": "0.7.4-staging.99db212",
51
+ "@dxos/hypercore": "0.7.4-staging.99db212",
52
+ "@dxos/indexing": "0.7.4-staging.99db212",
53
+ "@dxos/invariant": "0.7.4-staging.99db212",
54
+ "@dxos/kv-store": "0.7.4-staging.99db212",
55
+ "@dxos/log": "0.7.4-staging.99db212",
56
+ "@dxos/keys": "0.7.4-staging.99db212",
57
+ "@dxos/keyring": "0.7.4-staging.99db212",
58
+ "@dxos/messaging": "0.7.4-staging.99db212",
59
+ "@dxos/network-manager": "0.7.4-staging.99db212",
60
+ "@dxos/protocols": "0.7.4-staging.99db212",
61
+ "@dxos/node-std": "0.7.4-staging.99db212",
62
+ "@dxos/teleport": "0.7.4-staging.99db212",
63
+ "@dxos/teleport-extension-gossip": "0.7.4-staging.99db212",
64
+ "@dxos/random-access-storage": "0.7.4-staging.99db212",
65
+ "@dxos/teleport-extension-automerge-replicator": "0.7.4-staging.99db212",
66
+ "@dxos/teleport-extension-object-sync": "0.7.4-staging.99db212",
67
+ "@dxos/teleport-extension-replicator": "0.7.4-staging.99db212",
68
+ "@dxos/timeframe": "0.7.4-staging.99db212",
69
+ "@dxos/tracing": "0.7.4-staging.99db212",
70
+ "@dxos/typings": "0.7.4-staging.99db212",
71
+ "@dxos/util": "0.7.4-staging.99db212"
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.3"
77
+ "@dxos/test-utils": "0.7.4-staging.99db212"
78
78
  },
79
79
  "publishConfig": {
80
80
  "access": "public"
@@ -453,6 +453,10 @@ export class AutomergeHost extends Resource {
453
453
  this._collectionSynchronizer.setLocalCollectionState(collectionId, { documents });
454
454
  }
455
455
 
456
+ async clearLocalCollectionState(collectionId: string) {
457
+ this._collectionSynchronizer.clearLocalCollectionState(collectionId);
458
+ }
459
+
456
460
  private _onCollectionStateQueried(collectionId: string, peerId: PeerId) {
457
461
  this._collectionSynchronizer.onCollectionStateQueried(collectionId, peerId);
458
462
  }
@@ -31,6 +31,7 @@ export class CollectionSynchronizer extends Resource {
31
31
  * CollectionId -> State.
32
32
  */
33
33
  private readonly _perCollectionStates = new Map<string, PerCollectionState>();
34
+ private readonly _activeCollections = new Set<string>();
34
35
 
35
36
  private readonly _connectedPeers = new Set<PeerId>();
36
37
 
@@ -48,8 +49,10 @@ export class CollectionSynchronizer extends Resource {
48
49
  this._ctx,
49
50
  async () => {
50
51
  for (const collectionId of this._perCollectionStates.keys()) {
51
- this.refreshCollection(collectionId);
52
- await asyncReturn();
52
+ if (this._activeCollections.has(collectionId)) {
53
+ this.refreshCollection(collectionId);
54
+ await asyncReturn();
55
+ }
53
56
  }
54
57
  },
55
58
  POLL_INTERVAL,
@@ -57,32 +60,40 @@ export class CollectionSynchronizer extends Resource {
57
60
  }
58
61
 
59
62
  getRegisteredCollectionIds(): string[] {
60
- return [...this._perCollectionStates.keys()];
63
+ return [...this._activeCollections];
61
64
  }
62
65
 
63
66
  getLocalCollectionState(collectionId: string): CollectionState | undefined {
64
- return this._getPerCollectionState(collectionId).localState;
67
+ return this._perCollectionStates.get(collectionId)?.localState;
65
68
  }
66
69
 
67
70
  setLocalCollectionState(collectionId: string, state: CollectionState) {
71
+ this._activeCollections.add(collectionId);
72
+
68
73
  log('setLocalCollectionState', { collectionId, state });
69
- this._getPerCollectionState(collectionId).localState = state;
74
+ this._getOrCreatePerCollectionState(collectionId).localState = state;
70
75
 
71
76
  queueMicrotask(async () => {
72
- if (!this._ctx.disposed) {
77
+ if (!this._ctx.disposed && this._activeCollections.has(collectionId)) {
73
78
  this._refreshInterestedPeers(collectionId);
74
79
  this.refreshCollection(collectionId);
75
80
  }
76
81
  });
77
82
  }
78
83
 
84
+ clearLocalCollectionState(collectionId: string) {
85
+ this._activeCollections.delete(collectionId);
86
+ this._perCollectionStates.delete(collectionId);
87
+ log('clearLocalCollectionState', { collectionId });
88
+ }
89
+
79
90
  getRemoteCollectionStates(collectionId: string): ReadonlyMap<PeerId, CollectionState> {
80
- return this._getPerCollectionState(collectionId).remoteStates;
91
+ return this._getOrCreatePerCollectionState(collectionId).remoteStates;
81
92
  }
82
93
 
83
94
  refreshCollection(collectionId: string) {
84
95
  let scheduleAnotherRefresh = false;
85
- const state = this._getPerCollectionState(collectionId);
96
+ const state = this._getOrCreatePerCollectionState(collectionId);
86
97
  for (const peerId of this._connectedPeers) {
87
98
  if (state.interestedPeers.has(peerId)) {
88
99
  const lastQueried = state.lastQueried.get(peerId) ?? 0;
@@ -134,7 +145,7 @@ export class CollectionSynchronizer extends Resource {
134
145
  * Callback when a peer queries the state of a collection.
135
146
  */
136
147
  onCollectionStateQueried(collectionId: string, peerId: PeerId) {
137
- const perCollectionState = this._getPerCollectionState(collectionId);
148
+ const perCollectionState = this._getOrCreatePerCollectionState(collectionId);
138
149
 
139
150
  if (perCollectionState.localState) {
140
151
  this._sendCollectionState(collectionId, peerId, perCollectionState.localState);
@@ -147,12 +158,12 @@ export class CollectionSynchronizer extends Resource {
147
158
  onRemoteStateReceived(collectionId: string, peerId: PeerId, state: CollectionState) {
148
159
  log('onRemoteStateReceived', { collectionId, peerId, state });
149
160
  validateCollectionState(state);
150
- const perCollectionState = this._getPerCollectionState(collectionId);
161
+ const perCollectionState = this._getOrCreatePerCollectionState(collectionId);
151
162
  perCollectionState.remoteStates.set(peerId, state);
152
163
  this.remoteStateUpdated.emit({ peerId, collectionId });
153
164
  }
154
165
 
155
- private _getPerCollectionState(collectionId: string): PerCollectionState {
166
+ private _getOrCreatePerCollectionState(collectionId: string): PerCollectionState {
156
167
  return defaultMap(this._perCollectionStates, collectionId, () => ({
157
168
  localState: undefined,
158
169
  remoteStates: new Map(),
@@ -164,9 +175,9 @@ export class CollectionSynchronizer extends Resource {
164
175
  private _refreshInterestedPeers(collectionId: string) {
165
176
  for (const peerId of this._connectedPeers) {
166
177
  if (this._shouldSyncCollection(collectionId, peerId)) {
167
- this._getPerCollectionState(collectionId).interestedPeers.add(peerId);
178
+ this._getOrCreatePerCollectionState(collectionId).interestedPeers.add(peerId);
168
179
  } else {
169
- this._getPerCollectionState(collectionId).interestedPeers.delete(peerId);
180
+ this._getOrCreatePerCollectionState(collectionId).interestedPeers.delete(peerId);
170
181
  }
171
182
  }
172
183
  }
@@ -2,14 +2,16 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
+ import { type DocumentId } from '@dxos/automerge/automerge-repo';
5
6
  import type { CollectionId } from '@dxos/echo-protocol';
6
7
  import { invariant } from '@dxos/invariant';
7
8
  import { SpaceId } from '@dxos/keys';
8
9
 
9
- export const deriveCollectionIdFromSpaceId = (spaceId: SpaceId): CollectionId => `space:${spaceId}` as CollectionId;
10
+ export const deriveCollectionIdFromSpaceId = (spaceId: SpaceId, rootDocumentId?: DocumentId): CollectionId =>
11
+ (rootDocumentId ? `space:${spaceId}:${rootDocumentId}` : `space:${spaceId}`) as CollectionId;
10
12
 
11
13
  export const getSpaceIdFromCollectionId = (collectionId: CollectionId): SpaceId => {
12
- const spaceId = collectionId.replace(/^space:/, '');
14
+ const spaceId = collectionId.split(':')[1];
13
15
  invariant(SpaceId.isValid(spaceId));
14
16
  return spaceId;
15
17
  };
@@ -25,10 +25,12 @@ import {
25
25
  } from '@dxos/protocols/proto/dxos/echo/service';
26
26
 
27
27
  import { DocumentsSynchronizer } from './documents-synchronizer';
28
+ import { type SpaceStateManager } from './space-state-manager';
28
29
  import { deriveCollectionIdFromSpaceId, type AutomergeHost } from '../automerge';
29
30
 
30
31
  export type DataServiceParams = {
31
32
  automergeHost: AutomergeHost;
33
+ spaceStateManager: SpaceStateManager;
32
34
  updateIndexes: () => Promise<void>;
33
35
  };
34
36
 
@@ -44,10 +46,12 @@ export class DataServiceImpl implements DataService {
44
46
  private readonly _subscriptions = new Map<string, DocumentsSynchronizer>();
45
47
 
46
48
  private readonly _automergeHost: AutomergeHost;
49
+ private readonly _spaceStateManager: SpaceStateManager;
47
50
  private readonly _updateIndexes: () => Promise<void>;
48
51
 
49
52
  constructor(params: DataServiceParams) {
50
53
  this._automergeHost = params.automergeHost;
54
+ this._spaceStateManager = params.spaceStateManager;
51
55
  this._updateIndexes = params.updateIndexes;
52
56
  }
53
57
 
@@ -124,11 +128,21 @@ export class DataServiceImpl implements DataService {
124
128
 
125
129
  subscribeSpaceSyncState(request: GetSpaceSyncStateRequest): Stream<SpaceSyncState> {
126
130
  return new Stream<SpaceSyncState>(({ ctx, next, ready }) => {
127
- invariant(SpaceId.isValid(request.spaceId));
128
- const collectionId = deriveCollectionIdFromSpaceId(request.spaceId);
131
+ const spaceId = request.spaceId;
132
+ invariant(SpaceId.isValid(spaceId));
133
+
134
+ const rootDocumentId = this._spaceStateManager.getSpaceRootDocumentId(spaceId);
135
+ let collectionId = rootDocumentId && deriveCollectionIdFromSpaceId(spaceId, rootDocumentId);
136
+ this._spaceStateManager.spaceDocumentListUpdated.on(ctx, (event) => {
137
+ const newId = deriveCollectionIdFromSpaceId(spaceId, event.spaceRootId);
138
+ if (newId !== collectionId) {
139
+ collectionId = newId;
140
+ scheduler.trigger();
141
+ }
142
+ });
129
143
 
130
144
  const scheduler = new UpdateScheduler(ctx, async () => {
131
- const state = await this._automergeHost.getCollectionSyncState(collectionId);
145
+ const state = collectionId ? await this._automergeHost.getCollectionSyncState(collectionId) : { peers: [] };
132
146
 
133
147
  next({
134
148
  peers: state.peers.map((peer) => ({
@@ -31,7 +31,6 @@ import {
31
31
  type LoadDocOptions,
32
32
  type CreateDocOptions,
33
33
  type EchoReplicator,
34
- type CollectionSyncState,
35
34
  type EchoDataStats,
36
35
  type PeerIdProvider,
37
36
  } from '../automerge';
@@ -91,6 +90,7 @@ export class EchoHost extends Resource {
91
90
 
92
91
  this._dataService = new DataServiceImpl({
93
92
  automergeHost: this._automergeHost,
93
+ spaceStateManager: this._spaceStateManager,
94
94
  updateIndexes: async () => {
95
95
  await this._indexer.updateIndexes();
96
96
  },
@@ -163,7 +163,15 @@ export class EchoHost extends Resource {
163
163
  await this._spaceStateManager.open(ctx);
164
164
 
165
165
  this._spaceStateManager.spaceDocumentListUpdated.on(this._ctx, (e) => {
166
+ if (e.previousRootId) {
167
+ void this._automergeHost.clearLocalCollectionState(deriveCollectionIdFromSpaceId(e.spaceId, e.previousRootId));
168
+ }
169
+ // TODO(yaroslav): remove collection without spaceRootId after release (production<->staging interop)
166
170
  void this._automergeHost.updateLocalCollectionState(deriveCollectionIdFromSpaceId(e.spaceId), e.documentIds);
171
+ void this._automergeHost.updateLocalCollectionState(
172
+ deriveCollectionIdFromSpaceId(e.spaceId, e.spaceRootId),
173
+ e.documentIds,
174
+ );
167
175
  });
168
176
  }
169
177
 
@@ -249,11 +257,6 @@ export class EchoHost extends Resource {
249
257
  async removeReplicator(replicator: EchoReplicator): Promise<void> {
250
258
  await this._automergeHost.removeReplicator(replicator);
251
259
  }
252
-
253
- async getSpaceSyncState(spaceId: SpaceId): Promise<CollectionSyncState> {
254
- const collectionId = deriveCollectionIdFromSpaceId(spaceId);
255
- return this._automergeHost.getCollectionSyncState(collectionId);
256
- }
257
260
  }
258
261
 
259
262
  export type { EchoDataStats };
@@ -8,3 +8,4 @@ export * from './echo-host';
8
8
  export * from './database-root';
9
9
  export * from './query-state';
10
10
  export * from './query-service';
11
+ export * from './space-state-manager';
@@ -35,6 +35,10 @@ export class SpaceStateManager extends Resource {
35
35
  return this._roots.get(documentId);
36
36
  }
37
37
 
38
+ getSpaceRootDocumentId(spaceId: SpaceId): DocumentId | undefined {
39
+ return this._rootBySpace.get(spaceId);
40
+ }
41
+
38
42
  async assignRootToSpace(spaceId: SpaceId, handle: DocHandle<SpaceDoc>): Promise<DatabaseRoot> {
39
43
  let root: DatabaseRoot;
40
44
  if (this._roots.has(handle.documentId)) {
@@ -67,7 +71,9 @@ export class SpaceStateManager extends Resource {
67
71
  const documentIds = [root.documentId, ...root.getAllLinkedDocuments().map((url) => interpretAsDocumentId(url))];
68
72
  if (!isEqual(documentIds, this._lastSpaceDocumentList.get(spaceId))) {
69
73
  this._lastSpaceDocumentList.set(spaceId, documentIds);
70
- this.spaceDocumentListUpdated.emit(new SpaceDocumentListUpdatedEvent(spaceId, documentIds));
74
+ this.spaceDocumentListUpdated.emit(
75
+ new SpaceDocumentListUpdatedEvent(spaceId, root.documentId, prevRootId, documentIds),
76
+ );
71
77
  }
72
78
  },
73
79
  { maxFrequency: 50 },
@@ -85,6 +91,8 @@ export class SpaceStateManager extends Resource {
85
91
  export class SpaceDocumentListUpdatedEvent {
86
92
  constructor(
87
93
  public readonly spaceId: SpaceId,
94
+ public readonly spaceRootId: DocumentId,
95
+ public readonly previousRootId: DocumentId | undefined,
88
96
  public readonly documentIds: DocumentId[],
89
97
  ) {}
90
98
  }