@dxos/echo-pipeline 0.8.4-main.ae835ea → 0.8.4-main.bc674ce
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-WJJXJTNS.mjs → chunk-FJPXA75J.mjs} +7 -12
- package/dist/lib/browser/chunk-FJPXA75J.mjs.map +7 -0
- package/dist/lib/browser/{chunk-FR6GJ4S4.mjs → chunk-WIQUYO7H.mjs} +39 -29
- package/dist/lib/browser/chunk-WIQUYO7H.mjs.map +7 -0
- package/dist/lib/browser/filter/index.mjs +1 -2
- package/dist/lib/browser/index.mjs +1933 -620
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +28 -201
- package/dist/lib/browser/testing/index.mjs.map +4 -4
- package/dist/lib/node-esm/{chunk-JQCCVDLT.mjs → chunk-7PL6UK4B.mjs} +39 -29
- package/dist/lib/node-esm/chunk-7PL6UK4B.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-LEQ77KAT.mjs → chunk-S5KGVQFZ.mjs} +7 -12
- package/dist/lib/node-esm/chunk-S5KGVQFZ.mjs.map +7 -0
- package/dist/lib/node-esm/filter/index.mjs +1 -2
- package/dist/lib/node-esm/index.mjs +1933 -620
- 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 +28 -201
- package/dist/lib/node-esm/testing/index.mjs.map +4 -4
- package/dist/types/src/automerge/automerge-host.d.ts +36 -9
- package/dist/types/src/automerge/automerge-host.d.ts.map +1 -1
- 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/collection-synchronizer.d.ts +3 -2
- package/dist/types/src/automerge/collection-synchronizer.d.ts.map +1 -1
- package/dist/types/src/automerge/echo-network-adapter.d.ts +11 -6
- package/dist/types/src/automerge/echo-network-adapter.d.ts.map +1 -1
- package/dist/types/src/automerge/echo-replicator.d.ts +9 -6
- package/dist/types/src/automerge/echo-replicator.d.ts.map +1 -1
- 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/leveldb-storage-adapter.d.ts +4 -4
- package/dist/types/src/automerge/leveldb-storage-adapter.d.ts.map +1 -1
- package/dist/types/src/automerge/mesh-echo-replicator-connection.d.ts +7 -7
- 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/db-host/automerge-data-source.d.ts +34 -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/data-service.d.ts +4 -3
- package/dist/types/src/db-host/data-service.d.ts.map +1 -1
- package/dist/types/src/db-host/documents-iterator.d.ts.map +1 -1
- package/dist/types/src/db-host/documents-synchronizer.d.ts +6 -5
- package/dist/types/src/db-host/documents-synchronizer.d.ts.map +1 -1
- package/dist/types/src/db-host/echo-host.d.ts +43 -11
- package/dist/types/src/db-host/echo-host.d.ts.map +1 -1
- package/dist/types/src/db-host/index.d.ts +1 -0
- package/dist/types/src/db-host/index.d.ts.map +1 -1
- package/dist/types/src/db-host/local-queue-service.d.ts +15 -0
- package/dist/types/src/db-host/local-queue-service.d.ts.map +1 -0
- package/dist/types/src/db-host/query-service.d.ts +7 -2
- package/dist/types/src/db-host/query-service.d.ts.map +1 -1
- package/dist/types/src/db-host/queue-data-source.d.ts +28 -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 +1 -0
- package/dist/types/src/db-host/space-state-manager.d.ts.map +1 -1
- package/dist/types/src/db-host/stub.d.ts +10 -0
- package/dist/types/src/db-host/stub.d.ts.map +1 -0
- package/dist/types/src/edge/echo-edge-replicator.d.ts +2 -2
- package/dist/types/src/edge/echo-edge-replicator.d.ts.map +1 -1
- package/dist/types/src/filter/filter-match.d.ts.map +1 -1
- package/dist/types/src/metadata/metadata-store.d.ts.map +1 -1
- package/dist/types/src/pipeline/pipeline.d.ts +2 -2
- package/dist/types/src/pipeline/pipeline.d.ts.map +1 -1
- package/dist/types/src/query/errors.d.ts +11 -15
- package/dist/types/src/query/errors.d.ts.map +1 -1
- package/dist/types/src/query/plan.d.ts +37 -3
- package/dist/types/src/query/plan.d.ts.map +1 -1
- package/dist/types/src/query/query-executor.d.ts +39 -3
- package/dist/types/src/query/query-executor.d.ts.map +1 -1
- package/dist/types/src/query/query-planner.d.ts +6 -0
- package/dist/types/src/query/query-planner.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 +2 -2
- package/dist/types/src/space/control-pipeline.d.ts.map +1 -1
- package/dist/types/src/space/space-manager.d.ts +6 -6
- package/dist/types/src/space/space-manager.d.ts.map +1 -1
- package/dist/types/src/space/space-protocol.d.ts +5 -5
- package/dist/types/src/space/space-protocol.d.ts.map +1 -1
- package/dist/types/src/space/space.d.ts +3 -3
- package/dist/types/src/space/space.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-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 +6 -6
- package/dist/types/src/testing/test-replicator.d.ts.map +1 -1
- package/dist/types/src/util.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +48 -38
- package/src/automerge/automerge-host.test.ts +37 -14
- package/src/automerge/automerge-host.ts +130 -36
- package/src/automerge/automerge-repo.test.ts +147 -30
- package/src/automerge/collection-synchronizer.test.ts +16 -10
- package/src/automerge/collection-synchronizer.ts +41 -11
- package/src/automerge/echo-network-adapter.ts +23 -11
- package/src/automerge/echo-replicator.ts +10 -6
- package/src/automerge/heads-store.ts +14 -2
- package/src/automerge/leveldb-storage-adapter.ts +4 -4
- package/src/automerge/mesh-echo-replicator-connection.ts +7 -7
- package/src/automerge/mesh-echo-replicator.ts +2 -2
- package/src/db-host/automerge-data-source.test.ts +294 -0
- package/src/db-host/automerge-data-source.ts +144 -0
- package/src/db-host/data-service.ts +10 -3
- package/src/db-host/documents-iterator.ts +3 -1
- package/src/db-host/documents-synchronizer.test.ts +77 -13
- package/src/db-host/documents-synchronizer.ts +51 -50
- package/src/db-host/echo-host.ts +191 -28
- package/src/db-host/index.ts +1 -0
- package/src/db-host/local-queue-service.ts +127 -0
- package/src/db-host/query-service.ts +17 -4
- package/src/db-host/queue-data-source.ts +132 -0
- package/src/db-host/queue-service.test.ts +141 -0
- package/src/db-host/space-state-manager.ts +4 -0
- package/src/db-host/stub.ts +28 -0
- package/src/edge/echo-edge-replicator.ts +12 -8
- package/src/filter/filter-match.test.ts +31 -25
- package/src/filter/filter-match.ts +8 -14
- package/src/metadata/metadata-store.ts +5 -2
- package/src/pipeline/pipeline.ts +2 -2
- package/src/query/errors.ts +2 -2
- package/src/query/plan.ts +48 -3
- package/src/query/query-executor.ts +612 -131
- package/src/query/query-planner.test.ts +509 -35
- package/src/query/query-planner.ts +187 -13
- package/src/space/admission-discovery-extension.ts +1 -1
- package/src/space/auth.ts +6 -6
- package/src/space/control-pipeline.ts +2 -2
- package/src/space/space-manager.ts +6 -6
- package/src/space/space-protocol.ts +11 -11
- package/src/space/space.ts +3 -3
- package/src/testing/index.ts +0 -2
- package/src/testing/test-network-adapter.ts +16 -3
- package/src/testing/test-replicator.ts +8 -7
- package/src/util.ts +7 -3
- package/dist/lib/browser/chunk-CGS2ULMK.mjs +0 -11
- package/dist/lib/browser/chunk-CGS2ULMK.mjs.map +0 -7
- package/dist/lib/browser/chunk-FR6GJ4S4.mjs.map +0 -7
- package/dist/lib/browser/chunk-WJJXJTNS.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-JQCCVDLT.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-LEQ77KAT.mjs.map +0 -7
- 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/testing/test-data.ts +0 -127
- package/src/testing/test-schema.ts +0 -53
package/package.json
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/echo-pipeline",
|
|
3
|
-
"version": "0.8.4-main.
|
|
3
|
+
"version": "0.8.4-main.bc674ce",
|
|
4
4
|
"description": "ECHO database.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/dxos/dxos"
|
|
10
|
+
},
|
|
7
11
|
"license": "MIT",
|
|
8
12
|
"author": "info@dxos.org",
|
|
9
|
-
"sideEffects":
|
|
13
|
+
"sideEffects": false,
|
|
10
14
|
"type": "module",
|
|
11
15
|
"exports": {
|
|
12
16
|
".": {
|
|
@@ -43,50 +47,56 @@
|
|
|
43
47
|
"src"
|
|
44
48
|
],
|
|
45
49
|
"dependencies": {
|
|
46
|
-
"@automerge/automerge": "3.
|
|
47
|
-
"@automerge/automerge-repo": "2.
|
|
50
|
+
"@automerge/automerge": "3.2.3",
|
|
51
|
+
"@automerge/automerge-repo": "2.5.1",
|
|
52
|
+
"@effect/sql": "0.48.6",
|
|
53
|
+
"@effect/sql-sqlite-node": "0.49.1",
|
|
48
54
|
"crc-32": "^1.2.2",
|
|
49
|
-
"effect": "3.
|
|
55
|
+
"effect": "3.19.11",
|
|
50
56
|
"level-transcoder": "^1.0.1",
|
|
51
57
|
"lodash.isequal": "^4.5.0",
|
|
52
|
-
"@dxos/
|
|
53
|
-
"@dxos/
|
|
54
|
-
"@dxos/
|
|
55
|
-
"@dxos/credentials": "0.8.4-main.
|
|
56
|
-
"@dxos/
|
|
57
|
-
"@dxos/
|
|
58
|
-
"@dxos/echo
|
|
59
|
-
"@dxos/echo": "0.8.4-main.
|
|
60
|
-
"@dxos/edge-client": "0.8.4-main.
|
|
61
|
-
"@dxos/errors": "0.8.4-main.
|
|
62
|
-
"@dxos/
|
|
63
|
-
"@dxos/
|
|
64
|
-
"@dxos/
|
|
65
|
-
"@dxos/
|
|
66
|
-
"@dxos/
|
|
67
|
-
"@dxos/
|
|
68
|
-
"@dxos/indexing": "0.8.4-main.
|
|
69
|
-
"@dxos/
|
|
70
|
-
"@dxos/
|
|
71
|
-
"@dxos/
|
|
72
|
-
"@dxos/
|
|
73
|
-
"@dxos/
|
|
74
|
-
"@dxos/
|
|
75
|
-
"@dxos/
|
|
76
|
-
"@dxos/
|
|
77
|
-
"@dxos/
|
|
78
|
-
"@dxos/
|
|
79
|
-
"@dxos/teleport": "0.8.4-main.
|
|
80
|
-
"@dxos/
|
|
81
|
-
"@dxos/teleport-extension-
|
|
82
|
-
"@dxos/
|
|
83
|
-
"@dxos/
|
|
58
|
+
"@dxos/async": "0.8.4-main.bc674ce",
|
|
59
|
+
"@dxos/context": "0.8.4-main.bc674ce",
|
|
60
|
+
"@dxos/codec-protobuf": "0.8.4-main.bc674ce",
|
|
61
|
+
"@dxos/credentials": "0.8.4-main.bc674ce",
|
|
62
|
+
"@dxos/debug": "0.8.4-main.bc674ce",
|
|
63
|
+
"@dxos/crypto": "0.8.4-main.bc674ce",
|
|
64
|
+
"@dxos/echo": "0.8.4-main.bc674ce",
|
|
65
|
+
"@dxos/echo-protocol": "0.8.4-main.bc674ce",
|
|
66
|
+
"@dxos/edge-client": "0.8.4-main.bc674ce",
|
|
67
|
+
"@dxos/errors": "0.8.4-main.bc674ce",
|
|
68
|
+
"@dxos/effect": "0.8.4-main.bc674ce",
|
|
69
|
+
"@dxos/feed": "0.8.4-main.bc674ce",
|
|
70
|
+
"@dxos/feed-store": "0.8.4-main.bc674ce",
|
|
71
|
+
"@dxos/index-core": "0.8.4-main.bc674ce",
|
|
72
|
+
"@dxos/hypercore": "0.8.4-main.bc674ce",
|
|
73
|
+
"@dxos/invariant": "0.8.4-main.bc674ce",
|
|
74
|
+
"@dxos/indexing": "0.8.4-main.bc674ce",
|
|
75
|
+
"@dxos/keyring": "0.8.4-main.bc674ce",
|
|
76
|
+
"@dxos/keys": "0.8.4-main.bc674ce",
|
|
77
|
+
"@dxos/kv-store": "0.8.4-main.bc674ce",
|
|
78
|
+
"@dxos/log": "0.8.4-main.bc674ce",
|
|
79
|
+
"@dxos/messaging": "0.8.4-main.bc674ce",
|
|
80
|
+
"@dxos/network-manager": "0.8.4-main.bc674ce",
|
|
81
|
+
"@dxos/node-std": "0.8.4-main.bc674ce",
|
|
82
|
+
"@dxos/protocols": "0.8.4-main.bc674ce",
|
|
83
|
+
"@dxos/random-access-storage": "0.8.4-main.bc674ce",
|
|
84
|
+
"@dxos/sql-sqlite": "0.8.4-main.bc674ce",
|
|
85
|
+
"@dxos/teleport": "0.8.4-main.bc674ce",
|
|
86
|
+
"@dxos/teleport-extension-automerge-replicator": "0.8.4-main.bc674ce",
|
|
87
|
+
"@dxos/teleport-extension-object-sync": "0.8.4-main.bc674ce",
|
|
88
|
+
"@dxos/tracing": "0.8.4-main.bc674ce",
|
|
89
|
+
"@dxos/teleport-extension-gossip": "0.8.4-main.bc674ce",
|
|
90
|
+
"@dxos/teleport-extension-replicator": "0.8.4-main.bc674ce",
|
|
91
|
+
"@dxos/timeframe": "0.8.4-main.bc674ce",
|
|
92
|
+
"@dxos/util": "0.8.4-main.bc674ce",
|
|
93
|
+
"@dxos/typings": "0.8.4-main.bc674ce"
|
|
84
94
|
},
|
|
85
95
|
"devDependencies": {
|
|
86
96
|
"@types/lodash.isequal": "^4.5.0",
|
|
87
97
|
"fast-check": "^3.19.0",
|
|
88
98
|
"get-port-please": "^3.1.1",
|
|
89
|
-
"@dxos/test-utils": "0.8.4-main.
|
|
99
|
+
"@dxos/test-utils": "0.8.4-main.bc674ce"
|
|
90
100
|
},
|
|
91
101
|
"publishConfig": {
|
|
92
102
|
"access": "public"
|
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { getHeads } from '@automerge/automerge';
|
|
6
|
-
import
|
|
6
|
+
import * as Automerge from '@automerge/automerge';
|
|
7
|
+
import { type DocumentId, type Heads, generateAutomergeUrl, parseAutomergeUrl } from '@automerge/automerge-repo';
|
|
7
8
|
import { describe, expect, onTestFinished, test } from 'vitest';
|
|
8
9
|
|
|
10
|
+
import { Context } from '@dxos/context';
|
|
9
11
|
import { IndexMetadataStore } from '@dxos/indexing';
|
|
10
12
|
import { PublicKey } from '@dxos/keys';
|
|
11
13
|
import type { LevelDB } from '@dxos/kv-store';
|
|
@@ -21,31 +23,51 @@ describe('AutomergeHost', () => {
|
|
|
21
23
|
test('can create documents', async () => {
|
|
22
24
|
const level = await createLevel();
|
|
23
25
|
const host = await setupAutomergeHost({ level });
|
|
24
|
-
const handle = host.
|
|
26
|
+
const handle = await host.createDoc<any>();
|
|
25
27
|
handle.change((doc: any) => {
|
|
26
28
|
doc.text = 'Hello world';
|
|
27
29
|
});
|
|
28
|
-
await host.
|
|
30
|
+
await host.flush();
|
|
29
31
|
expect(handle.doc()!.text).toEqual('Hello world');
|
|
30
32
|
});
|
|
31
33
|
|
|
32
34
|
test('changes are preserved in storage', async () => {
|
|
33
35
|
const level = await createLevel();
|
|
34
36
|
const host = await setupAutomergeHost({ level });
|
|
35
|
-
const handle = host.
|
|
37
|
+
const handle = await host.createDoc<any>();
|
|
36
38
|
handle.change((doc: any) => {
|
|
37
39
|
doc.text = 'Hello world';
|
|
38
40
|
});
|
|
39
41
|
const url = handle.url;
|
|
40
42
|
|
|
41
|
-
await host.
|
|
43
|
+
await host.flush();
|
|
42
44
|
await host.close();
|
|
43
45
|
|
|
44
46
|
const host2 = await setupAutomergeHost({ level });
|
|
45
|
-
const handle2 = await host2.
|
|
47
|
+
const handle2 = await host2.loadDoc<any>(Context.default(), url);
|
|
46
48
|
await handle2.whenReady();
|
|
47
49
|
expect(handle2.doc()!.text).toEqual('Hello world');
|
|
48
|
-
await host2.
|
|
50
|
+
await host2.flush();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('load resolves when document is created from binary', async () => {
|
|
54
|
+
const level = await createLevel();
|
|
55
|
+
const host = await setupAutomergeHost({ level });
|
|
56
|
+
|
|
57
|
+
// Create a document to get its binary representation
|
|
58
|
+
const document = Automerge.from({ text: 'Hello world' });
|
|
59
|
+
const binary = Automerge.save(document);
|
|
60
|
+
const { documentId } = parseAutomergeUrl(generateAutomergeUrl());
|
|
61
|
+
|
|
62
|
+
// Start loading a non-existent document (should hang until created)
|
|
63
|
+
const loadPromise = host.loadDoc(Context.default(), documentId);
|
|
64
|
+
|
|
65
|
+
// Create the document from binary - this should resolve the load
|
|
66
|
+
const createdHandle = await host.createDoc(binary, { preserveHistory: true, documentId });
|
|
67
|
+
|
|
68
|
+
// The load should now resolve
|
|
69
|
+
const loadedHandle = await loadPromise;
|
|
70
|
+
expect(loadedHandle.doc()).toEqual(createdHandle.doc());
|
|
49
71
|
});
|
|
50
72
|
|
|
51
73
|
test('query single document heads', async () => {
|
|
@@ -53,7 +75,7 @@ describe('AutomergeHost', () => {
|
|
|
53
75
|
|
|
54
76
|
const level = await createLevel(tmpPath);
|
|
55
77
|
const host = await setupAutomergeHost({ level });
|
|
56
|
-
const handle = host.createDoc({ text: 'Hello world' });
|
|
78
|
+
const handle = await host.createDoc({ text: 'Hello world' });
|
|
57
79
|
const expectedHeads = getHeads(handle.doc()!);
|
|
58
80
|
await host.flush();
|
|
59
81
|
|
|
@@ -73,7 +95,7 @@ describe('AutomergeHost', () => {
|
|
|
73
95
|
|
|
74
96
|
const level = await createLevel(tmpPath);
|
|
75
97
|
const host = await setupAutomergeHost({ level });
|
|
76
|
-
const handles = range(2, () => host.createDoc({ text: 'Hello world' }));
|
|
98
|
+
const handles = await Promise.all(range(2, () => host.createDoc({ text: 'Hello world' })));
|
|
77
99
|
const expectedHeads: (Heads | undefined)[] = handles.map((handle) => getHeads(handle.doc()!));
|
|
78
100
|
await host.flush();
|
|
79
101
|
|
|
@@ -106,7 +128,7 @@ describe('AutomergeHost', () => {
|
|
|
106
128
|
{
|
|
107
129
|
const host2 = await setupAutomergeHost({ level: level2 });
|
|
108
130
|
for (const i of range(NUM_DOCUMENTS)) {
|
|
109
|
-
const handle = host2.createDoc({ docIndex: i });
|
|
131
|
+
const handle = await host2.createDoc({ docIndex: i });
|
|
110
132
|
documentIds.push(handle.documentId);
|
|
111
133
|
}
|
|
112
134
|
await host2.flush();
|
|
@@ -115,20 +137,21 @@ describe('AutomergeHost', () => {
|
|
|
115
137
|
|
|
116
138
|
const host2 = await setupAutomergeHost({ level: level2 });
|
|
117
139
|
|
|
140
|
+
const network = await new TestReplicationNetwork().open();
|
|
141
|
+
await host1.addReplicator(await network.createReplicator());
|
|
142
|
+
await host2.addReplicator(await network.createReplicator());
|
|
143
|
+
|
|
118
144
|
const collectionId = 'test-collection';
|
|
119
145
|
await host1.updateLocalCollectionState(collectionId, documentIds);
|
|
120
146
|
await host2.updateLocalCollectionState(collectionId, documentIds);
|
|
121
147
|
|
|
122
|
-
await using network = await new TestReplicationNetwork().open();
|
|
123
|
-
await host1.addReplicator(await network.createReplicator());
|
|
124
|
-
await host2.addReplicator(await network.createReplicator());
|
|
125
|
-
|
|
126
148
|
for (const documentId of documentIds) {
|
|
127
149
|
await expect.poll(() => host1.getHeads([documentId])).toEqual(await host2.getHeads([documentId]));
|
|
128
150
|
}
|
|
129
151
|
|
|
130
152
|
await host1.close();
|
|
131
153
|
await host2.close();
|
|
154
|
+
await network.close();
|
|
132
155
|
});
|
|
133
156
|
|
|
134
157
|
const createLevel = async (tmpPath?: string) => {
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
type StorageKey,
|
|
26
26
|
interpretAsDocumentId,
|
|
27
27
|
} from '@automerge/automerge-repo';
|
|
28
|
+
import type * as Record from 'effect/Record';
|
|
28
29
|
|
|
29
30
|
import { DeferredTask, Event, asyncTimeout } from '@dxos/async';
|
|
30
31
|
import { Context, type Lifecycle, Resource, cancelWithContext } from '@dxos/context';
|
|
@@ -38,20 +39,20 @@ import { objectPointerCodec } from '@dxos/protocols';
|
|
|
38
39
|
import { type SpaceSyncState } from '@dxos/protocols/proto/dxos/echo/service';
|
|
39
40
|
import { type DocHeadsList, type FlushRequest } from '@dxos/protocols/proto/dxos/echo/service';
|
|
40
41
|
import { trace } from '@dxos/tracing';
|
|
41
|
-
import { ComplexSet, bufferToArray, isNonNullable, range } from '@dxos/util';
|
|
42
|
+
import { ComplexSet, bufferToArray, defaultMap, isNonNullable, range } from '@dxos/util';
|
|
42
43
|
|
|
43
44
|
import { type CollectionState, CollectionSynchronizer, diffCollectionState } from './collection-synchronizer';
|
|
44
45
|
import { type EchoDataMonitor } from './echo-data-monitor';
|
|
45
46
|
import { EchoNetworkAdapter, isEchoPeerMetadata } from './echo-network-adapter';
|
|
46
|
-
import { type EchoReplicator, type
|
|
47
|
+
import { type EchoReplicator, type RemoteDocumentExistenceCheckProps } from './echo-replicator';
|
|
47
48
|
import { HeadsStore } from './heads-store';
|
|
48
|
-
import { type
|
|
49
|
+
import { type BeforeSaveProps, LevelDBStorageAdapter } from './leveldb-storage-adapter';
|
|
49
50
|
|
|
50
51
|
export type PeerIdProvider = () => string | undefined;
|
|
51
52
|
|
|
52
53
|
export type RootDocumentSpaceKeyProvider = (documentId: string) => PublicKey | undefined;
|
|
53
54
|
|
|
54
|
-
export type
|
|
55
|
+
export type AutomergeHostProps = {
|
|
55
56
|
db: LevelDB;
|
|
56
57
|
indexMetadataStore: IndexMetadataStore;
|
|
57
58
|
dataMonitor?: EchoDataMonitor;
|
|
@@ -65,6 +66,12 @@ export type AutomergeHostParams = {
|
|
|
65
66
|
|
|
66
67
|
export type LoadDocOptions = {
|
|
67
68
|
timeout?: number;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* If true, the document will be fetched from the network if it is not found in the local storage.
|
|
72
|
+
* Setting this to false does not guarantee that the document will not be fetched from the network.
|
|
73
|
+
*/
|
|
74
|
+
fetchFromNetwork?: boolean;
|
|
68
75
|
};
|
|
69
76
|
|
|
70
77
|
export type CreateDocOptions = {
|
|
@@ -72,6 +79,8 @@ export type CreateDocOptions = {
|
|
|
72
79
|
* Import the document together with its history.
|
|
73
80
|
*/
|
|
74
81
|
preserveHistory?: boolean;
|
|
82
|
+
|
|
83
|
+
documentId?: DocumentId;
|
|
75
84
|
};
|
|
76
85
|
|
|
77
86
|
export const FIND_PARAMS = {
|
|
@@ -91,6 +100,11 @@ const BUNDLE_SYNC_CONCURRENCY = 2;
|
|
|
91
100
|
*/
|
|
92
101
|
const BUNDLE_SYNC_THRESHOLD = 50;
|
|
93
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Only announce documents that are known to require sync.
|
|
105
|
+
*/
|
|
106
|
+
const OPTIMIZED_SHARE_POLICY = true;
|
|
107
|
+
|
|
94
108
|
/**
|
|
95
109
|
* Abstracts over the AutomergeRepo.
|
|
96
110
|
*/
|
|
@@ -132,7 +146,29 @@ export class AutomergeHost extends Resource {
|
|
|
132
146
|
public readonly documentsSaved = new Event();
|
|
133
147
|
|
|
134
148
|
private readonly _headsUpdates = new Map<DocumentId, Heads>();
|
|
135
|
-
private _onHeadsChangedTask?: DeferredTask
|
|
149
|
+
private _onHeadsChangedTask?: DeferredTask;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Documents created in this session.
|
|
153
|
+
*/
|
|
154
|
+
private _createdDocuments = new Set<DocumentId>();
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Documents that need to be synced based on the result of collection-sync.
|
|
158
|
+
*/
|
|
159
|
+
private _documentsToSync = new Set<DocumentId>();
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Documents that are not avaiale locally that should be requested.
|
|
163
|
+
*/
|
|
164
|
+
private _documentsToRequest = new Set<DocumentId>();
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Documents requested by remote peers.
|
|
168
|
+
*/
|
|
169
|
+
private _documentsRequested = new Map<PeerId, Set<DocumentId>>();
|
|
170
|
+
|
|
171
|
+
private _sharePolicyChangedTask?: DeferredTask;
|
|
136
172
|
|
|
137
173
|
constructor({
|
|
138
174
|
db,
|
|
@@ -140,7 +176,7 @@ export class AutomergeHost extends Resource {
|
|
|
140
176
|
dataMonitor,
|
|
141
177
|
peerIdProvider,
|
|
142
178
|
getSpaceKeyByRootDocumentId,
|
|
143
|
-
}:
|
|
179
|
+
}: AutomergeHostProps) {
|
|
144
180
|
super();
|
|
145
181
|
this._db = db;
|
|
146
182
|
this._storage = new LevelDBStorageAdapter({
|
|
@@ -158,6 +194,10 @@ export class AutomergeHost extends Resource {
|
|
|
158
194
|
onCollectionStateReceived: this._onCollectionStateReceived.bind(this),
|
|
159
195
|
monitor: dataMonitor,
|
|
160
196
|
});
|
|
197
|
+
this._echoNetworkAdapter.documentRequested.on(({ peerId, documentId }) => {
|
|
198
|
+
defaultMap(this._documentsRequested, peerId, () => new Set()).add(documentId);
|
|
199
|
+
this._sharePolicyChangedTask!.schedule();
|
|
200
|
+
});
|
|
161
201
|
this._headsStore = new HeadsStore({ db: db.sublevel('heads') });
|
|
162
202
|
this._indexMetadataStore = indexMetadataStore;
|
|
163
203
|
this._peerIdProvider = peerIdProvider;
|
|
@@ -178,7 +218,7 @@ export class AutomergeHost extends Resource {
|
|
|
178
218
|
// Construct the automerge repo.
|
|
179
219
|
this._repo = new Repo({
|
|
180
220
|
peerId: this._peerId as PeerId,
|
|
181
|
-
|
|
221
|
+
shareConfig: this._shareConfig,
|
|
182
222
|
storage: this._storage,
|
|
183
223
|
network: [
|
|
184
224
|
// Upstream swarm.
|
|
@@ -226,6 +266,11 @@ export class AutomergeHost extends Resource {
|
|
|
226
266
|
);
|
|
227
267
|
});
|
|
228
268
|
|
|
269
|
+
this._sharePolicyChangedTask = new DeferredTask(this._ctx, async () => {
|
|
270
|
+
log('share policy changed');
|
|
271
|
+
this._repo.shareConfigChanged();
|
|
272
|
+
});
|
|
273
|
+
|
|
229
274
|
await this._echoNetworkAdapter.open();
|
|
230
275
|
await this._collectionSynchronizer.open();
|
|
231
276
|
await this._echoNetworkAdapter.open();
|
|
@@ -238,13 +283,7 @@ export class AutomergeHost extends Resource {
|
|
|
238
283
|
await this._echoNetworkAdapter.close();
|
|
239
284
|
this._syncTask = undefined;
|
|
240
285
|
this._onHeadsChangedTask = undefined;
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* @deprecated To be abstracted away.
|
|
245
|
-
*/
|
|
246
|
-
get repo(): Repo {
|
|
247
|
-
return this._repo;
|
|
286
|
+
this._sharePolicyChangedTask = undefined;
|
|
248
287
|
}
|
|
249
288
|
|
|
250
289
|
get peerId(): PeerId {
|
|
@@ -255,6 +294,10 @@ export class AutomergeHost extends Resource {
|
|
|
255
294
|
return Object.keys(this._repo.handles).length;
|
|
256
295
|
}
|
|
257
296
|
|
|
297
|
+
get handles(): Readonly<Record<DocumentId, DocHandle<any>>> {
|
|
298
|
+
return this._repo.handles;
|
|
299
|
+
}
|
|
300
|
+
|
|
258
301
|
async addReplicator(replicator: EchoReplicator): Promise<void> {
|
|
259
302
|
invariant(this.isOpen, 'AutomergeHost is not open');
|
|
260
303
|
await this._echoNetworkAdapter.addReplicator(replicator);
|
|
@@ -276,7 +319,22 @@ export class AutomergeHost extends Resource {
|
|
|
276
319
|
handle = this._repo.handles[documentId as DocumentId];
|
|
277
320
|
}
|
|
278
321
|
if (!handle) {
|
|
279
|
-
|
|
322
|
+
if (!opts?.fetchFromNetwork) {
|
|
323
|
+
handle = await this._repo.find(documentId as DocumentId, FIND_PARAMS);
|
|
324
|
+
} else {
|
|
325
|
+
try {
|
|
326
|
+
handle = await this._repo.find(documentId as DocumentId, {
|
|
327
|
+
allowableStates: ['ready', 'requesting', 'unavailable'],
|
|
328
|
+
});
|
|
329
|
+
if (handle.state === 'requesting' || handle.state === 'unavailable') {
|
|
330
|
+
this._documentsToRequest.add(handle.documentId);
|
|
331
|
+
this._sharePolicyChangedTask!.schedule();
|
|
332
|
+
}
|
|
333
|
+
} catch (err) {
|
|
334
|
+
log.error('failed to load document', { documentId, err });
|
|
335
|
+
throw err;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
280
338
|
}
|
|
281
339
|
|
|
282
340
|
// `whenReady` creates a timeout so we guard it with an if to skip it if the handle is already ready.
|
|
@@ -302,11 +360,11 @@ export class AutomergeHost extends Resource {
|
|
|
302
360
|
/**
|
|
303
361
|
* Create new persisted document.
|
|
304
362
|
*/
|
|
305
|
-
createDoc<T>(initialValue?: T | Doc<T> | Uint8Array, opts?: CreateDocOptions): DocHandle<T
|
|
363
|
+
async createDoc<T>(initialValue?: T | Doc<T> | Uint8Array, opts?: CreateDocOptions): Promise<DocHandle<T>> {
|
|
306
364
|
invariant(this.isOpen, 'AutomergeHost is not open');
|
|
307
365
|
if (opts?.preserveHistory) {
|
|
308
366
|
if (initialValue instanceof Uint8Array) {
|
|
309
|
-
return this._repo.import(initialValue);
|
|
367
|
+
return this._repo.import(initialValue, { docId: opts?.documentId });
|
|
310
368
|
}
|
|
311
369
|
|
|
312
370
|
if (!isAutomerge(initialValue)) {
|
|
@@ -314,13 +372,22 @@ export class AutomergeHost extends Resource {
|
|
|
314
372
|
}
|
|
315
373
|
|
|
316
374
|
// TODO(dmaretskyi): There's a more efficient way.
|
|
317
|
-
|
|
375
|
+
const handle = this._repo.import(save(initialValue as Doc<T>), { docId: opts?.documentId });
|
|
376
|
+
this._createdDocuments.add(handle.documentId);
|
|
377
|
+
this._sharePolicyChangedTask!.schedule();
|
|
378
|
+
return handle as DocHandle<T>;
|
|
318
379
|
} else {
|
|
319
380
|
if (initialValue instanceof Uint8Array) {
|
|
320
381
|
throw new Error('Cannot create document from Uint8Array without preserving history');
|
|
321
382
|
}
|
|
322
383
|
|
|
323
|
-
|
|
384
|
+
if (opts?.documentId) {
|
|
385
|
+
throw new Error('Cannot prefil document id when not importing an existing doc');
|
|
386
|
+
}
|
|
387
|
+
const handle = await this._repo.create2<T>(initialValue);
|
|
388
|
+
this._createdDocuments.add(handle.documentId);
|
|
389
|
+
this._sharePolicyChangedTask!.schedule();
|
|
390
|
+
return handle;
|
|
324
391
|
}
|
|
325
392
|
}
|
|
326
393
|
|
|
@@ -373,24 +440,42 @@ export class AutomergeHost extends Resource {
|
|
|
373
440
|
log('done re-indexing heads');
|
|
374
441
|
}
|
|
375
442
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
private async _sharePolicy(peerId: PeerId, documentId?: DocumentId): Promise<boolean> {
|
|
381
|
-
if (!documentId) {
|
|
382
|
-
return false;
|
|
383
|
-
}
|
|
443
|
+
private readonly _shareConfig = {
|
|
444
|
+
access: async (peerId: PeerId, documentId: DocumentId): Promise<boolean> => {
|
|
445
|
+
return true;
|
|
446
|
+
},
|
|
384
447
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
448
|
+
// TODO(dmaretskyi): Share based on HALO permissions and space affinity.
|
|
449
|
+
// Hosts, running in the worker, don't share documents unless requested by other peers.
|
|
450
|
+
// NOTE: If both peers return sharePolicy=false the replication will not happen
|
|
451
|
+
// https://github.com/automerge/automerge-repo/pull/292
|
|
452
|
+
// Called for all loaded documents so they could be advertised to the sync server.
|
|
453
|
+
announce: async (peerId: PeerId, documentId?: DocumentId): Promise<boolean> => {
|
|
454
|
+
if (!documentId) {
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
389
457
|
|
|
390
|
-
|
|
391
|
-
|
|
458
|
+
if (OPTIMIZED_SHARE_POLICY) {
|
|
459
|
+
if (
|
|
460
|
+
!this._createdDocuments.has(documentId) &&
|
|
461
|
+
!this._documentsToSync.has(documentId) &&
|
|
462
|
+
!this._documentsToRequest.has(documentId)
|
|
463
|
+
) {
|
|
464
|
+
// Skip advertising documents that don't need to be synced.
|
|
465
|
+
return false;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const peerMetadata = this._repo.peerMetadataByPeerId[peerId];
|
|
470
|
+
if (isEchoPeerMetadata(peerMetadata)) {
|
|
471
|
+
return this._echoNetworkAdapter.shouldAdvertise(peerId, { documentId });
|
|
472
|
+
}
|
|
392
473
|
|
|
393
|
-
|
|
474
|
+
return false;
|
|
475
|
+
},
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
private async _beforeSave({ path, batch }: BeforeSaveProps): Promise<void> {
|
|
394
479
|
const handle = this._repo.handles[path[0] as DocumentId];
|
|
395
480
|
if (!handle || !handle.isReady()) {
|
|
396
481
|
return;
|
|
@@ -452,7 +537,7 @@ export class AutomergeHost extends Resource {
|
|
|
452
537
|
return this._repo.peers;
|
|
453
538
|
}
|
|
454
539
|
|
|
455
|
-
private async _isDocumentInRemoteCollection(params:
|
|
540
|
+
private async _isDocumentInRemoteCollection(params: RemoteDocumentExistenceCheckProps): Promise<boolean> {
|
|
456
541
|
for (const collectionId of this._collectionSynchronizer.getRegisteredCollectionIds()) {
|
|
457
542
|
const remoteCollections = this._collectionSynchronizer.getRemoteCollectionStates(collectionId);
|
|
458
543
|
const remotePeerDocs = remoteCollections.get(params.peerId as PeerId)?.documents;
|
|
@@ -530,6 +615,13 @@ export class AutomergeHost extends Resource {
|
|
|
530
615
|
return result;
|
|
531
616
|
}
|
|
532
617
|
|
|
618
|
+
/**
|
|
619
|
+
* Iterate over all document heads stored on disk.
|
|
620
|
+
*/
|
|
621
|
+
listDocumentHeads(): AsyncGenerator<{ documentId: DocumentId; heads: Heads }> {
|
|
622
|
+
return this._headsStore.iterateAll();
|
|
623
|
+
}
|
|
624
|
+
|
|
533
625
|
//
|
|
534
626
|
// Collection sync.
|
|
535
627
|
//
|
|
@@ -684,12 +776,14 @@ export class AutomergeHost extends Resource {
|
|
|
684
776
|
|
|
685
777
|
// Load the documents so they will start syncing.
|
|
686
778
|
for (const documentId of toReplicateWithoutBatching) {
|
|
779
|
+
// Unless we track the document in "to sync" list, it will not be advertised.
|
|
780
|
+
this._documentsToSync!.add(documentId);
|
|
687
781
|
this._repo.findWithProgress(documentId);
|
|
688
782
|
}
|
|
783
|
+
this._sharePolicyChangedTask!.schedule();
|
|
689
784
|
}
|
|
690
785
|
|
|
691
786
|
// TODO(mykola): Add retries of batches https://gist.github.com/mykola-vrmchk/fde270259e9209fcbf1331e5abbf12cf
|
|
692
|
-
// TODO(mykola): Use effect to retry batches.
|
|
693
787
|
private async _pushInBundles(
|
|
694
788
|
peerId: PeerId,
|
|
695
789
|
documentIds: DocumentId[],
|