@dxos/echo-pipeline 0.8.3 → 0.8.4-main.03d5cd7b56
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.
- package/dist/lib/{browser/chunk-35I6ERLG.mjs → neutral/chunk-VIHYDCCC.mjs} +313 -709
- package/dist/lib/neutral/chunk-VIHYDCCC.mjs.map +7 -0
- package/dist/lib/neutral/chunk-YBC6ONBZ.mjs +247 -0
- package/dist/lib/neutral/chunk-YBC6ONBZ.mjs.map +7 -0
- package/dist/lib/{browser → neutral}/filter/index.mjs +3 -2
- package/dist/lib/neutral/index.mjs +7065 -0
- package/dist/lib/neutral/index.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/lib/{browser → neutral}/testing/index.mjs +90 -311
- package/dist/lib/neutral/testing/index.mjs.map +7 -0
- package/dist/types/src/automerge/automerge-host-subduction.test.d.ts +2 -0
- package/dist/types/src/automerge/automerge-host-subduction.test.d.ts.map +1 -0
- package/dist/types/src/automerge/automerge-host.d.ts +99 -54
- package/dist/types/src/automerge/automerge-host.d.ts.map +1 -1
- package/dist/types/src/automerge/automerge-repo-subduction.test.d.ts +2 -0
- package/dist/types/src/automerge/automerge-repo-subduction.test.d.ts.map +1 -0
- package/dist/types/src/automerge/automerge-repo.test.d.ts +1 -1
- package/dist/types/src/automerge/automerge-repo.test.d.ts.map +1 -1
- package/dist/types/src/automerge/automerge-subduction.test.d.ts +2 -0
- package/dist/types/src/automerge/automerge-subduction.test.d.ts.map +1 -0
- package/dist/types/src/automerge/collection-synchronizer.d.ts +25 -4
- package/dist/types/src/automerge/collection-synchronizer.d.ts.map +1 -1
- package/dist/types/src/automerge/echo-data-monitor.d.ts.map +1 -1
- package/dist/types/src/automerge/echo-network-adapter.d.ts +23 -10
- package/dist/types/src/automerge/echo-network-adapter.d.ts.map +1 -1
- package/dist/types/src/automerge/echo-replicator.d.ts +38 -15
- package/dist/types/src/automerge/echo-replicator.d.ts.map +1 -1
- package/dist/types/src/automerge/handle-state.d.ts +52 -0
- package/dist/types/src/automerge/handle-state.d.ts.map +1 -0
- package/dist/types/src/automerge/heads-store.d.ts +9 -2
- package/dist/types/src/automerge/heads-store.d.ts.map +1 -1
- package/dist/types/src/automerge/index.d.ts +2 -1
- package/dist/types/src/automerge/index.d.ts.map +1 -1
- package/dist/types/src/automerge/leveldb-storage-adapter.d.ts +9 -5
- package/dist/types/src/automerge/leveldb-storage-adapter.d.ts.map +1 -1
- package/dist/types/src/automerge/mesh-echo-replicator-connection.d.ts +9 -8
- package/dist/types/src/automerge/mesh-echo-replicator-connection.d.ts.map +1 -1
- package/dist/types/src/automerge/mesh-echo-replicator.d.ts +6 -5
- package/dist/types/src/automerge/mesh-echo-replicator.d.ts.map +1 -1
- package/dist/types/src/automerge/network-protocol.d.ts.map +1 -1
- package/dist/types/src/automerge/space-collection.d.ts.map +1 -1
- package/dist/types/src/common/codec.d.ts +1 -1
- package/dist/types/src/common/codec.d.ts.map +1 -1
- package/dist/types/src/common/feeds.d.ts.map +1 -1
- package/dist/types/src/common/space-id.d.ts.map +1 -1
- package/dist/types/src/db-host/automerge-data-source.d.ts +35 -0
- package/dist/types/src/db-host/automerge-data-source.d.ts.map +1 -0
- package/dist/types/src/db-host/automerge-data-source.test.d.ts +2 -0
- package/dist/types/src/db-host/automerge-data-source.test.d.ts.map +1 -0
- package/dist/types/src/db-host/automerge-metrics.d.ts.map +1 -1
- package/dist/types/src/db-host/data-service.d.ts +8 -7
- package/dist/types/src/db-host/data-service.d.ts.map +1 -1
- package/dist/types/src/db-host/database-root.d.ts +9 -3
- package/dist/types/src/db-host/database-root.d.ts.map +1 -1
- package/dist/types/src/db-host/documents-synchronizer.d.ts +9 -8
- package/dist/types/src/db-host/documents-synchronizer.d.ts.map +1 -1
- package/dist/types/src/db-host/echo-host.d.ts +62 -27
- package/dist/types/src/db-host/echo-host.d.ts.map +1 -1
- package/dist/types/src/db-host/index.d.ts +2 -0
- package/dist/types/src/db-host/index.d.ts.map +1 -1
- package/dist/types/src/db-host/invalidation-hint.d.ts +19 -0
- package/dist/types/src/db-host/invalidation-hint.d.ts.map +1 -0
- package/dist/types/src/db-host/local-queue-service.d.ts +19 -0
- package/dist/types/src/db-host/local-queue-service.d.ts.map +1 -0
- package/dist/types/src/db-host/query-invalidation.test.d.ts +2 -0
- package/dist/types/src/db-host/query-invalidation.test.d.ts.map +1 -0
- package/dist/types/src/db-host/query-service.d.ts +19 -11
- package/dist/types/src/db-host/query-service.d.ts.map +1 -1
- package/dist/types/src/db-host/queue-data-source.d.ts +38 -0
- package/dist/types/src/db-host/queue-data-source.d.ts.map +1 -0
- package/dist/types/src/db-host/queue-service.test.d.ts +2 -0
- package/dist/types/src/db-host/queue-service.test.d.ts.map +1 -0
- package/dist/types/src/db-host/space-state-manager.d.ts +4 -3
- package/dist/types/src/db-host/space-state-manager.d.ts.map +1 -1
- package/dist/types/src/db-host/stub.d.ts +11 -0
- package/dist/types/src/db-host/stub.d.ts.map +1 -0
- package/dist/types/src/edge/echo-edge-replicator.d.ts +10 -7
- package/dist/types/src/edge/echo-edge-replicator.d.ts.map +1 -1
- package/dist/types/src/edge/echo-edge-subduction-replicator.d.ts +39 -0
- package/dist/types/src/edge/echo-edge-subduction-replicator.d.ts.map +1 -0
- package/dist/types/src/edge/echo-edge-subduction-replicator.test.d.ts +2 -0
- package/dist/types/src/edge/echo-edge-subduction-replicator.test.d.ts.map +1 -0
- package/dist/types/src/edge/index.d.ts +1 -0
- package/dist/types/src/edge/index.d.ts.map +1 -1
- package/dist/types/src/edge/inflight-request-limiter.d.ts.map +1 -1
- package/dist/types/src/filter/filter-match.d.ts +4 -1
- package/dist/types/src/filter/filter-match.d.ts.map +1 -1
- package/dist/types/src/metadata/metadata-store.d.ts +1 -1
- package/dist/types/src/metadata/metadata-store.d.ts.map +1 -1
- package/dist/types/src/pipeline/message-selector.d.ts.map +1 -1
- package/dist/types/src/pipeline/pipeline.d.ts +3 -3
- package/dist/types/src/pipeline/pipeline.d.ts.map +1 -1
- package/dist/types/src/pipeline/timeframe-clock.d.ts.map +1 -1
- package/dist/types/src/query/errors.d.ts +22 -10
- package/dist/types/src/query/errors.d.ts.map +1 -1
- package/dist/types/src/query/plan.d.ts +66 -8
- package/dist/types/src/query/plan.d.ts.map +1 -1
- package/dist/types/src/query/query-executor.d.ts +66 -11
- package/dist/types/src/query/query-executor.d.ts.map +1 -1
- package/dist/types/src/query/query-planner.d.ts +12 -1
- package/dist/types/src/query/query-planner.d.ts.map +1 -1
- package/dist/types/src/space/admission-discovery-extension.d.ts.map +1 -1
- package/dist/types/src/space/auth.d.ts +3 -3
- package/dist/types/src/space/auth.d.ts.map +1 -1
- package/dist/types/src/space/control-pipeline.d.ts +5 -4
- package/dist/types/src/space/control-pipeline.d.ts.map +1 -1
- package/dist/types/src/space/space-manager.d.ts +8 -8
- package/dist/types/src/space/space-manager.d.ts.map +1 -1
- package/dist/types/src/space/space-protocol.d.ts +10 -9
- package/dist/types/src/space/space-protocol.d.ts.map +1 -1
- package/dist/types/src/space/space.d.ts +6 -6
- package/dist/types/src/space/space.d.ts.map +1 -1
- package/dist/types/src/testing/change-metadata.d.ts.map +1 -1
- package/dist/types/src/testing/index.d.ts +0 -2
- package/dist/types/src/testing/index.d.ts.map +1 -1
- package/dist/types/src/testing/test-agent-builder.d.ts +2 -2
- package/dist/types/src/testing/test-agent-builder.d.ts.map +1 -1
- package/dist/types/src/testing/test-feed-builder.d.ts.map +1 -1
- package/dist/types/src/testing/test-network-adapter.d.ts +1 -1
- package/dist/types/src/testing/test-network-adapter.d.ts.map +1 -1
- package/dist/types/src/testing/test-replicator.d.ts +14 -13
- package/dist/types/src/testing/test-replicator.d.ts.map +1 -1
- package/dist/types/src/util.d.ts +1 -1
- package/dist/types/src/util.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +56 -54
- package/src/automerge/automerge-host-subduction.test.ts +247 -0
- package/src/automerge/automerge-host.test.ts +65 -39
- package/src/automerge/automerge-host.ts +589 -206
- package/src/automerge/automerge-repo-subduction.test.ts +1049 -0
- package/src/automerge/automerge-repo.test.ts +349 -91
- package/src/automerge/automerge-subduction.test.ts +330 -0
- package/src/automerge/collection-synchronizer.test.ts +53 -17
- package/src/automerge/collection-synchronizer.ts +119 -36
- package/src/automerge/echo-data-monitor.ts +1 -1
- package/src/automerge/echo-network-adapter.test.ts +5 -4
- package/src/automerge/echo-network-adapter.ts +74 -30
- package/src/automerge/echo-replicator.ts +41 -15
- package/src/automerge/handle-state.ts +62 -0
- package/src/automerge/heads-store.ts +42 -3
- package/src/automerge/index.ts +2 -1
- package/src/automerge/leveldb-storage-adapter.ts +40 -11
- package/src/automerge/mesh-echo-replicator-connection.ts +12 -8
- package/src/automerge/mesh-echo-replicator.ts +15 -11
- package/src/automerge/storage-adapter.test.ts +1 -1
- package/src/common/space-id.ts +1 -1
- package/src/db-host/automerge-data-source.test.ts +299 -0
- package/src/db-host/automerge-data-source.ts +187 -0
- package/src/db-host/data-service.ts +25 -26
- package/src/db-host/database-root.ts +20 -8
- package/src/db-host/documents-synchronizer.test.ts +143 -14
- package/src/db-host/documents-synchronizer.ts +72 -55
- package/src/db-host/echo-host.ts +348 -96
- package/src/db-host/index.ts +2 -0
- package/src/db-host/invalidation-hint.ts +50 -0
- package/src/db-host/local-queue-service.ts +127 -0
- package/src/db-host/query-invalidation.test.ts +270 -0
- package/src/db-host/query-service.ts +111 -106
- package/src/db-host/queue-data-source.ts +160 -0
- package/src/db-host/queue-service.test.ts +145 -0
- package/src/db-host/space-state-manager.ts +12 -10
- package/src/db-host/stub.ts +33 -0
- package/src/edge/echo-edge-replicator.test.ts +12 -10
- package/src/edge/echo-edge-replicator.ts +144 -58
- package/src/edge/echo-edge-subduction-replicator.test.ts +158 -0
- package/src/edge/echo-edge-subduction-replicator.ts +377 -0
- package/src/edge/index.ts +1 -0
- package/src/filter/filter-match.test.ts +68 -32
- package/src/filter/filter-match.ts +184 -20
- package/src/metadata/metadata-store.ts +11 -8
- package/src/pipeline/pipeline-stress.test.ts +1 -1
- package/src/pipeline/pipeline.test.ts +2 -2
- package/src/pipeline/pipeline.ts +9 -7
- package/src/query/errors.ts +3 -1
- package/src/query/plan.ts +82 -8
- package/src/query/query-executor.ts +1199 -164
- package/src/query/query-planner.test.ts +993 -81
- package/src/query/query-planner.ts +467 -27
- package/src/space/admission-discovery-extension.ts +3 -3
- package/src/space/auth.ts +6 -6
- package/src/space/control-pipeline.test.ts +5 -4
- package/src/space/control-pipeline.ts +16 -13
- package/src/space/space-manager.browser.test.ts +1 -1
- package/src/space/space-manager.ts +16 -20
- package/src/space/space-protocol.browser.test.ts +15 -14
- package/src/space/space-protocol.test.ts +15 -14
- package/src/space/space-protocol.ts +22 -18
- package/src/space/space.test.ts +3 -3
- package/src/space/space.ts +11 -11
- package/src/testing/index.ts +0 -2
- package/src/testing/test-agent-builder.ts +3 -3
- package/src/testing/test-network-adapter.ts +24 -3
- package/src/testing/test-replicator.ts +19 -20
- package/src/util.ts +7 -3
- package/dist/lib/browser/chunk-35I6ERLG.mjs.map +0 -7
- package/dist/lib/browser/chunk-CGS2ULMK.mjs +0 -11
- package/dist/lib/browser/chunk-CGS2ULMK.mjs.map +0 -7
- package/dist/lib/browser/chunk-TQJTKNMS.mjs +0 -126
- package/dist/lib/browser/chunk-TQJTKNMS.mjs.map +0 -7
- package/dist/lib/browser/index.mjs +0 -4743
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/browser/testing/index.mjs.map +0 -7
- package/dist/lib/node/chunk-HOPOFWAL.cjs +0 -147
- package/dist/lib/node/chunk-HOPOFWAL.cjs.map +0 -7
- package/dist/lib/node/chunk-JXX6LF5U.cjs +0 -2084
- package/dist/lib/node/chunk-JXX6LF5U.cjs.map +0 -7
- package/dist/lib/node/chunk-Q7SFCCGT.cjs +0 -33
- package/dist/lib/node/chunk-Q7SFCCGT.cjs.map +0 -7
- package/dist/lib/node/filter/index.cjs +0 -32
- package/dist/lib/node/filter/index.cjs.map +0 -7
- package/dist/lib/node/index.cjs +0 -4699
- package/dist/lib/node/index.cjs.map +0 -7
- package/dist/lib/node/meta.json +0 -1
- package/dist/lib/node/testing/index.cjs +0 -753
- package/dist/lib/node/testing/index.cjs.map +0 -7
- package/dist/lib/node-esm/chunk-5BHLPT24.mjs +0 -2067
- package/dist/lib/node-esm/chunk-5BHLPT24.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +0 -11
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-RVK35BS7.mjs +0 -126
- package/dist/lib/node-esm/chunk-RVK35BS7.mjs.map +0 -7
- package/dist/lib/node-esm/filter/index.mjs +0 -11
- package/dist/lib/node-esm/filter/index.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -4743
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/lib/node-esm/testing/index.mjs +0 -742
- package/dist/lib/node-esm/testing/index.mjs.map +0 -7
- package/dist/types/src/db-host/documents-iterator.d.ts +0 -7
- package/dist/types/src/db-host/documents-iterator.d.ts.map +0 -1
- package/dist/types/src/testing/test-data.d.ts +0 -18
- package/dist/types/src/testing/test-data.d.ts.map +0 -1
- package/dist/types/src/testing/test-schema.d.ts +0 -37
- package/dist/types/src/testing/test-schema.d.ts.map +0 -1
- package/src/db-host/documents-iterator.ts +0 -74
- package/src/testing/test-data.ts +0 -127
- package/src/testing/test-schema.ts +0 -53
- /package/dist/lib/{browser → neutral}/filter/index.mjs.map +0 -0
|
@@ -2,20 +2,22 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { next as
|
|
5
|
+
import { next as A, type Heads } from '@automerge/automerge';
|
|
6
6
|
import type { DocumentId, PeerId } from '@automerge/automerge-repo';
|
|
7
|
+
import * as Array from 'effect/Array';
|
|
8
|
+
import * as Record from 'effect/Record';
|
|
7
9
|
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
+
import { Event, asyncReturn, scheduleTask, scheduleTaskInterval } from '@dxos/async';
|
|
11
|
+
import { type Context, Resource } from '@dxos/context';
|
|
10
12
|
import { log } from '@dxos/log';
|
|
11
13
|
import { trace } from '@dxos/tracing';
|
|
12
14
|
import { defaultMap } from '@dxos/util';
|
|
13
15
|
|
|
14
16
|
const MIN_QUERY_INTERVAL = 5_000;
|
|
15
17
|
|
|
16
|
-
const POLL_INTERVAL =
|
|
18
|
+
const POLL_INTERVAL = 10_000;
|
|
17
19
|
|
|
18
|
-
export type
|
|
20
|
+
export type CollectionSynchronizerProps = {
|
|
19
21
|
sendCollectionState: (collectionId: string, peerId: PeerId, state: CollectionState) => void;
|
|
20
22
|
queryCollectionState: (collectionId: string, peerId: PeerId) => void;
|
|
21
23
|
shouldSyncCollection: (collectionId: string, peerId: PeerId) => boolean;
|
|
@@ -24,11 +26,11 @@ export type CollectionSynchronizerParams = {
|
|
|
24
26
|
/**
|
|
25
27
|
* Implements collection sync protocol.
|
|
26
28
|
*/
|
|
27
|
-
@trace.resource()
|
|
29
|
+
@trace.resource({ lifecycle: true })
|
|
28
30
|
export class CollectionSynchronizer extends Resource {
|
|
29
|
-
private readonly _sendCollectionState:
|
|
30
|
-
private readonly _queryCollectionState:
|
|
31
|
-
private readonly _shouldSyncCollection:
|
|
31
|
+
private readonly _sendCollectionState: CollectionSynchronizerProps['sendCollectionState'];
|
|
32
|
+
private readonly _queryCollectionState: CollectionSynchronizerProps['queryCollectionState'];
|
|
33
|
+
private readonly _shouldSyncCollection: CollectionSynchronizerProps['shouldSyncCollection'];
|
|
32
34
|
|
|
33
35
|
/**
|
|
34
36
|
* CollectionId -> State.
|
|
@@ -40,7 +42,7 @@ export class CollectionSynchronizer extends Resource {
|
|
|
40
42
|
|
|
41
43
|
public readonly remoteStateUpdated = new Event<{ collectionId: string; peerId: PeerId; newDocsAppeared: boolean }>();
|
|
42
44
|
|
|
43
|
-
constructor(params:
|
|
45
|
+
constructor(params: CollectionSynchronizerProps) {
|
|
44
46
|
super();
|
|
45
47
|
this._sendCollectionState = params.sendCollectionState;
|
|
46
48
|
this._queryCollectionState = params.queryCollectionState;
|
|
@@ -74,13 +76,29 @@ export class CollectionSynchronizer extends Resource {
|
|
|
74
76
|
this._activeCollections.add(collectionId);
|
|
75
77
|
|
|
76
78
|
log('setLocalCollectionState', { collectionId, state });
|
|
77
|
-
this._getOrCreatePerCollectionState(collectionId)
|
|
79
|
+
const perCollectionState = this._getOrCreatePerCollectionState(collectionId);
|
|
80
|
+
perCollectionState.localState = state;
|
|
78
81
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
for (const peerId of this._connectedPeers) {
|
|
83
|
+
this._diffCollectionState(collectionId, peerId);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
this._scheduleBroadcast(collectionId);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Coalesce bursts of `setLocalCollectionState` (e.g. from `_onHeadsChanged` during an
|
|
91
|
+
* import) into one broadcast microtask per collection. If another call arrives while
|
|
92
|
+
* the microtask is pending, we just flag the collection dirty — the in-flight run
|
|
93
|
+
* already reads the latest `localState`. If it arrives during the run itself, we
|
|
94
|
+
* schedule exactly one follow-up.
|
|
95
|
+
*/
|
|
96
|
+
private _scheduleBroadcast(collectionId: string): void {
|
|
97
|
+
const perCollectionState = this._getOrCreatePerCollectionState(collectionId);
|
|
98
|
+
queueMicrotask(() => {
|
|
99
|
+
this._refreshInterestedPeers(collectionId);
|
|
100
|
+
this._broadcastLocalState(collectionId);
|
|
101
|
+
this.refreshCollection(collectionId);
|
|
84
102
|
});
|
|
85
103
|
}
|
|
86
104
|
|
|
@@ -117,10 +135,14 @@ export class CollectionSynchronizer extends Resource {
|
|
|
117
135
|
* Callback when a connection to a peer is established.
|
|
118
136
|
*/
|
|
119
137
|
onConnectionOpen(peerId: PeerId): void {
|
|
120
|
-
|
|
121
|
-
|
|
138
|
+
log('onConnectionOpen', { peerId });
|
|
139
|
+
const spanId = getSpanId(peerId);
|
|
140
|
+
// Browser-timeline-only span; the derived ctx is intentionally discarded because
|
|
141
|
+
// the downstream `_queryCollectionState` hop is user-supplied callback land with
|
|
142
|
+
// no ctx plumbing.
|
|
143
|
+
void trace.spanStart({
|
|
122
144
|
id: spanId,
|
|
123
|
-
methodName:
|
|
145
|
+
methodName: SYNC_SPAN_METHOD,
|
|
124
146
|
instance: this,
|
|
125
147
|
parentCtx: this._ctx,
|
|
126
148
|
showInBrowserTimeline: true,
|
|
@@ -146,6 +168,8 @@ export class CollectionSynchronizer extends Resource {
|
|
|
146
168
|
* Callback when a connection to a peer is closed.
|
|
147
169
|
*/
|
|
148
170
|
onConnectionClosed(peerId: PeerId): void {
|
|
171
|
+
log('onConnectionClosed', { peerId });
|
|
172
|
+
|
|
149
173
|
this._connectedPeers.delete(peerId);
|
|
150
174
|
|
|
151
175
|
for (const perCollectionState of this._perCollectionStates.values()) {
|
|
@@ -155,6 +179,9 @@ export class CollectionSynchronizer extends Resource {
|
|
|
155
179
|
|
|
156
180
|
/**
|
|
157
181
|
* Callback when a peer queries the state of a collection.
|
|
182
|
+
*
|
|
183
|
+
* If we have no local state yet we silently drop the query; the peer will receive our
|
|
184
|
+
* state via {@link _broadcastLocalState} once `setLocalCollectionState` is called.
|
|
158
185
|
*/
|
|
159
186
|
onCollectionStateQueried(collectionId: string, peerId: PeerId): void {
|
|
160
187
|
const perCollectionState = this._getOrCreatePerCollectionState(collectionId);
|
|
@@ -171,24 +198,48 @@ export class CollectionSynchronizer extends Resource {
|
|
|
171
198
|
log('onRemoteStateReceived', { collectionId, peerId, state });
|
|
172
199
|
validateCollectionState(state);
|
|
173
200
|
const perCollectionState = this._getOrCreatePerCollectionState(collectionId);
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
201
|
+
perCollectionState.remoteStates.set(peerId, state);
|
|
202
|
+
this._diffCollectionState(collectionId, peerId);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private _diffCollectionState(collectionId: string, peerId: PeerId) {
|
|
206
|
+
const perCollectionState = this._getOrCreatePerCollectionState(collectionId);
|
|
207
|
+
const remoteState = perCollectionState.remoteStates.get(peerId);
|
|
208
|
+
if (!remoteState) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
log('diffCollectionState', { collectionId, peerId });
|
|
213
|
+
const localState = perCollectionState.localState ?? { documents: {} };
|
|
214
|
+
const diff = diffCollectionState(localState, remoteState);
|
|
215
|
+
const spanId = getSpanId(peerId);
|
|
177
216
|
if (diff.different.length === 0) {
|
|
178
217
|
trace.spanEnd(spanId);
|
|
179
218
|
} else {
|
|
180
|
-
|
|
219
|
+
// Browser-timeline-only span; see note in onConnectionOpen.
|
|
220
|
+
void trace.spanStart({
|
|
181
221
|
id: spanId,
|
|
182
|
-
methodName:
|
|
222
|
+
methodName: SYNC_SPAN_METHOD,
|
|
183
223
|
instance: this,
|
|
184
224
|
parentCtx: this._ctx,
|
|
185
225
|
showInBrowserTimeline: true,
|
|
186
226
|
attributes: { peerId },
|
|
187
227
|
});
|
|
188
228
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
229
|
+
log('diff', {
|
|
230
|
+
localState: localState.documents,
|
|
231
|
+
remoteState: remoteState.documents,
|
|
232
|
+
missingOnLocal: diff.missingOnLocal,
|
|
233
|
+
missingOnRemote: diff.missingOnRemote,
|
|
234
|
+
different: diff.different,
|
|
235
|
+
});
|
|
236
|
+
if (diff.missingOnLocal.length > 0 || diff.different.length > 0 || diff.missingOnRemote.length > 0) {
|
|
237
|
+
log('emit remote state update');
|
|
238
|
+
this.remoteStateUpdated.emit({
|
|
239
|
+
peerId,
|
|
240
|
+
collectionId,
|
|
241
|
+
newDocsAppeared: diff.missingOnLocal.length > 0,
|
|
242
|
+
});
|
|
192
243
|
}
|
|
193
244
|
}
|
|
194
245
|
|
|
@@ -198,9 +249,35 @@ export class CollectionSynchronizer extends Resource {
|
|
|
198
249
|
remoteStates: new Map(),
|
|
199
250
|
interestedPeers: new Set(),
|
|
200
251
|
lastQueried: new Map(),
|
|
252
|
+
lastBroadcast: new Map(),
|
|
201
253
|
}));
|
|
202
254
|
}
|
|
203
255
|
|
|
256
|
+
/**
|
|
257
|
+
* Push local state to interested peers whose last-known remote state differs from local
|
|
258
|
+
* (or is unknown), then pull from peers via {@link refreshCollection}.
|
|
259
|
+
*
|
|
260
|
+
* Diff-gating avoids spamming peers that are already in sync; this matters because
|
|
261
|
+
* {@link setLocalCollectionState} is called on every local heads change.
|
|
262
|
+
*/
|
|
263
|
+
private _broadcastLocalState(collectionId: string): void {
|
|
264
|
+
if (this._ctx.disposed || !this._activeCollections.has(collectionId)) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const perCollectionState = this._getOrCreatePerCollectionState(collectionId);
|
|
268
|
+
const localState = perCollectionState.localState;
|
|
269
|
+
if (!localState) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
for (const peerId of perCollectionState.interestedPeers) {
|
|
273
|
+
const lastBroadcast = perCollectionState.lastBroadcast.get(peerId) ?? 0;
|
|
274
|
+
if (Date.now() - lastBroadcast > MIN_QUERY_INTERVAL) {
|
|
275
|
+
perCollectionState.lastBroadcast.set(peerId, Date.now());
|
|
276
|
+
this._sendCollectionState(collectionId, peerId, localState);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
204
281
|
private _refreshInterestedPeers(collectionId: string): void {
|
|
205
282
|
for (const peerId of this._connectedPeers) {
|
|
206
283
|
if (this._shouldSyncCollection(collectionId, peerId)) {
|
|
@@ -217,13 +294,14 @@ type PerCollectionState = {
|
|
|
217
294
|
remoteStates: Map<PeerId, CollectionState>;
|
|
218
295
|
interestedPeers: Set<PeerId>;
|
|
219
296
|
lastQueried: Map<PeerId, number>;
|
|
297
|
+
lastBroadcast: Map<PeerId, number>;
|
|
220
298
|
};
|
|
221
299
|
|
|
222
300
|
export type CollectionState = {
|
|
223
301
|
/**
|
|
224
302
|
* DocumentId -> Heads.
|
|
225
303
|
*/
|
|
226
|
-
documents: Record<
|
|
304
|
+
documents: Record<DocumentId, Heads>;
|
|
227
305
|
};
|
|
228
306
|
|
|
229
307
|
export type CollectionStateDiff = {
|
|
@@ -233,18 +311,21 @@ export type CollectionStateDiff = {
|
|
|
233
311
|
};
|
|
234
312
|
|
|
235
313
|
export const diffCollectionState = (local: CollectionState, remote: CollectionState): CollectionStateDiff => {
|
|
236
|
-
const
|
|
314
|
+
const localDocuments = Record.filter(local.documents, (heads) => heads.length > 0);
|
|
315
|
+
const remoteDocuments = Record.filter(remote.documents, (heads) => heads.length > 0);
|
|
316
|
+
// NOTE: Using `Array.union` is slow.
|
|
317
|
+
const allDocuments = [...new Set([...Record.keys(localDocuments), ...Record.keys(remoteDocuments)])] as DocumentId[];
|
|
237
318
|
|
|
238
319
|
const missingOnRemote: DocumentId[] = [];
|
|
239
320
|
const missingOnLocal: DocumentId[] = [];
|
|
240
321
|
const different: DocumentId[] = [];
|
|
241
322
|
for (const documentId of allDocuments) {
|
|
242
|
-
if (!
|
|
243
|
-
missingOnLocal.push(documentId
|
|
244
|
-
} else if (!
|
|
245
|
-
missingOnRemote.push(documentId
|
|
246
|
-
} else if (!
|
|
247
|
-
different.push(documentId
|
|
323
|
+
if (!localDocuments[documentId]) {
|
|
324
|
+
missingOnLocal.push(documentId);
|
|
325
|
+
} else if (!remoteDocuments[documentId]) {
|
|
326
|
+
missingOnRemote.push(documentId);
|
|
327
|
+
} else if (!A.equals(local.documents[documentId], remote.documents[documentId])) {
|
|
328
|
+
different.push(documentId);
|
|
248
329
|
}
|
|
249
330
|
}
|
|
250
331
|
|
|
@@ -270,6 +351,8 @@ const isValidDocumentId = (documentId: DocumentId) => {
|
|
|
270
351
|
return typeof documentId === 'string' && !documentId.includes(':');
|
|
271
352
|
};
|
|
272
353
|
|
|
273
|
-
const
|
|
354
|
+
const SYNC_SPAN_METHOD = 'syncPeer';
|
|
355
|
+
|
|
356
|
+
const getSpanId = (peerId: PeerId) => {
|
|
274
357
|
return `collection-sync-${peerId}`;
|
|
275
358
|
};
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { type Message } from '@automerge/automerge-repo';
|
|
6
6
|
|
|
7
7
|
import { type TimeAware, trace } from '@dxos/tracing';
|
|
8
|
-
import { CircularBuffer,
|
|
8
|
+
import { CircularBuffer, SlidingWindowSummary, type SlidingWindowSummaryConfig, mapValues } from '@dxos/util';
|
|
9
9
|
|
|
10
10
|
import { type NetworkDataMonitor } from './echo-network-adapter';
|
|
11
11
|
import { type StorageAdapterDataMonitor } from './leveldb-storage-adapter';
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { type PeerId, cbor } from '@automerge/automerge-repo';
|
|
6
|
+
import { describe, expect, onTestFinished, test } from 'vitest';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { Trigger, sleep, waitForCondition } from '@dxos/async';
|
|
9
|
+
import { Context } from '@dxos/context';
|
|
9
10
|
import { invariant } from '@dxos/invariant';
|
|
10
11
|
import { PublicKey } from '@dxos/keys';
|
|
11
12
|
import { type SyncMessage } from '@dxos/protocols/proto/dxos/mesh/teleport/automerge';
|
|
@@ -113,7 +114,7 @@ describe('EchoNetworkAdapter', () => {
|
|
|
113
114
|
onTestFinished(async () => {
|
|
114
115
|
await adapter.close();
|
|
115
116
|
});
|
|
116
|
-
await adapter.addReplicator(replicator);
|
|
117
|
+
await adapter.addReplicator(Context.default(), replicator);
|
|
117
118
|
return adapter;
|
|
118
119
|
};
|
|
119
120
|
|
|
@@ -2,30 +2,37 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
import {
|
|
6
|
+
type DocumentId,
|
|
7
|
+
type Heads,
|
|
8
|
+
type Message,
|
|
9
|
+
NetworkAdapter,
|
|
10
|
+
type PeerId,
|
|
11
|
+
type PeerMetadata,
|
|
12
|
+
} from '@automerge/automerge-repo';
|
|
13
|
+
|
|
14
|
+
import { Event, Trigger, synchronized } from '@dxos/async';
|
|
15
|
+
import { type Context, LifecycleState } from '@dxos/context';
|
|
9
16
|
import { invariant } from '@dxos/invariant';
|
|
10
17
|
import { type PublicKey } from '@dxos/keys';
|
|
11
18
|
import { log } from '@dxos/log';
|
|
12
19
|
import type { AutomergeProtocolMessage } from '@dxos/protocols';
|
|
13
20
|
import { isNonNullable } from '@dxos/util';
|
|
14
21
|
|
|
22
|
+
import { createIdFromSpaceKey } from '../common/space-id';
|
|
15
23
|
import {
|
|
16
|
-
type
|
|
17
|
-
type
|
|
18
|
-
type
|
|
19
|
-
type
|
|
20
|
-
type
|
|
24
|
+
type AutomergeReplicator,
|
|
25
|
+
type AutomergeReplicatorConnection,
|
|
26
|
+
type RemoteDocumentExistenceCheckProps,
|
|
27
|
+
type ShouldAdvertiseProps,
|
|
28
|
+
type ShouldSyncCollectionProps,
|
|
21
29
|
} from './echo-replicator';
|
|
22
30
|
import {
|
|
23
|
-
isCollectionQueryMessage,
|
|
24
|
-
isCollectionStateMessage,
|
|
25
31
|
type CollectionQueryMessage,
|
|
26
32
|
type CollectionStateMessage,
|
|
33
|
+
isCollectionQueryMessage,
|
|
34
|
+
isCollectionStateMessage,
|
|
27
35
|
} from './network-protocol';
|
|
28
|
-
import { createIdFromSpaceKey } from '../common/space-id';
|
|
29
36
|
|
|
30
37
|
export interface NetworkDataMonitor {
|
|
31
38
|
recordPeerConnected(peerId: string): void;
|
|
@@ -35,9 +42,9 @@ export interface NetworkDataMonitor {
|
|
|
35
42
|
recordMessageSendingFailed(message: Message): void;
|
|
36
43
|
}
|
|
37
44
|
|
|
38
|
-
export type
|
|
45
|
+
export type EchoNetworkAdapterProps = {
|
|
39
46
|
getContainingSpaceForDocument: (documentId: string) => Promise<PublicKey | null>;
|
|
40
|
-
isDocumentInRemoteCollection: (params:
|
|
47
|
+
isDocumentInRemoteCollection: (params: RemoteDocumentExistenceCheckProps) => Promise<boolean>;
|
|
41
48
|
onCollectionStateQueried: (collectionId: string, peerId: PeerId) => void;
|
|
42
49
|
onCollectionStateReceived: (collectionId: string, peerId: PeerId, state: unknown) => void;
|
|
43
50
|
monitor?: NetworkDataMonitor;
|
|
@@ -45,16 +52,17 @@ export type EchoNetworkAdapterParams = {
|
|
|
45
52
|
|
|
46
53
|
type ConnectionEntry = {
|
|
47
54
|
isOpen: boolean;
|
|
48
|
-
connection:
|
|
55
|
+
connection: AutomergeReplicatorConnection;
|
|
49
56
|
reader: ReadableStreamDefaultReader<AutomergeProtocolMessage>;
|
|
50
57
|
writer: WritableStreamDefaultWriter<AutomergeProtocolMessage>;
|
|
58
|
+
requestedDocuments: Set<DocumentId>;
|
|
51
59
|
};
|
|
52
60
|
|
|
53
61
|
/**
|
|
54
|
-
* Manages a set of {@link
|
|
62
|
+
* Manages a set of {@link AutomergeReplicator} instances.
|
|
55
63
|
*/
|
|
56
64
|
export class EchoNetworkAdapter extends NetworkAdapter {
|
|
57
|
-
private readonly _replicators = new Set<
|
|
65
|
+
private readonly _replicators = new Set<AutomergeReplicator>();
|
|
58
66
|
/**
|
|
59
67
|
* Remote peer id -> connection.
|
|
60
68
|
*/
|
|
@@ -63,7 +71,9 @@ export class EchoNetworkAdapter extends NetworkAdapter {
|
|
|
63
71
|
private readonly _connected = new Trigger();
|
|
64
72
|
private readonly _ready = new Trigger();
|
|
65
73
|
|
|
66
|
-
|
|
74
|
+
public readonly documentRequested = new Event<{ documentId: DocumentId; peerId: PeerId }>();
|
|
75
|
+
|
|
76
|
+
constructor(private readonly _params: EchoNetworkAdapterProps) {
|
|
67
77
|
super();
|
|
68
78
|
}
|
|
69
79
|
|
|
@@ -125,13 +135,13 @@ export class EchoNetworkAdapter extends NetworkAdapter {
|
|
|
125
135
|
}
|
|
126
136
|
|
|
127
137
|
@synchronized
|
|
128
|
-
async addReplicator(replicator:
|
|
138
|
+
async addReplicator(ctx: Context, replicator: AutomergeReplicator): Promise<void> {
|
|
129
139
|
invariant(this._lifecycleState === LifecycleState.OPEN);
|
|
130
140
|
invariant(this.peerId);
|
|
131
141
|
invariant(!this._replicators.has(replicator));
|
|
132
142
|
|
|
133
143
|
this._replicators.add(replicator);
|
|
134
|
-
await replicator.connect({
|
|
144
|
+
await replicator.connect(ctx, {
|
|
135
145
|
peerId: this.peerId,
|
|
136
146
|
onConnectionOpen: this._onConnectionOpen.bind(this),
|
|
137
147
|
onConnectionClosed: this._onConnectionClosed.bind(this),
|
|
@@ -146,14 +156,14 @@ export class EchoNetworkAdapter extends NetworkAdapter {
|
|
|
146
156
|
}
|
|
147
157
|
|
|
148
158
|
@synchronized
|
|
149
|
-
async removeReplicator(replicator:
|
|
159
|
+
async removeReplicator(replicator: AutomergeReplicator): Promise<void> {
|
|
150
160
|
invariant(this._lifecycleState === LifecycleState.OPEN);
|
|
151
161
|
invariant(this._replicators.has(replicator));
|
|
152
162
|
await replicator.disconnect();
|
|
153
163
|
this._replicators.delete(replicator);
|
|
154
164
|
}
|
|
155
165
|
|
|
156
|
-
async shouldAdvertise(peerId: PeerId, params:
|
|
166
|
+
async shouldAdvertise(peerId: PeerId, params: ShouldAdvertiseProps): Promise<boolean> {
|
|
157
167
|
const connection = this._connections.get(peerId);
|
|
158
168
|
if (!connection) {
|
|
159
169
|
return false;
|
|
@@ -162,7 +172,7 @@ export class EchoNetworkAdapter extends NetworkAdapter {
|
|
|
162
172
|
return connection.connection.shouldAdvertise(params);
|
|
163
173
|
}
|
|
164
174
|
|
|
165
|
-
shouldSyncCollection(peerId: PeerId, params:
|
|
175
|
+
shouldSyncCollection(peerId: PeerId, params: ShouldSyncCollectionProps): boolean {
|
|
166
176
|
const connection = this._connections.get(peerId);
|
|
167
177
|
if (!connection) {
|
|
168
178
|
return false;
|
|
@@ -203,6 +213,30 @@ export class EchoNetworkAdapter extends NetworkAdapter {
|
|
|
203
213
|
.filter(isNonNullable);
|
|
204
214
|
}
|
|
205
215
|
|
|
216
|
+
bundleSyncEnabledForPeer(peerId: PeerId): boolean {
|
|
217
|
+
const connection = this._connections.get(peerId);
|
|
218
|
+
if (!connection) {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
return connection.connection.bundleSyncEnabled;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async pushBundle(ctx: Context, peerId: PeerId, bundle: { documentId: DocumentId; data: Uint8Array; heads: Heads }[]) {
|
|
225
|
+
const connection = this._connections.get(peerId);
|
|
226
|
+
if (!connection) {
|
|
227
|
+
throw new Error('Connection not found.');
|
|
228
|
+
}
|
|
229
|
+
return connection.connection.pushBundle!(ctx, bundle);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async pullBundle(ctx: Context, peerId: PeerId, docHeads: Record<DocumentId, Heads>) {
|
|
233
|
+
const connection = this._connections.get(peerId);
|
|
234
|
+
if (!connection) {
|
|
235
|
+
throw new Error('Connection not found.');
|
|
236
|
+
}
|
|
237
|
+
return connection.connection.pullBundle!(ctx, docHeads);
|
|
238
|
+
}
|
|
239
|
+
|
|
206
240
|
private _send(message: Message): void {
|
|
207
241
|
const connectionEntry = this._connections.get(message.targetId);
|
|
208
242
|
if (!connectionEntry) {
|
|
@@ -225,7 +259,7 @@ export class EchoNetworkAdapter extends NetworkAdapter {
|
|
|
225
259
|
});
|
|
226
260
|
}
|
|
227
261
|
|
|
228
|
-
private _onConnectionOpen(connection:
|
|
262
|
+
private _onConnectionOpen(connection: AutomergeReplicatorConnection): void {
|
|
229
263
|
log('connection opened', { peerId: connection.peerId });
|
|
230
264
|
invariant(!this._connections.has(connection.peerId as PeerId));
|
|
231
265
|
const connectionEntry: ConnectionEntry = {
|
|
@@ -233,6 +267,7 @@ export class EchoNetworkAdapter extends NetworkAdapter {
|
|
|
233
267
|
connection,
|
|
234
268
|
reader: connection.readable.getReader(),
|
|
235
269
|
writer: connection.writable.getWriter(),
|
|
270
|
+
requestedDocuments: new Set(),
|
|
236
271
|
};
|
|
237
272
|
|
|
238
273
|
this._connections.set(connection.peerId as PeerId, connectionEntry);
|
|
@@ -247,7 +282,7 @@ export class EchoNetworkAdapter extends NetworkAdapter {
|
|
|
247
282
|
break;
|
|
248
283
|
}
|
|
249
284
|
|
|
250
|
-
this._onMessage(value as Message);
|
|
285
|
+
this._onMessage(connectionEntry, value as Message);
|
|
251
286
|
}
|
|
252
287
|
} catch (err) {
|
|
253
288
|
if (connectionEntry.isOpen) {
|
|
@@ -261,7 +296,15 @@ export class EchoNetworkAdapter extends NetworkAdapter {
|
|
|
261
296
|
this._params.monitor?.recordPeerConnected(connection.peerId);
|
|
262
297
|
}
|
|
263
298
|
|
|
264
|
-
private _onMessage(message: Message): void {
|
|
299
|
+
private _onMessage(connectionEntry: ConnectionEntry, message: Message): void {
|
|
300
|
+
const amMessage = message as AutomergeProtocolMessage;
|
|
301
|
+
if (amMessage.type === 'request') {
|
|
302
|
+
this.documentRequested.emit({
|
|
303
|
+
documentId: amMessage.documentId as DocumentId,
|
|
304
|
+
peerId: connectionEntry.connection.peerId as PeerId,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
265
308
|
if (isCollectionQueryMessage(message)) {
|
|
266
309
|
this._params.onCollectionStateQueried(message.collectionId, message.senderId);
|
|
267
310
|
} else if (isCollectionStateMessage(message)) {
|
|
@@ -272,7 +315,7 @@ export class EchoNetworkAdapter extends NetworkAdapter {
|
|
|
272
315
|
this._params.monitor?.recordMessageReceived(message);
|
|
273
316
|
}
|
|
274
317
|
|
|
275
|
-
private _onConnectionClosed(connection:
|
|
318
|
+
private _onConnectionClosed(connection: AutomergeReplicatorConnection): void {
|
|
276
319
|
log('connection closed', { peerId: connection.peerId });
|
|
277
320
|
const entry = this._connections.get(connection.peerId as PeerId);
|
|
278
321
|
invariant(entry);
|
|
@@ -281,8 +324,9 @@ export class EchoNetworkAdapter extends NetworkAdapter {
|
|
|
281
324
|
this.emit('peer-disconnected', { peerId: connection.peerId as PeerId });
|
|
282
325
|
this._params.monitor?.recordPeerDisconnected(connection.peerId);
|
|
283
326
|
|
|
284
|
-
void entry.reader.cancel().catch((err) => log.catch(err));
|
|
285
327
|
void entry.writer.abort().catch((err) => log.catch(err));
|
|
328
|
+
void entry.reader.cancel().catch((err) => log.catch(err));
|
|
329
|
+
|
|
286
330
|
this._connections.delete(connection.peerId as PeerId);
|
|
287
331
|
}
|
|
288
332
|
|
|
@@ -290,7 +334,7 @@ export class EchoNetworkAdapter extends NetworkAdapter {
|
|
|
290
334
|
* Trigger doc-synchronizer shared documents set recalculation. Happens on peer-candidate.
|
|
291
335
|
* TODO(y): replace with a proper API call when sharePolicy update becomes supported by automerge-repo
|
|
292
336
|
*/
|
|
293
|
-
private _onConnectionAuthScopeChanged(connection:
|
|
337
|
+
private _onConnectionAuthScopeChanged(connection: AutomergeReplicatorConnection): void {
|
|
294
338
|
log('Connection auth scope changed', { peerId: connection.peerId });
|
|
295
339
|
const entry = this._connections.get(connection.peerId as PeerId);
|
|
296
340
|
invariant(entry);
|
|
@@ -298,7 +342,7 @@ export class EchoNetworkAdapter extends NetworkAdapter {
|
|
|
298
342
|
this._emitPeerCandidate(connection);
|
|
299
343
|
}
|
|
300
344
|
|
|
301
|
-
private _emitPeerCandidate(connection:
|
|
345
|
+
private _emitPeerCandidate(connection: AutomergeReplicatorConnection): void {
|
|
302
346
|
this.emit('peer-candidate', {
|
|
303
347
|
peerId: connection.peerId as PeerId,
|
|
304
348
|
peerMetadata: createEchoPeerMetadata(),
|
|
@@ -2,14 +2,19 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { type Heads } from '@automerge/automerge';
|
|
6
|
+
import { type DocumentId } from '@automerge/automerge-repo';
|
|
7
|
+
|
|
8
|
+
import { type Context } from '@dxos/context';
|
|
5
9
|
import { type PublicKey, type SpaceId } from '@dxos/keys';
|
|
6
|
-
import type
|
|
10
|
+
import { type AutomergeProtocolMessage } from '@dxos/protocols';
|
|
7
11
|
|
|
8
|
-
|
|
12
|
+
// TODO(burdon): Rename AutomergeReplicator?
|
|
13
|
+
export interface AutomergeReplicator {
|
|
9
14
|
/**
|
|
10
15
|
* Called on when replicator is added to EchoHost.
|
|
11
16
|
*/
|
|
12
|
-
connect(context:
|
|
17
|
+
connect(ctx: Context, context: AutomergeReplicatorContext): Promise<void>;
|
|
13
18
|
|
|
14
19
|
/**
|
|
15
20
|
* Called on when replicator is removed from EchoHost.
|
|
@@ -17,7 +22,7 @@ export interface EchoReplicator {
|
|
|
17
22
|
disconnect(): Promise<void>;
|
|
18
23
|
}
|
|
19
24
|
|
|
20
|
-
export interface
|
|
25
|
+
export interface AutomergeReplicatorContext {
|
|
21
26
|
/**
|
|
22
27
|
* Our own peer id.
|
|
23
28
|
*/
|
|
@@ -28,16 +33,20 @@ export interface EchoReplicatorContext {
|
|
|
28
33
|
*/
|
|
29
34
|
getContainingSpaceForDocument(documentId: string): Promise<PublicKey | null>;
|
|
30
35
|
getContainingSpaceIdForDocument(documentId: string): Promise<SpaceId | null>;
|
|
31
|
-
isDocumentInRemoteCollection(params: RemoteDocumentExistenceCheckParams): Promise<boolean>;
|
|
32
36
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Returns false if collection sync hasn't happened yet.
|
|
39
|
+
*/
|
|
40
|
+
isDocumentInRemoteCollection(params: RemoteDocumentExistenceCheckProps): Promise<boolean>;
|
|
41
|
+
|
|
42
|
+
onConnectionOpen(connection: AutomergeReplicatorConnection): void;
|
|
43
|
+
onConnectionClosed(connection: AutomergeReplicatorConnection): void;
|
|
44
|
+
onConnectionAuthScopeChanged(connection: AutomergeReplicatorConnection): void;
|
|
36
45
|
}
|
|
37
46
|
|
|
38
|
-
export interface
|
|
47
|
+
export interface AutomergeReplicatorConnection {
|
|
39
48
|
/**
|
|
40
|
-
*
|
|
49
|
+
* Remote peer id.
|
|
41
50
|
*/
|
|
42
51
|
get peerId(): string;
|
|
43
52
|
|
|
@@ -55,23 +64,40 @@ export interface ReplicatorConnection {
|
|
|
55
64
|
* @returns true if the document should be advertised to this peer.
|
|
56
65
|
* The remote peer can still request the document by its id bypassing this check.
|
|
57
66
|
*/
|
|
58
|
-
shouldAdvertise(params:
|
|
67
|
+
shouldAdvertise(params: ShouldAdvertiseProps): Promise<boolean>;
|
|
59
68
|
|
|
60
69
|
/**
|
|
61
70
|
* @returns true if the collection should be synced to this peer.
|
|
62
71
|
*/
|
|
63
|
-
shouldSyncCollection(params:
|
|
72
|
+
shouldSyncCollection(params: ShouldSyncCollectionProps): boolean;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Batch syncing considered enabled if AutomergeReplicatorConnection implements `pushBatch` and `pullBatch` methods.
|
|
76
|
+
* @returns true if the batch syncing is enabled.
|
|
77
|
+
*/
|
|
78
|
+
get bundleSyncEnabled(): boolean;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Pushes the batch of documents to the remote peer.
|
|
82
|
+
*/
|
|
83
|
+
pushBundle?(ctx: Context, bundle: { documentId: DocumentId; data: Uint8Array; heads: Heads }[]): Promise<void>;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Pulls the batch of documents from the remote peer.
|
|
87
|
+
*/
|
|
88
|
+
// TODO(mykola): Use automerge-repo-bundles Bundle type here.
|
|
89
|
+
pullBundle?(ctx: Context, docHeads: Record<DocumentId, Heads>): Promise<Record<DocumentId, Uint8Array>>;
|
|
64
90
|
}
|
|
65
91
|
|
|
66
|
-
export type
|
|
92
|
+
export type ShouldAdvertiseProps = {
|
|
67
93
|
documentId: string;
|
|
68
94
|
};
|
|
69
95
|
|
|
70
|
-
export type
|
|
96
|
+
export type ShouldSyncCollectionProps = {
|
|
71
97
|
collectionId: string;
|
|
72
98
|
};
|
|
73
99
|
|
|
74
|
-
export type
|
|
100
|
+
export type RemoteDocumentExistenceCheckProps = {
|
|
75
101
|
peerId: string;
|
|
76
102
|
documentId: string;
|
|
77
103
|
};
|