@dxos/echo-pipeline 0.6.1 → 0.6.2-main.8a232a5
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-DMUP426Q.mjs → chunk-UJQ5VS5V.mjs} +383 -196
- package/dist/lib/browser/chunk-UJQ5VS5V.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +59 -562
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +1 -1
- package/dist/lib/node/{chunk-NH5WJKOW.cjs → chunk-RH6TDRML.cjs} +438 -256
- package/dist/lib/node/chunk-RH6TDRML.cjs.map +7 -0
- package/dist/lib/node/index.cjs +80 -581
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs +11 -11
- package/dist/types/src/automerge/automerge-host.d.ts +2 -17
- package/dist/types/src/automerge/automerge-host.d.ts.map +1 -1
- package/dist/types/src/automerge/echo-network-adapter.d.ts +1 -1
- package/dist/types/src/automerge/index.d.ts +0 -2
- package/dist/types/src/automerge/index.d.ts.map +1 -1
- package/dist/types/src/db-host/data-service.d.ts +10 -7
- package/dist/types/src/db-host/data-service.d.ts.map +1 -1
- package/dist/types/src/db-host/documents-synchronizer.d.ts +43 -0
- package/dist/types/src/db-host/documents-synchronizer.d.ts.map +1 -0
- package/dist/types/src/db-host/documents-synchronizer.test.d.ts +2 -0
- package/dist/types/src/db-host/documents-synchronizer.test.d.ts.map +1 -0
- package/dist/types/src/db-host/index.d.ts +1 -0
- package/dist/types/src/db-host/index.d.ts.map +1 -1
- package/package.json +33 -33
- package/src/automerge/automerge-host.ts +6 -56
- package/src/automerge/automerge-repo.test.ts +124 -1
- package/src/automerge/echo-network-adapter.ts +1 -1
- package/src/automerge/index.ts +0 -2
- package/src/db-host/data-service.ts +49 -25
- package/src/db-host/documents-synchronizer.test.ts +40 -0
- package/src/db-host/documents-synchronizer.ts +156 -0
- package/src/db-host/index.ts +1 -0
- package/dist/lib/browser/chunk-DMUP426Q.mjs.map +0 -7
- package/dist/lib/node/chunk-NH5WJKOW.cjs.map +0 -7
- package/dist/types/src/automerge/automerge-doc-loader.d.ts +0 -71
- package/dist/types/src/automerge/automerge-doc-loader.d.ts.map +0 -1
- package/dist/types/src/automerge/automerge-doc-loader.test.d.ts +0 -2
- package/dist/types/src/automerge/automerge-doc-loader.test.d.ts.map +0 -1
- package/dist/types/src/automerge/local-host-network-adapter.d.ts +0 -30
- package/dist/types/src/automerge/local-host-network-adapter.d.ts.map +0 -1
- package/src/automerge/automerge-doc-loader.test.ts +0 -103
- package/src/automerge/automerge-doc-loader.ts +0 -267
- package/src/automerge/local-host-network-adapter.ts +0 -115
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/echo-pipeline",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2-main.8a232a5",
|
|
4
4
|
"description": "ECHO database.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -39,38 +39,38 @@
|
|
|
39
39
|
"crc-32": "^1.2.2",
|
|
40
40
|
"level": "^8.0.1",
|
|
41
41
|
"level-transcoder": "^1.0.1",
|
|
42
|
-
"@dxos/
|
|
43
|
-
"@dxos/
|
|
44
|
-
"@dxos/
|
|
45
|
-
"@dxos/
|
|
46
|
-
"@dxos/
|
|
47
|
-
"@dxos/
|
|
48
|
-
"@dxos/echo-
|
|
49
|
-
"@dxos/
|
|
50
|
-
"@dxos/echo-
|
|
51
|
-
"@dxos/
|
|
52
|
-
"@dxos/
|
|
53
|
-
"@dxos/indexing": "0.6.
|
|
54
|
-
"@dxos/
|
|
55
|
-
"@dxos/keyring": "0.6.
|
|
56
|
-
"@dxos/keys": "0.6.
|
|
57
|
-
"@dxos/kv-store": "0.6.
|
|
58
|
-
"@dxos/
|
|
59
|
-
"@dxos/
|
|
60
|
-
"@dxos/network-manager": "0.6.
|
|
61
|
-
"@dxos/
|
|
62
|
-
"@dxos/
|
|
63
|
-
"@dxos/
|
|
64
|
-
"@dxos/
|
|
65
|
-
"@dxos/teleport": "0.6.
|
|
66
|
-
"@dxos/teleport-extension-
|
|
67
|
-
"@dxos/teleport-extension-
|
|
68
|
-
"@dxos/teleport-extension-
|
|
69
|
-
"@dxos/teleport-extension-
|
|
70
|
-
"@dxos/timeframe": "0.6.
|
|
71
|
-
"@dxos/tracing": "0.6.
|
|
72
|
-
"@dxos/typings": "0.6.
|
|
73
|
-
"@dxos/util": "0.6.
|
|
42
|
+
"@dxos/async": "0.6.2-main.8a232a5",
|
|
43
|
+
"@dxos/codec-protobuf": "0.6.2-main.8a232a5",
|
|
44
|
+
"@dxos/automerge": "0.6.2-main.8a232a5",
|
|
45
|
+
"@dxos/context": "0.6.2-main.8a232a5",
|
|
46
|
+
"@dxos/credentials": "0.6.2-main.8a232a5",
|
|
47
|
+
"@dxos/crypto": "0.6.2-main.8a232a5",
|
|
48
|
+
"@dxos/echo-protocol": "0.6.2-main.8a232a5",
|
|
49
|
+
"@dxos/debug": "0.6.2-main.8a232a5",
|
|
50
|
+
"@dxos/echo-schema": "0.6.2-main.8a232a5",
|
|
51
|
+
"@dxos/feed-store": "0.6.2-main.8a232a5",
|
|
52
|
+
"@dxos/hypercore": "0.6.2-main.8a232a5",
|
|
53
|
+
"@dxos/indexing": "0.6.2-main.8a232a5",
|
|
54
|
+
"@dxos/invariant": "0.6.2-main.8a232a5",
|
|
55
|
+
"@dxos/keyring": "0.6.2-main.8a232a5",
|
|
56
|
+
"@dxos/keys": "0.6.2-main.8a232a5",
|
|
57
|
+
"@dxos/kv-store": "0.6.2-main.8a232a5",
|
|
58
|
+
"@dxos/messaging": "0.6.2-main.8a232a5",
|
|
59
|
+
"@dxos/log": "0.6.2-main.8a232a5",
|
|
60
|
+
"@dxos/network-manager": "0.6.2-main.8a232a5",
|
|
61
|
+
"@dxos/protocols": "0.6.2-main.8a232a5",
|
|
62
|
+
"@dxos/node-std": "0.6.2-main.8a232a5",
|
|
63
|
+
"@dxos/rpc": "0.6.2-main.8a232a5",
|
|
64
|
+
"@dxos/random-access-storage": "0.6.2-main.8a232a5",
|
|
65
|
+
"@dxos/teleport": "0.6.2-main.8a232a5",
|
|
66
|
+
"@dxos/teleport-extension-gossip": "0.6.2-main.8a232a5",
|
|
67
|
+
"@dxos/teleport-extension-automerge-replicator": "0.6.2-main.8a232a5",
|
|
68
|
+
"@dxos/teleport-extension-replicator": "0.6.2-main.8a232a5",
|
|
69
|
+
"@dxos/teleport-extension-object-sync": "0.6.2-main.8a232a5",
|
|
70
|
+
"@dxos/timeframe": "0.6.2-main.8a232a5",
|
|
71
|
+
"@dxos/tracing": "0.6.2-main.8a232a5",
|
|
72
|
+
"@dxos/typings": "0.6.2-main.8a232a5",
|
|
73
|
+
"@dxos/util": "0.6.2-main.8a232a5"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
76
|
"fast-check": "^3.19.0",
|
|
@@ -8,21 +8,20 @@ import {
|
|
|
8
8
|
getBackend,
|
|
9
9
|
getHeads,
|
|
10
10
|
isAutomerge,
|
|
11
|
-
save,
|
|
12
11
|
equals as headsEquals,
|
|
12
|
+
save,
|
|
13
13
|
type Doc,
|
|
14
14
|
type Heads,
|
|
15
15
|
} from '@dxos/automerge/automerge';
|
|
16
16
|
import {
|
|
17
|
+
type DocHandleChangePayload,
|
|
17
18
|
Repo,
|
|
18
19
|
type AnyDocumentId,
|
|
19
20
|
type DocHandle,
|
|
20
|
-
type DocHandleChangePayload,
|
|
21
21
|
type DocumentId,
|
|
22
22
|
type PeerId,
|
|
23
23
|
type StorageAdapterInterface,
|
|
24
24
|
} from '@dxos/automerge/automerge-repo';
|
|
25
|
-
import { type Stream } from '@dxos/codec-protobuf';
|
|
26
25
|
import { Context, Resource, cancelWithContext, type Lifecycle } from '@dxos/context';
|
|
27
26
|
import { type SpaceDoc } from '@dxos/echo-protocol';
|
|
28
27
|
import { type IndexMetadataStore } from '@dxos/indexing';
|
|
@@ -31,13 +30,7 @@ import { PublicKey } from '@dxos/keys';
|
|
|
31
30
|
import { type LevelDB } from '@dxos/kv-store';
|
|
32
31
|
import { log } from '@dxos/log';
|
|
33
32
|
import { objectPointerCodec } from '@dxos/protocols';
|
|
34
|
-
import {
|
|
35
|
-
type DocHeadsList,
|
|
36
|
-
type FlushRequest,
|
|
37
|
-
type HostInfo,
|
|
38
|
-
type SyncRepoRequest,
|
|
39
|
-
type SyncRepoResponse,
|
|
40
|
-
} from '@dxos/protocols/proto/dxos/echo/service';
|
|
33
|
+
import { type DocHeadsList, type FlushRequest } from '@dxos/protocols/proto/dxos/echo/service';
|
|
41
34
|
import { trace } from '@dxos/tracing';
|
|
42
35
|
import { mapValues } from '@dxos/util';
|
|
43
36
|
|
|
@@ -45,10 +38,6 @@ import { EchoNetworkAdapter, isEchoPeerMetadata } from './echo-network-adapter';
|
|
|
45
38
|
import { type EchoReplicator } from './echo-replicator';
|
|
46
39
|
import { HeadsStore } from './heads-store';
|
|
47
40
|
import { LevelDBStorageAdapter, type BeforeSaveParams } from './leveldb-storage-adapter';
|
|
48
|
-
import { LocalHostNetworkAdapter } from './local-host-network-adapter';
|
|
49
|
-
|
|
50
|
-
// TODO: Remove
|
|
51
|
-
export type { DocumentId };
|
|
52
41
|
|
|
53
42
|
export type AutomergeHostParams = {
|
|
54
43
|
db: LevelDB;
|
|
@@ -79,7 +68,6 @@ export class AutomergeHost extends Resource {
|
|
|
79
68
|
});
|
|
80
69
|
|
|
81
70
|
private _repo!: Repo;
|
|
82
|
-
private _clientNetwork!: LocalHostNetworkAdapter;
|
|
83
71
|
private _storage!: StorageAdapterInterface & Lifecycle;
|
|
84
72
|
private readonly _headsStore: HeadsStore;
|
|
85
73
|
|
|
@@ -105,7 +93,6 @@ export class AutomergeHost extends Resource {
|
|
|
105
93
|
this._peerId = `host-${PublicKey.random().toHex()}` as PeerId;
|
|
106
94
|
|
|
107
95
|
await this._storage.open?.();
|
|
108
|
-
this._clientNetwork = new LocalHostNetworkAdapter();
|
|
109
96
|
|
|
110
97
|
// Construct the automerge repo.
|
|
111
98
|
this._repo = new Repo({
|
|
@@ -113,22 +100,17 @@ export class AutomergeHost extends Resource {
|
|
|
113
100
|
sharePolicy: this._sharePolicy.bind(this),
|
|
114
101
|
storage: this._storage,
|
|
115
102
|
network: [
|
|
116
|
-
// Downstream client.
|
|
117
|
-
this._clientNetwork,
|
|
118
103
|
// Upstream swarm.
|
|
119
104
|
this._echoNetworkAdapter,
|
|
120
105
|
],
|
|
121
106
|
});
|
|
122
107
|
|
|
123
|
-
this._clientNetwork.ready();
|
|
124
108
|
await this._echoNetworkAdapter.open();
|
|
125
|
-
await this._clientNetwork.whenConnected();
|
|
126
109
|
await this._echoNetworkAdapter.whenConnected();
|
|
127
110
|
}
|
|
128
111
|
|
|
129
112
|
protected override async _close() {
|
|
130
113
|
await this._storage.close?.();
|
|
131
|
-
await this._clientNetwork.close();
|
|
132
114
|
await this._echoNetworkAdapter.close();
|
|
133
115
|
await this._ctx.dispose();
|
|
134
116
|
}
|
|
@@ -335,21 +317,10 @@ export class AutomergeHost extends Resource {
|
|
|
335
317
|
* Flush documents to disk.
|
|
336
318
|
*/
|
|
337
319
|
@trace.span({ showInBrowserTimeline: true })
|
|
338
|
-
async flush({
|
|
339
|
-
// Note:
|
|
340
|
-
if (states) {
|
|
341
|
-
await Promise.all(
|
|
342
|
-
states.map(async ({ heads, documentId }) => {
|
|
343
|
-
if (!heads) {
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
const handle = this._repo.handles[documentId as DocumentId] ?? this._repo.find(documentId as DocumentId);
|
|
347
|
-
await waitForHeads(handle, heads);
|
|
348
|
-
}) ?? [],
|
|
349
|
-
);
|
|
350
|
-
}
|
|
320
|
+
async flush({ documentIds }: FlushRequest = {}): Promise<void> {
|
|
321
|
+
// Note: Sync protocol for client and services ensures that all handles should have all changes.
|
|
351
322
|
|
|
352
|
-
await this._repo.flush(
|
|
323
|
+
await this._repo.flush(documentIds as DocumentId[] | undefined);
|
|
353
324
|
}
|
|
354
325
|
|
|
355
326
|
async getHeads(documentId: DocumentId): Promise<Heads | undefined> {
|
|
@@ -364,27 +335,6 @@ export class AutomergeHost extends Resource {
|
|
|
364
335
|
return this._headsStore.getHeads(documentId);
|
|
365
336
|
}
|
|
366
337
|
}
|
|
367
|
-
|
|
368
|
-
/**
|
|
369
|
-
* Host <-> Client sync.
|
|
370
|
-
*/
|
|
371
|
-
syncRepo(request: SyncRepoRequest): Stream<SyncRepoResponse> {
|
|
372
|
-
return this._clientNetwork.syncRepo(request);
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
/**
|
|
376
|
-
* Host <-> Client sync.
|
|
377
|
-
*/
|
|
378
|
-
sendSyncMessage(request: SyncRepoRequest): Promise<void> {
|
|
379
|
-
return this._clientNetwork.sendSyncMessage(request);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
/**
|
|
383
|
-
* Host <-> Client sync.
|
|
384
|
-
*/
|
|
385
|
-
async getHostInfo(): Promise<HostInfo> {
|
|
386
|
-
return this._clientNetwork.getHostInfo();
|
|
387
|
-
}
|
|
388
338
|
}
|
|
389
339
|
|
|
390
340
|
export const getSpaceKeyFromDoc = (doc: Doc<SpaceDoc>): string | null => {
|
|
@@ -6,7 +6,18 @@ import { expect } from 'chai';
|
|
|
6
6
|
import waitForExpect from 'wait-for-expect';
|
|
7
7
|
|
|
8
8
|
import { asyncTimeout, sleep } from '@dxos/async';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
next as A,
|
|
11
|
+
type Heads,
|
|
12
|
+
change,
|
|
13
|
+
clone,
|
|
14
|
+
equals,
|
|
15
|
+
from,
|
|
16
|
+
getBackend,
|
|
17
|
+
getHeads,
|
|
18
|
+
save,
|
|
19
|
+
saveSince,
|
|
20
|
+
} from '@dxos/automerge/automerge';
|
|
10
21
|
import {
|
|
11
22
|
type Message,
|
|
12
23
|
Repo,
|
|
@@ -14,6 +25,8 @@ import {
|
|
|
14
25
|
type DocumentId,
|
|
15
26
|
type HandleState,
|
|
16
27
|
type AutomergeUrl,
|
|
28
|
+
parseAutomergeUrl,
|
|
29
|
+
generateAutomergeUrl,
|
|
17
30
|
} from '@dxos/automerge/automerge-repo';
|
|
18
31
|
import { randomBytes } from '@dxos/crypto';
|
|
19
32
|
import { PublicKey } from '@dxos/keys';
|
|
@@ -630,5 +643,115 @@ describe('AutomergeRepo', () => {
|
|
|
630
643
|
expect(peer2.handles[hostHandle.documentId]).to.not.be.undefined;
|
|
631
644
|
});
|
|
632
645
|
});
|
|
646
|
+
|
|
647
|
+
test('client cold-starts and syncs doc from a Repo', async () => {
|
|
648
|
+
const repo = new Repo({ network: [] });
|
|
649
|
+
const serverHandle = repo.create<{ field?: string }>();
|
|
650
|
+
|
|
651
|
+
let clientDoc = A.from<{ field?: string }>({});
|
|
652
|
+
const receiveByClient = (blob: Uint8Array) => {
|
|
653
|
+
clientDoc = A.loadIncremental(clientDoc, blob);
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
// Sync handshake.
|
|
657
|
+
let syncedHeads = getHeads(serverHandle.docSync());
|
|
658
|
+
receiveByClient(save(serverHandle.docSync()));
|
|
659
|
+
|
|
660
|
+
serverHandle.on('change', ({ doc }) => {
|
|
661
|
+
// Note: This is mock of a sync protocol between client and server.
|
|
662
|
+
const blob = saveSince(doc, syncedHeads);
|
|
663
|
+
syncedHeads = getHeads(doc);
|
|
664
|
+
receiveByClient(blob);
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
{
|
|
668
|
+
const value = 'text to test if sync works';
|
|
669
|
+
serverHandle.change((doc: any) => {
|
|
670
|
+
doc.field = value;
|
|
671
|
+
});
|
|
672
|
+
expect(clientDoc.field).to.deep.equal(value);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
{
|
|
676
|
+
const value = 'test if updates propagate';
|
|
677
|
+
serverHandle.change((doc: any) => {
|
|
678
|
+
doc.field = value;
|
|
679
|
+
});
|
|
680
|
+
expect(clientDoc.field).to.deep.equal(value);
|
|
681
|
+
}
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
test('client creates doc and syncs with a Repo', async () => {
|
|
685
|
+
const repo = new Repo({ network: [] });
|
|
686
|
+
const receiveByServer = async (blob: Uint8Array, docId: DocumentId) => {
|
|
687
|
+
const serverHandle = repo.find(docId);
|
|
688
|
+
serverHandle.update((doc) => {
|
|
689
|
+
return A.loadIncremental(doc, blob);
|
|
690
|
+
});
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
let clientDoc = A.from<{ field?: string }>({});
|
|
694
|
+
const { documentId } = parseAutomergeUrl(generateAutomergeUrl());
|
|
695
|
+
// Sync handshake.
|
|
696
|
+
let sentHeads = getHeads(clientDoc);
|
|
697
|
+
|
|
698
|
+
// Sync protocol.
|
|
699
|
+
const sendDoc = async (doc: A.Doc<any>) => {
|
|
700
|
+
await receiveByServer(saveSince(doc, sentHeads), documentId);
|
|
701
|
+
sentHeads = getHeads(doc);
|
|
702
|
+
};
|
|
703
|
+
|
|
704
|
+
{
|
|
705
|
+
// Change doc and send changes to server.
|
|
706
|
+
const value = 'text to test if sync works';
|
|
707
|
+
clientDoc = A.change(clientDoc, (doc: any) => {
|
|
708
|
+
doc.field = value;
|
|
709
|
+
});
|
|
710
|
+
await sendDoc(clientDoc);
|
|
711
|
+
|
|
712
|
+
await repo.find(documentId).whenReady();
|
|
713
|
+
expect(repo.find(documentId).docSync().field).to.deep.equal(value);
|
|
714
|
+
}
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
test('two repo sync docs on `update` call', async () => {
|
|
718
|
+
const [adapter1, adapter2] = TestAdapter.createPair();
|
|
719
|
+
const repoA = new Repo({
|
|
720
|
+
peerId: 'A' as any,
|
|
721
|
+
network: [adapter1],
|
|
722
|
+
sharePolicy: async () => true,
|
|
723
|
+
});
|
|
724
|
+
const repoB = new Repo({
|
|
725
|
+
peerId: 'B' as any,
|
|
726
|
+
network: [adapter2],
|
|
727
|
+
sharePolicy: async () => true,
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
{
|
|
731
|
+
// Connect repos.
|
|
732
|
+
adapter1.ready();
|
|
733
|
+
adapter2.ready();
|
|
734
|
+
await adapter1.onConnect.wait();
|
|
735
|
+
await adapter2.onConnect.wait();
|
|
736
|
+
adapter1.peerCandidate(adapter2.peerId!);
|
|
737
|
+
adapter2.peerCandidate(adapter1.peerId!);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
const handleA = repoA.create();
|
|
741
|
+
const handleB = repoB.find(handleA.url);
|
|
742
|
+
|
|
743
|
+
const text = 'Hello world';
|
|
744
|
+
handleA.update((doc: any) => {
|
|
745
|
+
const newDoc = A.change(doc, (doc: any) => {
|
|
746
|
+
doc.text = text;
|
|
747
|
+
});
|
|
748
|
+
return newDoc;
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
expect(handleA.docSync().text).to.equal(text);
|
|
752
|
+
|
|
753
|
+
await asyncTimeout(handleB.whenReady(), 1000);
|
|
754
|
+
expect(handleB.docSync().text).to.equal(text);
|
|
755
|
+
});
|
|
633
756
|
});
|
|
634
757
|
});
|
package/src/automerge/index.ts
CHANGED
|
@@ -2,25 +2,26 @@
|
|
|
2
2
|
// Copyright 2021 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { type
|
|
5
|
+
import { type DocumentId } from '@dxos/automerge/automerge-repo';
|
|
6
|
+
import { type RequestOptions, Stream } from '@dxos/codec-protobuf';
|
|
7
|
+
import { invariant } from '@dxos/invariant';
|
|
8
|
+
import { log } from '@dxos/log';
|
|
6
9
|
import {
|
|
7
10
|
type DataService,
|
|
8
11
|
type DocHeadsList,
|
|
9
|
-
type EchoEvent,
|
|
10
12
|
type FlushRequest,
|
|
13
|
+
type SubscribeRequest,
|
|
14
|
+
type BatchedDocumentUpdates,
|
|
15
|
+
type UpdateSubscriptionRequest,
|
|
11
16
|
type GetDocumentHeadsRequest,
|
|
12
17
|
type GetDocumentHeadsResponse,
|
|
13
|
-
type HostInfo,
|
|
14
|
-
type MutationReceipt,
|
|
15
18
|
type ReIndexHeadsRequest,
|
|
16
|
-
type SubscribeRequest,
|
|
17
|
-
type SyncRepoRequest,
|
|
18
|
-
type SyncRepoResponse,
|
|
19
19
|
type WaitUntilHeadsReplicatedRequest,
|
|
20
|
-
type
|
|
20
|
+
type UpdateRequest,
|
|
21
21
|
} from '@dxos/protocols/proto/dxos/echo/service';
|
|
22
22
|
|
|
23
|
-
import {
|
|
23
|
+
import { DocumentsSynchronizer } from './documents-synchronizer';
|
|
24
|
+
import { type AutomergeHost } from '../automerge';
|
|
24
25
|
|
|
25
26
|
export type DataServiceParams = {
|
|
26
27
|
automergeHost: AutomergeHost;
|
|
@@ -32,6 +33,12 @@ export type DataServiceParams = {
|
|
|
32
33
|
*/
|
|
33
34
|
// TODO(burdon): Move to client-services.
|
|
34
35
|
export class DataServiceImpl implements DataService {
|
|
36
|
+
/**
|
|
37
|
+
* Map of subscriptions.
|
|
38
|
+
* subscriptionId -> DocumentsSynchronizer
|
|
39
|
+
*/
|
|
40
|
+
private readonly _subscriptions = new Map<string, DocumentsSynchronizer>();
|
|
41
|
+
|
|
35
42
|
private readonly _automergeHost: AutomergeHost;
|
|
36
43
|
private readonly _updateIndexes: () => Promise<void>;
|
|
37
44
|
|
|
@@ -40,30 +47,47 @@ export class DataServiceImpl implements DataService {
|
|
|
40
47
|
this._updateIndexes = params.updateIndexes;
|
|
41
48
|
}
|
|
42
49
|
|
|
43
|
-
subscribe(request: SubscribeRequest): Stream<
|
|
44
|
-
|
|
50
|
+
subscribe(request: SubscribeRequest): Stream<BatchedDocumentUpdates> {
|
|
51
|
+
return new Stream<BatchedDocumentUpdates>(({ next, ready }) => {
|
|
52
|
+
const synchronizer = new DocumentsSynchronizer({
|
|
53
|
+
repo: this._automergeHost.repo,
|
|
54
|
+
sendUpdates: (updates) => next(updates),
|
|
55
|
+
});
|
|
56
|
+
synchronizer
|
|
57
|
+
.open()
|
|
58
|
+
.then(() => {
|
|
59
|
+
this._subscriptions.set(request.subscriptionId, synchronizer);
|
|
60
|
+
ready();
|
|
61
|
+
})
|
|
62
|
+
.catch((err) => log.catch(err));
|
|
63
|
+
return () => synchronizer.close();
|
|
64
|
+
});
|
|
45
65
|
}
|
|
46
66
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
67
|
+
async updateSubscription(request: UpdateSubscriptionRequest) {
|
|
68
|
+
const synchronizer = this._subscriptions.get(request.subscriptionId);
|
|
69
|
+
invariant(synchronizer, 'Subscription not found');
|
|
50
70
|
|
|
51
|
-
|
|
52
|
-
|
|
71
|
+
if (request.addIds?.length) {
|
|
72
|
+
await synchronizer.addDocuments(request.addIds as DocumentId[]);
|
|
73
|
+
}
|
|
74
|
+
if (request.removeIds?.length) {
|
|
75
|
+
await synchronizer.removeDocuments(request.removeIds as DocumentId[]);
|
|
76
|
+
}
|
|
53
77
|
}
|
|
54
78
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
79
|
+
async update(request: UpdateRequest): Promise<void> {
|
|
80
|
+
if (!request.updates) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const synchronizer = this._subscriptions.get(request.subscriptionId);
|
|
84
|
+
invariant(synchronizer, 'Subscription not found');
|
|
60
85
|
|
|
61
|
-
|
|
62
|
-
return this._automergeHost.syncRepo(request);
|
|
86
|
+
synchronizer.update(request.updates);
|
|
63
87
|
}
|
|
64
88
|
|
|
65
|
-
|
|
66
|
-
|
|
89
|
+
async flush(request: FlushRequest): Promise<void> {
|
|
90
|
+
await this._automergeHost.flush(request);
|
|
67
91
|
}
|
|
68
92
|
|
|
69
93
|
async getDocumentHeads(request: GetDocumentHeadsRequest): Promise<GetDocumentHeadsResponse> {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { expect } from 'chai';
|
|
6
|
+
|
|
7
|
+
import { sleep } from '@dxos/async';
|
|
8
|
+
import { next as A } from '@dxos/automerge/automerge';
|
|
9
|
+
import { generateAutomergeUrl, parseAutomergeUrl, Repo } from '@dxos/automerge/automerge-repo';
|
|
10
|
+
import { describe, openAndClose, test } from '@dxos/test';
|
|
11
|
+
|
|
12
|
+
import { DocumentsSynchronizer } from './documents-synchronizer';
|
|
13
|
+
|
|
14
|
+
describe('DocumentsSynchronizer', () => {
|
|
15
|
+
test('do not get init changes for client created docs', async () => {
|
|
16
|
+
let counter = 0;
|
|
17
|
+
const serverRepo = new Repo({ network: [] });
|
|
18
|
+
const synchronizer = new DocumentsSynchronizer({
|
|
19
|
+
repo: serverRepo,
|
|
20
|
+
sendUpdates: () => {
|
|
21
|
+
counter++;
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
await openAndClose(synchronizer);
|
|
25
|
+
|
|
26
|
+
synchronizer.update([
|
|
27
|
+
{
|
|
28
|
+
documentId: parseAutomergeUrl(generateAutomergeUrl()).documentId,
|
|
29
|
+
mutation: A.save(A.from({ text: 'hello' })),
|
|
30
|
+
isNew: true,
|
|
31
|
+
},
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
// Wait for the changes to be processed.
|
|
35
|
+
await sleep(100);
|
|
36
|
+
|
|
37
|
+
// No updates should be sent.
|
|
38
|
+
expect(counter).to.eq(0);
|
|
39
|
+
});
|
|
40
|
+
});
|