@dxos/echo-pipeline 0.8.3 → 0.8.4-main.1da679c
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-TQJTKNMS.mjs → chunk-KQYT6ADL.mjs} +109 -3
- package/dist/lib/browser/chunk-KQYT6ADL.mjs.map +7 -0
- package/dist/lib/browser/{chunk-35I6ERLG.mjs → chunk-XGG76KKU.mjs} +513 -350
- package/dist/lib/browser/chunk-XGG76KKU.mjs.map +7 -0
- package/dist/lib/browser/filter/index.mjs +3 -1
- package/dist/lib/browser/index.mjs +1371 -601
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +119 -56
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/node-esm/{chunk-5BHLPT24.mjs → chunk-CHMJJ4DG.mjs} +513 -350
- package/dist/lib/node-esm/chunk-CHMJJ4DG.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-RVK35BS7.mjs → chunk-W4ACY3YC.mjs} +109 -3
- package/dist/lib/node-esm/chunk-W4ACY3YC.mjs.map +7 -0
- package/dist/lib/node-esm/filter/index.mjs +3 -1
- package/dist/lib/node-esm/index.mjs +1371 -601
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +119 -56
- package/dist/lib/node-esm/testing/index.mjs.map +3 -3
- package/dist/types/src/automerge/automerge-host.d.ts +15 -28
- package/dist/types/src/automerge/automerge-host.d.ts.map +1 -1
- package/dist/types/src/automerge/collection-synchronizer.d.ts +1 -1
- package/dist/types/src/automerge/collection-synchronizer.d.ts.map +1 -1
- package/dist/types/src/automerge/echo-network-adapter.d.ts +8 -1
- package/dist/types/src/automerge/echo-network-adapter.d.ts.map +1 -1
- package/dist/types/src/automerge/echo-replicator.d.ts +21 -2
- package/dist/types/src/automerge/echo-replicator.d.ts.map +1 -1
- package/dist/types/src/automerge/index.d.ts +1 -1
- package/dist/types/src/automerge/index.d.ts.map +1 -1
- package/dist/types/src/automerge/leveldb-storage-adapter.d.ts +1 -1
- package/dist/types/src/automerge/leveldb-storage-adapter.d.ts.map +1 -1
- package/dist/types/src/automerge/mesh-echo-replicator-connection.d.ts +1 -0
- package/dist/types/src/automerge/mesh-echo-replicator-connection.d.ts.map +1 -1
- package/dist/types/src/automerge/mesh-echo-replicator.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/db-host/data-service.d.ts +2 -2
- package/dist/types/src/db-host/data-service.d.ts.map +1 -1
- package/dist/types/src/db-host/database-root.d.ts.map +1 -1
- package/dist/types/src/db-host/documents-synchronizer.d.ts +2 -2
- package/dist/types/src/db-host/documents-synchronizer.d.ts.map +1 -1
- package/dist/types/src/db-host/echo-host.d.ts +2 -2
- package/dist/types/src/db-host/echo-host.d.ts.map +1 -1
- package/dist/types/src/db-host/query-service.d.ts +1 -1
- package/dist/types/src/db-host/query-service.d.ts.map +1 -1
- package/dist/types/src/db-host/space-state-manager.d.ts +1 -1
- package/dist/types/src/db-host/space-state-manager.d.ts.map +1 -1
- package/dist/types/src/edge/echo-edge-replicator.d.ts +4 -2
- package/dist/types/src/edge/echo-edge-replicator.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/pipeline.d.ts +1 -1
- package/dist/types/src/pipeline/pipeline.d.ts.map +1 -1
- package/dist/types/src/query/errors.d.ts +24 -8
- package/dist/types/src/query/errors.d.ts.map +1 -1
- package/dist/types/src/query/plan.d.ts +8 -1
- package/dist/types/src/query/plan.d.ts.map +1 -1
- package/dist/types/src/query/query-executor.d.ts +4 -1
- package/dist/types/src/query/query-executor.d.ts.map +1 -1
- package/dist/types/src/query/query-planner.d.ts +2 -0
- 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/control-pipeline.d.ts +1 -1
- package/dist/types/src/space/control-pipeline.d.ts.map +1 -1
- package/dist/types/src/space/space-manager.d.ts +1 -1
- package/dist/types/src/space/space-manager.d.ts.map +1 -1
- package/dist/types/src/space/space-protocol.d.ts +1 -1
- package/dist/types/src/space/space-protocol.d.ts.map +1 -1
- package/dist/types/src/space/space.d.ts +1 -1
- package/dist/types/src/space/space.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-replicator.d.ts +1 -0
- 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 +42 -38
- package/src/automerge/automerge-host.test.ts +18 -8
- package/src/automerge/automerge-host.ts +251 -65
- package/src/automerge/automerge-repo.test.ts +67 -16
- package/src/automerge/collection-synchronizer.test.ts +2 -2
- package/src/automerge/collection-synchronizer.ts +4 -4
- package/src/automerge/echo-data-monitor.ts +1 -1
- package/src/automerge/echo-network-adapter.test.ts +3 -3
- package/src/automerge/echo-network-adapter.ts +40 -7
- package/src/automerge/echo-replicator.ts +23 -2
- package/src/automerge/index.ts +1 -1
- package/src/automerge/leveldb-storage-adapter.ts +7 -7
- package/src/automerge/mesh-echo-replicator-connection.ts +4 -0
- package/src/automerge/mesh-echo-replicator.ts +2 -1
- package/src/automerge/storage-adapter.test.ts +1 -1
- package/src/common/space-id.ts +1 -1
- package/src/db-host/data-service.ts +9 -17
- package/src/db-host/database-root.ts +2 -2
- package/src/db-host/documents-synchronizer.test.ts +1 -1
- package/src/db-host/documents-synchronizer.ts +39 -26
- package/src/db-host/echo-host.ts +13 -14
- package/src/db-host/query-service.ts +8 -1
- package/src/db-host/space-state-manager.ts +2 -2
- package/src/edge/echo-edge-replicator.test.ts +5 -3
- package/src/edge/echo-edge-replicator.ts +75 -18
- package/src/filter/filter-match.test.ts +23 -3
- package/src/filter/filter-match.ts +148 -3
- package/src/metadata/metadata-store.ts +3 -3
- package/src/pipeline/pipeline-stress.test.ts +4 -2
- package/src/pipeline/pipeline.test.ts +3 -2
- package/src/pipeline/pipeline.ts +8 -5
- package/src/query/errors.ts +2 -0
- package/src/query/plan.ts +12 -1
- package/src/query/query-executor.ts +66 -11
- package/src/query/query-planner.test.ts +146 -2
- package/src/query/query-planner.ts +52 -8
- package/src/space/admission-discovery-extension.ts +2 -2
- package/src/space/control-pipeline.test.ts +4 -3
- package/src/space/control-pipeline.ts +9 -6
- package/src/space/space-manager.browser.test.ts +1 -1
- package/src/space/space-manager.ts +5 -4
- package/src/space/space-protocol.browser.test.ts +2 -2
- package/src/space/space-protocol.test.ts +3 -2
- package/src/space/space-protocol.ts +6 -3
- package/src/space/space.test.ts +1 -1
- package/src/space/space.ts +3 -2
- package/src/testing/test-agent-builder.ts +4 -3
- package/src/testing/test-replicator.ts +4 -0
- package/src/util.ts +1 -1
- package/dist/lib/browser/chunk-35I6ERLG.mjs.map +0 -7
- package/dist/lib/browser/chunk-TQJTKNMS.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.map +0 -7
- package/dist/lib/node-esm/chunk-RVK35BS7.mjs.map +0 -7
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
import { next as am } from '@automerge/automerge';
|
|
6
6
|
import type { DocumentId, PeerId } from '@automerge/automerge-repo';
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { Event, asyncReturn, scheduleTask, scheduleTaskInterval } from '@dxos/async';
|
|
9
|
+
import { type Context, Resource } from '@dxos/context';
|
|
10
10
|
import { log } from '@dxos/log';
|
|
11
11
|
import { trace } from '@dxos/tracing';
|
|
12
12
|
import { defaultMap } from '@dxos/util';
|
|
@@ -239,9 +239,9 @@ export const diffCollectionState = (local: CollectionState, remote: CollectionSt
|
|
|
239
239
|
const missingOnLocal: DocumentId[] = [];
|
|
240
240
|
const different: DocumentId[] = [];
|
|
241
241
|
for (const documentId of allDocuments) {
|
|
242
|
-
if (!local.documents[documentId]) {
|
|
242
|
+
if (!local.documents[documentId] || local.documents[documentId].length === 0) {
|
|
243
243
|
missingOnLocal.push(documentId as DocumentId);
|
|
244
|
-
} else if (!remote.documents[documentId]) {
|
|
244
|
+
} else if (!remote.documents[documentId] || remote.documents[documentId].length === 0) {
|
|
245
245
|
missingOnRemote.push(documentId as DocumentId);
|
|
246
246
|
} else if (!am.equals(local.documents[documentId], remote.documents[documentId])) {
|
|
247
247
|
different.push(documentId as DocumentId);
|
|
@@ -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,10 @@
|
|
|
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
9
|
import { invariant } from '@dxos/invariant';
|
|
10
10
|
import { PublicKey } from '@dxos/keys';
|
|
11
11
|
import { type SyncMessage } from '@dxos/protocols/proto/dxos/mesh/teleport/automerge';
|
|
@@ -2,9 +2,16 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
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 { Trigger, synchronized } from '@dxos/async';
|
|
8
15
|
import { LifecycleState } from '@dxos/context';
|
|
9
16
|
import { invariant } from '@dxos/invariant';
|
|
10
17
|
import { type PublicKey } from '@dxos/keys';
|
|
@@ -12,6 +19,8 @@ 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';
|
|
23
|
+
|
|
15
24
|
import {
|
|
16
25
|
type EchoReplicator,
|
|
17
26
|
type RemoteDocumentExistenceCheckParams,
|
|
@@ -20,12 +29,11 @@ import {
|
|
|
20
29
|
type ShouldSyncCollectionParams,
|
|
21
30
|
} from './echo-replicator';
|
|
22
31
|
import {
|
|
23
|
-
isCollectionQueryMessage,
|
|
24
|
-
isCollectionStateMessage,
|
|
25
32
|
type CollectionQueryMessage,
|
|
26
33
|
type CollectionStateMessage,
|
|
34
|
+
isCollectionQueryMessage,
|
|
35
|
+
isCollectionStateMessage,
|
|
27
36
|
} from './network-protocol';
|
|
28
|
-
import { createIdFromSpaceKey } from '../common/space-id';
|
|
29
37
|
|
|
30
38
|
export interface NetworkDataMonitor {
|
|
31
39
|
recordPeerConnected(peerId: string): void;
|
|
@@ -203,6 +211,30 @@ export class EchoNetworkAdapter extends NetworkAdapter {
|
|
|
203
211
|
.filter(isNonNullable);
|
|
204
212
|
}
|
|
205
213
|
|
|
214
|
+
bundleSyncEnabledForPeer(peerId: PeerId): boolean {
|
|
215
|
+
const connection = this._connections.get(peerId);
|
|
216
|
+
if (!connection) {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
return connection.connection.bundleSyncEnabled;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async pushBundle(peerId: PeerId, bundle: { documentId: DocumentId; data: Uint8Array; heads: Heads }[]) {
|
|
223
|
+
const connection = this._connections.get(peerId);
|
|
224
|
+
if (!connection) {
|
|
225
|
+
throw new Error('Connection not found.');
|
|
226
|
+
}
|
|
227
|
+
return connection.connection.pushBundle!(bundle);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async pullBundle(peerId: PeerId, docHeads: Record<DocumentId, Heads>) {
|
|
231
|
+
const connection = this._connections.get(peerId);
|
|
232
|
+
if (!connection) {
|
|
233
|
+
throw new Error('Connection not found.');
|
|
234
|
+
}
|
|
235
|
+
return connection.connection.pullBundle!(docHeads);
|
|
236
|
+
}
|
|
237
|
+
|
|
206
238
|
private _send(message: Message): void {
|
|
207
239
|
const connectionEntry = this._connections.get(message.targetId);
|
|
208
240
|
if (!connectionEntry) {
|
|
@@ -281,8 +313,9 @@ export class EchoNetworkAdapter extends NetworkAdapter {
|
|
|
281
313
|
this.emit('peer-disconnected', { peerId: connection.peerId as PeerId });
|
|
282
314
|
this._params.monitor?.recordPeerDisconnected(connection.peerId);
|
|
283
315
|
|
|
284
|
-
void entry.reader.cancel().catch((err) => log.catch(err));
|
|
285
316
|
void entry.writer.abort().catch((err) => log.catch(err));
|
|
317
|
+
void entry.reader.cancel().catch((err) => log.catch(err));
|
|
318
|
+
|
|
286
319
|
this._connections.delete(connection.peerId as PeerId);
|
|
287
320
|
}
|
|
288
321
|
|
|
@@ -2,9 +2,13 @@
|
|
|
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
|
+
|
|
5
8
|
import { type PublicKey, type SpaceId } from '@dxos/keys';
|
|
6
|
-
import type
|
|
9
|
+
import { type AutomergeProtocolMessage } from '@dxos/protocols';
|
|
7
10
|
|
|
11
|
+
// TODO(burdon): Rename AutomergeReplicator?
|
|
8
12
|
export interface EchoReplicator {
|
|
9
13
|
/**
|
|
10
14
|
* Called on when replicator is added to EchoHost.
|
|
@@ -37,7 +41,7 @@ export interface EchoReplicatorContext {
|
|
|
37
41
|
|
|
38
42
|
export interface ReplicatorConnection {
|
|
39
43
|
/**
|
|
40
|
-
*
|
|
44
|
+
* Remote peer id.
|
|
41
45
|
*/
|
|
42
46
|
get peerId(): string;
|
|
43
47
|
|
|
@@ -61,6 +65,23 @@ export interface ReplicatorConnection {
|
|
|
61
65
|
* @returns true if the collection should be synced to this peer.
|
|
62
66
|
*/
|
|
63
67
|
shouldSyncCollection(params: ShouldSyncCollectionParams): boolean;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Batch syncing considered enabled if ReplicatorConnection implements `pushBatch` and `pullBatch` methods.
|
|
71
|
+
* @returns true if the batch syncing is enabled.
|
|
72
|
+
*/
|
|
73
|
+
get bundleSyncEnabled(): boolean;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Pushes the batch of documents to the remote peer.
|
|
77
|
+
*/
|
|
78
|
+
pushBundle?(bundle: { documentId: DocumentId; data: Uint8Array; heads: Heads }[]): Promise<void>;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Pulls the batch of documents from the remote peer.
|
|
82
|
+
*/
|
|
83
|
+
// TODO(mykola): Use automerge-repo-bundles Bundle type here.
|
|
84
|
+
pullBundle?(docHeads: Record<DocumentId, Heads>): Promise<Record<DocumentId, Uint8Array>>;
|
|
64
85
|
}
|
|
65
86
|
|
|
66
87
|
export type ShouldAdvertiseParams = {
|
package/src/automerge/index.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
export * from './automerge-host';
|
|
6
6
|
export * from './leveldb-storage-adapter';
|
|
7
7
|
export * from './mesh-echo-replicator';
|
|
8
|
-
export * from './echo-replicator';
|
|
8
|
+
export type * from './echo-replicator';
|
|
9
9
|
export { diffCollectionState } from './collection-synchronizer';
|
|
10
10
|
export * from './space-collection';
|
|
11
11
|
export * from './echo-data-monitor';
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
// s
|
|
4
4
|
|
|
5
|
-
import { type
|
|
5
|
+
import { type Chunk, type StorageAdapterInterface, type StorageKey } from '@automerge/automerge-repo';
|
|
6
6
|
import { type MixedEncoding } from 'level-transcoder';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { Resource } from '@dxos/context';
|
|
9
9
|
import { type BatchLevel, type SublevelDB } from '@dxos/kv-store';
|
|
10
10
|
import { type MaybePromise } from '@dxos/util';
|
|
11
11
|
|
|
@@ -36,7 +36,7 @@ export class LevelDBStorageAdapter extends Resource implements StorageAdapterInt
|
|
|
36
36
|
|
|
37
37
|
async load(keyArray: StorageKey): Promise<Uint8Array | undefined> {
|
|
38
38
|
try {
|
|
39
|
-
if (this.
|
|
39
|
+
if (!this.isOpen) {
|
|
40
40
|
// TODO(mykola): this should be an error.
|
|
41
41
|
return undefined;
|
|
42
42
|
}
|
|
@@ -54,7 +54,7 @@ export class LevelDBStorageAdapter extends Resource implements StorageAdapterInt
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
async save(keyArray: StorageKey, binary: Uint8Array): Promise<void> {
|
|
57
|
-
if (this.
|
|
57
|
+
if (!this.isOpen) {
|
|
58
58
|
return undefined;
|
|
59
59
|
}
|
|
60
60
|
const startMs = Date.now();
|
|
@@ -72,14 +72,14 @@ export class LevelDBStorageAdapter extends Resource implements StorageAdapterInt
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
async remove(keyArray: StorageKey): Promise<void> {
|
|
75
|
-
if (this.
|
|
75
|
+
if (!this.isOpen) {
|
|
76
76
|
return undefined;
|
|
77
77
|
}
|
|
78
78
|
await this._params.db.del<StorageKey>(keyArray, { ...encodingOptions });
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
async loadRange(keyPrefix: StorageKey): Promise<Chunk[]> {
|
|
82
|
-
if (this.
|
|
82
|
+
if (!this.isOpen) {
|
|
83
83
|
return [];
|
|
84
84
|
}
|
|
85
85
|
const startMs = Date.now();
|
|
@@ -100,7 +100,7 @@ export class LevelDBStorageAdapter extends Resource implements StorageAdapterInt
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
async removeRange(keyPrefix: StorageKey): Promise<void> {
|
|
103
|
-
if (this.
|
|
103
|
+
if (!this.isOpen) {
|
|
104
104
|
return undefined;
|
|
105
105
|
}
|
|
106
106
|
const batch = this._params.db.batch();
|
|
@@ -111,6 +111,10 @@ export class MeshReplicatorConnection extends Resource implements ReplicatorConn
|
|
|
111
111
|
return this._isEnabled;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
get bundleSyncEnabled(): boolean {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
|
|
114
118
|
async shouldAdvertise(params: ShouldAdvertiseParams): Promise<boolean> {
|
|
115
119
|
return this._params.shouldAdvertise(params);
|
|
116
120
|
}
|
|
@@ -12,10 +12,11 @@ import {
|
|
|
12
12
|
} from '@dxos/teleport-extension-automerge-replicator';
|
|
13
13
|
import { ComplexSet, defaultMap } from '@dxos/util';
|
|
14
14
|
|
|
15
|
+
import { createIdFromSpaceKey } from '../common/space-id';
|
|
16
|
+
|
|
15
17
|
import { type EchoReplicator, type EchoReplicatorContext, type ShouldAdvertiseParams } from './echo-replicator';
|
|
16
18
|
import { MeshReplicatorConnection } from './mesh-echo-replicator-connection';
|
|
17
19
|
import { getSpaceIdFromCollectionId } from './space-collection';
|
|
18
|
-
import { createIdFromSpaceKey } from '../common/space-id';
|
|
19
20
|
|
|
20
21
|
// TODO(dmaretskyi): Move out of @dxos/echo-pipeline.
|
|
21
22
|
|
package/src/common/space-id.ts
CHANGED
|
@@ -18,7 +18,7 @@ export const createIdFromSpaceKey = async (spaceKey: PublicKey): Promise<SpaceId
|
|
|
18
18
|
return cachedValue;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
const digest = await subtleCrypto.digest('SHA-256', spaceKey.asUint8Array());
|
|
21
|
+
const digest = await subtleCrypto.digest('SHA-256', spaceKey.asUint8Array() as Uint8Array<ArrayBuffer>);
|
|
22
22
|
|
|
23
23
|
const bytes = new Uint8Array(digest).slice(0, SpaceId.byteLength);
|
|
24
24
|
const spaceId = SpaceId.encode(bytes);
|
|
@@ -11,23 +11,24 @@ import { invariant } from '@dxos/invariant';
|
|
|
11
11
|
import { SpaceId } from '@dxos/keys';
|
|
12
12
|
import { log } from '@dxos/log';
|
|
13
13
|
import {
|
|
14
|
+
type BatchedDocumentUpdates,
|
|
14
15
|
type DataService,
|
|
15
16
|
type FlushRequest,
|
|
16
|
-
type SubscribeRequest,
|
|
17
|
-
type BatchedDocumentUpdates,
|
|
18
|
-
type UpdateSubscriptionRequest,
|
|
19
17
|
type GetDocumentHeadsRequest,
|
|
20
18
|
type GetDocumentHeadsResponse,
|
|
21
|
-
type ReIndexHeadsRequest,
|
|
22
|
-
type WaitUntilHeadsReplicatedRequest,
|
|
23
|
-
type UpdateRequest,
|
|
24
19
|
type GetSpaceSyncStateRequest,
|
|
20
|
+
type ReIndexHeadsRequest,
|
|
25
21
|
type SpaceSyncState,
|
|
22
|
+
type SubscribeRequest,
|
|
23
|
+
type UpdateRequest,
|
|
24
|
+
type UpdateSubscriptionRequest,
|
|
25
|
+
type WaitUntilHeadsReplicatedRequest,
|
|
26
26
|
} from '@dxos/protocols/proto/dxos/echo/service';
|
|
27
27
|
|
|
28
|
+
import { type AutomergeHost, deriveCollectionIdFromSpaceId } from '../automerge';
|
|
29
|
+
|
|
28
30
|
import { DocumentsSynchronizer } from './documents-synchronizer';
|
|
29
31
|
import { type SpaceStateManager } from './space-state-manager';
|
|
30
|
-
import { deriveCollectionIdFromSpaceId, type AutomergeHost } from '../automerge';
|
|
31
32
|
|
|
32
33
|
export type DataServiceParams = {
|
|
33
34
|
automergeHost: AutomergeHost;
|
|
@@ -145,16 +146,7 @@ export class DataServiceImpl implements DataService {
|
|
|
145
146
|
const scheduler = new UpdateScheduler(ctx, async () => {
|
|
146
147
|
const state = collectionId ? await this._automergeHost.getCollectionSyncState(collectionId) : { peers: [] };
|
|
147
148
|
|
|
148
|
-
next({
|
|
149
|
-
peers: state.peers.map((peer) => ({
|
|
150
|
-
peerId: peer.peerId,
|
|
151
|
-
missingOnRemote: peer.missingOnRemote,
|
|
152
|
-
missingOnLocal: peer.missingOnLocal,
|
|
153
|
-
differentDocuments: peer.differentDocuments,
|
|
154
|
-
localDocumentCount: peer.localDocumentCount,
|
|
155
|
-
remoteDocumentCount: peer.remoteDocumentCount,
|
|
156
|
-
})),
|
|
157
|
-
});
|
|
149
|
+
next({ peers: state.peers });
|
|
158
150
|
});
|
|
159
151
|
|
|
160
152
|
this._automergeHost.collectionStateUpdated.on(ctx, (e) => {
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import type * as A from '@automerge/automerge';
|
|
6
|
-
import {
|
|
6
|
+
import { type AutomergeUrl, type DocHandle, type DocumentId, interpretAsDocumentId } from '@automerge/automerge-repo';
|
|
7
7
|
|
|
8
8
|
import { DatabaseDirectory, SpaceDocVersion } from '@dxos/echo-protocol';
|
|
9
9
|
import { invariant } from '@dxos/invariant';
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import { type DocMetrics, measureDocMetrics } from './automerge-metrics';
|
|
12
12
|
|
|
13
13
|
export class DatabaseRoot {
|
|
14
14
|
static mapLinks(doc: DocHandle<DatabaseDirectory>, mapping: Record<DocumentId, DocumentId>): void {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { next as A } from '@automerge/automerge';
|
|
6
|
-
import { generateAutomergeUrl, parseAutomergeUrl
|
|
6
|
+
import { Repo, generateAutomergeUrl, parseAutomergeUrl } from '@automerge/automerge-repo';
|
|
7
7
|
import { describe, expect, test } from 'vitest';
|
|
8
8
|
|
|
9
9
|
import { sleep } from '@dxos/async';
|
|
@@ -3,17 +3,15 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { next as A, type Heads } from '@automerge/automerge';
|
|
6
|
-
import { type
|
|
6
|
+
import { type DocHandle, type DocumentId, type Repo } from '@automerge/automerge-repo';
|
|
7
7
|
|
|
8
|
-
import { UpdateScheduler } from '@dxos/async';
|
|
9
|
-
import { Resource } from '@dxos/context';
|
|
8
|
+
import { UpdateScheduler, sleep } from '@dxos/async';
|
|
9
|
+
import { LifecycleState, Resource, cancelWithContext } from '@dxos/context';
|
|
10
10
|
import { type DatabaseDirectory } from '@dxos/echo-protocol';
|
|
11
11
|
import { invariant } from '@dxos/invariant';
|
|
12
12
|
import { log } from '@dxos/log';
|
|
13
13
|
import { type BatchedDocumentUpdates, type DocumentUpdate } from '@dxos/protocols/proto/dxos/echo/service';
|
|
14
14
|
|
|
15
|
-
import { FIND_PARAMS } from '../automerge';
|
|
16
|
-
|
|
17
15
|
const MAX_UPDATE_FREQ = 10; // [updates/sec]
|
|
18
16
|
|
|
19
17
|
export type DocumentsSynchronizerParams = {
|
|
@@ -27,6 +25,9 @@ interface DocSyncState {
|
|
|
27
25
|
clearSubscriptions?: () => void;
|
|
28
26
|
}
|
|
29
27
|
|
|
28
|
+
const WRAP_AROUND_RETRY_LIMIT = 3;
|
|
29
|
+
const WRAP_AROUND_RETRY_INITIAL_DELAY = 100; // [ms]
|
|
30
|
+
|
|
30
31
|
/**
|
|
31
32
|
* Manages a connection and replication between worker's Automerge Repo and the client's Repo.
|
|
32
33
|
*/
|
|
@@ -47,8 +48,12 @@ export class DocumentsSynchronizer extends Resource {
|
|
|
47
48
|
super();
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
addDocuments(
|
|
51
|
-
|
|
51
|
+
addDocuments(
|
|
52
|
+
documentIds: DocumentId[],
|
|
53
|
+
retryCounter = 0,
|
|
54
|
+
wrapAroundRetryDelay = WRAP_AROUND_RETRY_INITIAL_DELAY,
|
|
55
|
+
): void {
|
|
56
|
+
if (retryCounter > WRAP_AROUND_RETRY_LIMIT) {
|
|
52
57
|
log.warn('Failed to load document, retry limit reached', { documentIds });
|
|
53
58
|
return;
|
|
54
59
|
}
|
|
@@ -64,7 +69,9 @@ export class DocumentsSynchronizer extends Resource {
|
|
|
64
69
|
})
|
|
65
70
|
.catch((error) => {
|
|
66
71
|
log.warn('Failed to load document, wraparound', { documentId, error });
|
|
67
|
-
this.
|
|
72
|
+
void cancelWithContext(this._ctx, sleep(wrapAroundRetryDelay)).then(() =>
|
|
73
|
+
this.addDocuments([documentId], retryCounter + 1, wrapAroundRetryDelay * 2),
|
|
74
|
+
);
|
|
68
75
|
});
|
|
69
76
|
}
|
|
70
77
|
}
|
|
@@ -90,17 +97,13 @@ export class DocumentsSynchronizer extends Resource {
|
|
|
90
97
|
|
|
91
98
|
async update(updates: DocumentUpdate[]): Promise<void> {
|
|
92
99
|
for (const { documentId, mutation, isNew } of updates) {
|
|
93
|
-
|
|
94
|
-
const doc = await this._params.repo.find<DatabaseDirectory>(documentId as DocumentId, FIND_PARAMS);
|
|
95
|
-
doc.update((doc) => A.loadIncremental(doc, mutation));
|
|
96
|
-
this._startSync(doc);
|
|
97
|
-
} else {
|
|
98
|
-
this._writeMutation(documentId as DocumentId, mutation);
|
|
99
|
-
}
|
|
100
|
+
this._writeMutation(documentId as DocumentId, mutation, isNew);
|
|
100
101
|
}
|
|
102
|
+
// TODO(mykola): This should not be required.
|
|
103
|
+
await this._params.repo.flush(updates.map(({ documentId }) => documentId as DocumentId));
|
|
101
104
|
}
|
|
102
105
|
|
|
103
|
-
private _startSync(doc: DocHandle<DatabaseDirectory>)
|
|
106
|
+
private _startSync(doc: DocHandle<DatabaseDirectory>) {
|
|
104
107
|
if (this._syncStates.has(doc.documentId)) {
|
|
105
108
|
log('Document already being synced', { documentId: doc.documentId });
|
|
106
109
|
return;
|
|
@@ -109,6 +112,7 @@ export class DocumentsSynchronizer extends Resource {
|
|
|
109
112
|
const syncState: DocSyncState = { handle: doc };
|
|
110
113
|
this._subscribeForChanges(syncState);
|
|
111
114
|
this._syncStates.set(doc.documentId, syncState);
|
|
115
|
+
return syncState;
|
|
112
116
|
}
|
|
113
117
|
|
|
114
118
|
_subscribeForChanges(syncState: DocSyncState): void {
|
|
@@ -157,16 +161,25 @@ export class DocumentsSynchronizer extends Resource {
|
|
|
157
161
|
return mutation;
|
|
158
162
|
}
|
|
159
163
|
|
|
160
|
-
private _writeMutation(documentId: DocumentId, mutation: Uint8Array): void {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
164
|
+
private _writeMutation(documentId: DocumentId, mutation: Uint8Array, isNew?: boolean): void {
|
|
165
|
+
if (this._lifecycleState === LifecycleState.CLOSED) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (isNew) {
|
|
169
|
+
const newHandle = this._params.repo.import<DatabaseDirectory>(mutation, { docId: documentId });
|
|
170
|
+
const syncState = this._startSync(newHandle);
|
|
171
|
+
syncState!.lastSentHead = A.getHeads(newHandle.doc());
|
|
172
|
+
} else {
|
|
173
|
+
const syncState = this._syncStates.get(documentId);
|
|
174
|
+
invariant(syncState, 'Sync state for document not found');
|
|
175
|
+
const headsBefore = A.getHeads(syncState.handle.doc());
|
|
176
|
+
// This will update corresponding handle in the repo.
|
|
177
|
+
this._params.repo.import(mutation, { docId: documentId });
|
|
178
|
+
|
|
179
|
+
if (A.equals(headsBefore, syncState!.lastSentHead)) {
|
|
180
|
+
// No new mutations were discovered on network, so we do not need to send updates from worker to client.
|
|
181
|
+
syncState!.lastSentHead = A.getHeads(syncState.handle.doc());
|
|
168
182
|
}
|
|
169
|
-
|
|
170
|
-
});
|
|
183
|
+
}
|
|
171
184
|
}
|
|
172
185
|
}
|
package/src/db-host/echo-host.ts
CHANGED
|
@@ -10,9 +10,9 @@ import {
|
|
|
10
10
|
type Repo,
|
|
11
11
|
} from '@automerge/automerge-repo';
|
|
12
12
|
|
|
13
|
-
import { LifecycleState, Resource
|
|
13
|
+
import { type Context, LifecycleState, Resource } from '@dxos/context';
|
|
14
14
|
import { todo } from '@dxos/debug';
|
|
15
|
-
import {
|
|
15
|
+
import { type DatabaseDirectory, SpaceDocVersion, createIdFromSpaceKey } from '@dxos/echo-protocol';
|
|
16
16
|
import { IndexMetadataStore, IndexStore, Indexer } from '@dxos/indexing';
|
|
17
17
|
import { invariant } from '@dxos/invariant';
|
|
18
18
|
import { type PublicKey, type SpaceId } from '@dxos/keys';
|
|
@@ -20,24 +20,25 @@ import { type LevelDB } from '@dxos/kv-store';
|
|
|
20
20
|
import { IndexKind } from '@dxos/protocols/proto/dxos/echo/indexing';
|
|
21
21
|
import { trace } from '@dxos/tracing';
|
|
22
22
|
|
|
23
|
-
import { DataServiceImpl } from './data-service';
|
|
24
|
-
import { type DatabaseRoot } from './database-root';
|
|
25
|
-
import { createSelectedDocumentsIterator } from './documents-iterator';
|
|
26
|
-
import { QueryServiceImpl } from './query-service';
|
|
27
|
-
import { SpaceStateManager } from './space-state-manager';
|
|
28
23
|
import {
|
|
29
24
|
AutomergeHost,
|
|
30
|
-
FIND_PARAMS,
|
|
31
|
-
EchoDataMonitor,
|
|
32
|
-
deriveCollectionIdFromSpaceId,
|
|
33
|
-
type LoadDocOptions,
|
|
34
25
|
type CreateDocOptions,
|
|
35
|
-
|
|
26
|
+
EchoDataMonitor,
|
|
36
27
|
type EchoDataStats,
|
|
28
|
+
type EchoReplicator,
|
|
29
|
+
FIND_PARAMS,
|
|
30
|
+
type LoadDocOptions,
|
|
37
31
|
type PeerIdProvider,
|
|
38
32
|
type RootDocumentSpaceKeyProvider,
|
|
33
|
+
deriveCollectionIdFromSpaceId,
|
|
39
34
|
} from '../automerge';
|
|
40
35
|
|
|
36
|
+
import { DataServiceImpl } from './data-service';
|
|
37
|
+
import { type DatabaseRoot } from './database-root';
|
|
38
|
+
import { createSelectedDocumentsIterator } from './documents-iterator';
|
|
39
|
+
import { QueryServiceImpl } from './query-service';
|
|
40
|
+
import { SpaceStateManager } from './space-state-manager';
|
|
41
|
+
|
|
41
42
|
export interface EchoHostIndexingConfig {
|
|
42
43
|
/**
|
|
43
44
|
* @default true
|
|
@@ -197,8 +198,6 @@ export class EchoHost extends Resource {
|
|
|
197
198
|
if (e.previousRootId) {
|
|
198
199
|
void this._automergeHost.clearLocalCollectionState(deriveCollectionIdFromSpaceId(e.spaceId, e.previousRootId));
|
|
199
200
|
}
|
|
200
|
-
// TODO(yaroslav): remove collection without spaceRootId after release (production<->staging interop)
|
|
201
|
-
void this._automergeHost.updateLocalCollectionState(deriveCollectionIdFromSpaceId(e.spaceId), e.documentIds);
|
|
202
201
|
void this._automergeHost.updateLocalCollectionState(
|
|
203
202
|
deriveCollectionIdFromSpaceId(e.spaceId, e.spaceRootId),
|
|
204
203
|
e.documentIds,
|
|
@@ -23,10 +23,11 @@ import {
|
|
|
23
23
|
} from '@dxos/protocols/proto/dxos/echo/query';
|
|
24
24
|
import { trace } from '@dxos/tracing';
|
|
25
25
|
|
|
26
|
-
import type { SpaceStateManager } from './space-state-manager';
|
|
27
26
|
import { type AutomergeHost } from '../automerge';
|
|
28
27
|
import { QueryExecutor } from '../query';
|
|
29
28
|
|
|
29
|
+
import type { SpaceStateManager } from './space-state-manager';
|
|
30
|
+
|
|
30
31
|
export type QueryServiceParams = {
|
|
31
32
|
indexer: Indexer;
|
|
32
33
|
automergeHost: AutomergeHost;
|
|
@@ -96,6 +97,12 @@ export class QueryServiceImpl extends Resource implements QueryService {
|
|
|
96
97
|
|
|
97
98
|
execQuery(request: QueryRequest): Stream<QueryResponse> {
|
|
98
99
|
return new Stream<QueryResponse>(({ next, close, ctx }) => {
|
|
100
|
+
if (this._params.indexer.config?.enabled !== true) {
|
|
101
|
+
log.error('indexer is disabled', { config: this._params.indexer.config });
|
|
102
|
+
close();
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
99
106
|
const queryEntry = this._createQuery(ctx, request, next, close, close);
|
|
100
107
|
scheduleMicroTask(ctx, async () => {
|
|
101
108
|
await queryEntry.executor.open();
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { type DocHandle, type DocumentId, interpretAsDocumentId } from '@automerge/automerge-repo';
|
|
6
6
|
import isEqual from 'lodash.isequal';
|
|
7
7
|
|
|
8
8
|
import { Event, UpdateScheduler } from '@dxos/async';
|
|
9
|
-
import {
|
|
9
|
+
import { Context, LifecycleState, Resource } from '@dxos/context';
|
|
10
10
|
import { type DatabaseDirectory } from '@dxos/echo-protocol';
|
|
11
11
|
import { invariant } from '@dxos/invariant';
|
|
12
12
|
import { type SpaceId } from '@dxos/keys';
|
|
@@ -7,7 +7,7 @@ import { getRandomPort } from 'get-port-please';
|
|
|
7
7
|
import { describe, expect, onTestFinished, test } from 'vitest';
|
|
8
8
|
|
|
9
9
|
import { Event } from '@dxos/async';
|
|
10
|
-
import {
|
|
10
|
+
import { EdgeClient, type EdgeHttpClient, MessageSchema, createEphemeralEdgeIdentity } from '@dxos/edge-client';
|
|
11
11
|
import { createTestEdgeWsServer } from '@dxos/edge-client/testing';
|
|
12
12
|
import { PublicKey, SpaceId } from '@dxos/keys';
|
|
13
13
|
import { EdgeService } from '@dxos/protocols';
|
|
@@ -16,9 +16,10 @@ import { createBuf } from '@dxos/protocols/buf';
|
|
|
16
16
|
import type { Peer } from '@dxos/protocols/proto/dxos/edge/messenger';
|
|
17
17
|
import { openAndClose } from '@dxos/test-utils';
|
|
18
18
|
|
|
19
|
-
import { EchoEdgeReplicator } from './echo-edge-replicator';
|
|
20
19
|
import type { EchoReplicatorContext, ReplicatorConnection } from '../automerge';
|
|
21
20
|
|
|
21
|
+
import { EchoEdgeReplicator } from './echo-edge-replicator';
|
|
22
|
+
|
|
22
23
|
describe('EchoEdgeReplicator', () => {
|
|
23
24
|
test('reconnects', async () => {
|
|
24
25
|
const { client, server } = await createClientServer();
|
|
@@ -79,7 +80,8 @@ describe('EchoEdgeReplicator', () => {
|
|
|
79
80
|
});
|
|
80
81
|
|
|
81
82
|
const connectReplicator = async (client: EdgeClient, context: EchoReplicatorContext) => {
|
|
82
|
-
|
|
83
|
+
// EdgeHttpClient functionality is not tested here.
|
|
84
|
+
const replicator = new EchoEdgeReplicator({ edgeConnection: client, edgeHttpClient: {} as EdgeHttpClient });
|
|
83
85
|
await replicator.connect(context);
|
|
84
86
|
onTestFinished(() => replicator.disconnect());
|
|
85
87
|
return replicator;
|