@dxos/echo-pipeline 0.8.4-main.ae835ea → 0.8.4-main.bc2380dfbc
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/LICENSE +102 -5
- package/README.md +1 -1
- package/dist/lib/{browser/chunk-WJJXJTNS.mjs → neutral/chunk-46DCRDA7.mjs} +44 -13
- package/dist/lib/neutral/chunk-46DCRDA7.mjs.map +7 -0
- package/dist/lib/{browser/chunk-FR6GJ4S4.mjs → neutral/chunk-LR7UFVQX.mjs} +145 -618
- package/dist/lib/neutral/chunk-LR7UFVQX.mjs.map +7 -0
- package/dist/lib/{browser → neutral}/filter/index.mjs +1 -2
- package/dist/lib/neutral/index.mjs +7332 -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 +49 -292
- 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 +105 -27
- 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 +22 -2
- 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 +17 -11
- package/dist/types/src/automerge/echo-network-adapter.d.ts.map +1 -1
- package/dist/types/src/automerge/echo-replicator.d.ts +28 -16
- package/dist/types/src/automerge/echo-replicator.d.ts.map +1 -1
- package/dist/types/src/automerge/errors.d.ts +35 -0
- package/dist/types/src/automerge/errors.d.ts.map +1 -0
- 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 -0
- package/dist/types/src/automerge/index.d.ts.map +1 -1
- package/dist/types/src/automerge/leveldb-storage-adapter.d.ts +10 -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 +8 -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/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 +12 -6
- 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 +30 -8
- package/dist/types/src/db-host/documents-synchronizer.d.ts.map +1 -1
- package/dist/types/src/db-host/echo-host.d.ts +74 -28
- 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 +18 -10
- 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 +3 -2
- 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 +7 -6
- 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 +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/message-selector.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/pipeline/timeframe-clock.d.ts.map +1 -1
- package/dist/types/src/query/errors.d.ts +16 -20
- package/dist/types/src/query/errors.d.ts.map +1 -1
- package/dist/types/src/query/plan.d.ts +59 -8
- package/dist/types/src/query/plan.d.ts.map +1 -1
- package/dist/types/src/query/query-executor.d.ts +55 -12
- package/dist/types/src/query/query-executor.d.ts.map +1 -1
- package/dist/types/src/query/query-planner.d.ts +10 -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 +4 -3
- package/dist/types/src/space/control-pipeline.d.ts.map +1 -1
- package/dist/types/src/space/space-manager.d.ts +7 -7
- package/dist/types/src/space/space-manager.d.ts.map +1 -1
- package/dist/types/src/space/space-protocol.d.ts +9 -8
- package/dist/types/src/space/space-protocol.d.ts.map +1 -1
- package/dist/types/src/space/space.d.ts +5 -5
- 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.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 +13 -13
- 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 +56 -54
- package/src/automerge/automerge-host-subduction.test.ts +263 -0
- package/src/automerge/automerge-host.test.ts +145 -32
- package/src/automerge/automerge-host.ts +442 -163
- package/src/automerge/automerge-repo-subduction.test.ts +1054 -0
- package/src/automerge/automerge-repo.test.ts +284 -77
- package/src/automerge/automerge-subduction.test.ts +330 -0
- package/src/automerge/collection-synchronizer.test.ts +96 -16
- package/src/automerge/collection-synchronizer.ts +125 -26
- package/src/automerge/echo-network-adapter.test.ts +2 -1
- package/src/automerge/echo-network-adapter.ts +44 -32
- package/src/automerge/echo-replicator.ts +30 -16
- package/src/automerge/errors.ts +10 -0
- package/src/automerge/handle-state.ts +62 -0
- package/src/automerge/heads-store.ts +42 -3
- package/src/automerge/index.ts +2 -0
- package/src/automerge/leveldb-storage-adapter.ts +70 -4
- package/src/automerge/mesh-echo-replicator-connection.ts +8 -8
- package/src/automerge/mesh-echo-replicator.ts +14 -11
- 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 +27 -10
- package/src/db-host/database-root.ts +19 -7
- package/src/db-host/documents-synchronizer.test.ts +145 -14
- package/src/db-host/documents-synchronizer.ts +158 -59
- package/src/db-host/echo-host.ts +376 -90
- 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 +108 -111
- 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 +11 -9
- package/src/db-host/stub.ts +33 -0
- package/src/edge/echo-edge-replicator.test.ts +9 -9
- package/src/edge/echo-edge-replicator.ts +50 -42
- 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 +41 -25
- package/src/filter/filter-match.ts +75 -20
- package/src/metadata/metadata-store.ts +8 -5
- package/src/pipeline/pipeline-stress.test.ts +1 -3
- package/src/pipeline/pipeline.test.ts +0 -1
- package/src/pipeline/pipeline.ts +2 -3
- package/src/query/errors.ts +2 -2
- package/src/query/plan.ts +73 -9
- package/src/query/query-executor.ts +1064 -172
- package/src/query/query-planner.test.ts +854 -86
- package/src/query/query-planner.ts +444 -38
- package/src/space/admission-discovery-extension.ts +1 -1
- package/src/space/auth.ts +6 -6
- package/src/space/control-pipeline.test.ts +2 -2
- package/src/space/control-pipeline.ts +7 -7
- package/src/space/space-manager.ts +12 -17
- package/src/space/space-protocol.browser.test.ts +13 -12
- package/src/space/space-protocol.test.ts +13 -13
- package/src/space/space-protocol.ts +16 -15
- package/src/space/space.test.ts +2 -2
- package/src/space/space.ts +9 -10
- package/src/testing/index.ts +0 -2
- package/src/testing/test-agent-builder.ts +0 -1
- package/src/testing/test-network-adapter.ts +24 -3
- package/src/testing/test-replicator.ts +15 -20
- 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/browser/index.mjs +0 -5492
- 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-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 +0 -2144
- package/dist/lib/node-esm/chunk-JQCCVDLT.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-LEQ77KAT.mjs +0 -240
- package/dist/lib/node-esm/chunk-LEQ77KAT.mjs.map +0 -7
- package/dist/lib/node-esm/filter/index.mjs +0 -13
- package/dist/lib/node-esm/filter/index.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -5492
- 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 -764
- 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
|
@@ -18,17 +18,20 @@ import {
|
|
|
18
18
|
type AutomergeUrl,
|
|
19
19
|
type DocHandle,
|
|
20
20
|
type DocumentId,
|
|
21
|
-
type
|
|
21
|
+
type DocumentProgress,
|
|
22
|
+
type Message,
|
|
22
23
|
type PeerId,
|
|
23
24
|
Repo,
|
|
24
25
|
type SharePolicy,
|
|
25
26
|
type StorageAdapterInterface,
|
|
26
27
|
generateAutomergeUrl,
|
|
28
|
+
initSubduction,
|
|
27
29
|
parseAutomergeUrl,
|
|
28
30
|
} from '@automerge/automerge-repo';
|
|
29
|
-
import { describe, expect, onTestFinished, test } from 'vitest';
|
|
31
|
+
import { beforeAll, describe, expect, onTestFinished, test } from 'vitest';
|
|
30
32
|
|
|
31
|
-
import { asyncTimeout, sleep } from '@dxos/async';
|
|
33
|
+
import { Trigger, asyncTimeout, sleep } from '@dxos/async';
|
|
34
|
+
import { Context } from '@dxos/context';
|
|
32
35
|
import { randomBytes } from '@dxos/crypto';
|
|
33
36
|
import { PublicKey } from '@dxos/keys';
|
|
34
37
|
import { createTestLevel } from '@dxos/kv-store/testing';
|
|
@@ -37,15 +40,68 @@ import { openAndClose } from '@dxos/test-utils';
|
|
|
37
40
|
import { isNonNullable, range } from '@dxos/util';
|
|
38
41
|
|
|
39
42
|
import { TestAdapter, type TestConnectionStateProvider } from '../testing';
|
|
40
|
-
|
|
41
|
-
import { FIND_PARAMS } from './automerge-host';
|
|
42
43
|
import { EchoNetworkAdapter } from './echo-network-adapter';
|
|
44
|
+
import { type HandleQueryState } from './handle-state';
|
|
43
45
|
import { LevelDBStorageAdapter } from './leveldb-storage-adapter';
|
|
44
46
|
import { MeshEchoReplicator } from './mesh-echo-replicator';
|
|
45
47
|
|
|
46
48
|
const HOST_AND_CLIENT: [string, string] = ['host', 'client'];
|
|
47
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Block until the {@link DocumentProgress} reports a state in `awaitStates`.
|
|
52
|
+
*/
|
|
53
|
+
const waitForQueryState = async (
|
|
54
|
+
progress: DocumentProgress<unknown>,
|
|
55
|
+
awaitStates: readonly HandleQueryState[],
|
|
56
|
+
{ timeout }: { timeout?: number } = {},
|
|
57
|
+
): Promise<void> => {
|
|
58
|
+
if (awaitStates.includes(progress.peek().state)) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const trigger = new Trigger();
|
|
62
|
+
const unsubscribe = progress.subscribe((state) => {
|
|
63
|
+
if (awaitStates.includes(state.state)) {
|
|
64
|
+
trigger.wake();
|
|
65
|
+
} else if (state.state === 'failed' && !awaitStates.includes('failed')) {
|
|
66
|
+
trigger.throw(state.error);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
try {
|
|
70
|
+
await trigger.wait({ timeout });
|
|
71
|
+
} finally {
|
|
72
|
+
unsubscribe();
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Multi-state `Repo.find` replacement for the pre-subduction
|
|
78
|
+
* `Repo.find(url, { allowableStates })` ergonomics. */
|
|
79
|
+
const findInStates = async <T>(
|
|
80
|
+
repo: Repo,
|
|
81
|
+
url: AutomergeUrl,
|
|
82
|
+
awaitStates: readonly HandleQueryState[],
|
|
83
|
+
): Promise<DocHandle<T>> => {
|
|
84
|
+
const { documentId } = parseAutomergeUrl(url);
|
|
85
|
+
const progress = repo.findWithProgress<T>(url);
|
|
86
|
+
await waitForQueryState(progress, awaitStates);
|
|
87
|
+
return repo.handles[documentId] as DocHandle<T>;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Default state set for `findInStates`. Equivalent to the pre-subduction
|
|
92
|
+
* `FIND_PARAMS = { allowableStates: ['ready', 'requesting'] }` — i.e. "give
|
|
93
|
+
* me the handle as soon as we have it OR are still trying to fetch it".
|
|
94
|
+
* `'loading'` is the {@link QueryState} equivalent of the old `'requesting'`.
|
|
95
|
+
*/
|
|
96
|
+
const FIND_STATES: readonly HandleQueryState[] = ['ready', 'loading'];
|
|
97
|
+
|
|
48
98
|
describe('AutomergeRepo', () => {
|
|
99
|
+
// Subduction-fork `Repo` constructs a `MemorySigner` internally; WASM must be
|
|
100
|
+
// initialized first or the constructor throws on `memorysigner_new`.
|
|
101
|
+
beforeAll(async () => {
|
|
102
|
+
await initSubduction();
|
|
103
|
+
});
|
|
104
|
+
|
|
49
105
|
test('change events', () => {
|
|
50
106
|
const repo = new Repo({ network: [] });
|
|
51
107
|
const handle = repo.create<{ field?: string }>();
|
|
@@ -112,13 +168,14 @@ describe('AutomergeRepo', () => {
|
|
|
112
168
|
expect(equals(a, c)).to.be.true;
|
|
113
169
|
});
|
|
114
170
|
|
|
115
|
-
test('documents missing from local storage go to
|
|
171
|
+
test('documents missing from local storage go to loading state', async () => {
|
|
116
172
|
const { repos, adapters } = await createHostClientRepoTopology();
|
|
117
173
|
const [host] = repos;
|
|
118
174
|
await connectAdapters(adapters);
|
|
119
175
|
const url = 'automerge:3JN8F3Z4dUWEEKKFN7WE9gEGvVUT';
|
|
120
|
-
|
|
121
|
-
|
|
176
|
+
// `'loading'` is the equivalent of the legacy `'requesting'` handle state.
|
|
177
|
+
const progress = host.findWithProgress(url as AutomergeUrl);
|
|
178
|
+
expect(progress.peek().state).to.equal('loading');
|
|
122
179
|
});
|
|
123
180
|
|
|
124
181
|
test('documents on disk go to ready state', async () => {
|
|
@@ -162,32 +219,6 @@ describe('AutomergeRepo', () => {
|
|
|
162
219
|
expect((await asyncTimeout(client.find<any>(handle.url), 1000))!.doc().text).to.equal(text);
|
|
163
220
|
});
|
|
164
221
|
|
|
165
|
-
test('share policy gets enabled afterwards', async () => {
|
|
166
|
-
let sharePolicy = false;
|
|
167
|
-
|
|
168
|
-
const { repos, adapters } = await createHostClientRepoTopology({
|
|
169
|
-
sharePolicy: async () => sharePolicy,
|
|
170
|
-
});
|
|
171
|
-
const [host, client] = repos;
|
|
172
|
-
await connectAdapters(adapters);
|
|
173
|
-
|
|
174
|
-
const handle = host.create();
|
|
175
|
-
const text = 'Hello world';
|
|
176
|
-
handle.change((doc: any) => {
|
|
177
|
-
doc.text = text;
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
{
|
|
181
|
-
await client.find(handle.url, { allowableStates: ['unavailable'] });
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
sharePolicy = true;
|
|
185
|
-
|
|
186
|
-
{
|
|
187
|
-
await client.find(handle.url, { allowableStates: ['unavailable'] });
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
|
|
191
222
|
test('two documents and share policy switching', async () => {
|
|
192
223
|
const allowedDocs: DocumentId[] = [];
|
|
193
224
|
|
|
@@ -237,15 +268,15 @@ describe('AutomergeRepo', () => {
|
|
|
237
268
|
await connectAdapters(adapters);
|
|
238
269
|
|
|
239
270
|
const handle = host.create();
|
|
240
|
-
const docOnClient = await
|
|
271
|
+
const docOnClient = await findInStates<any>(client, handle.url, FIND_STATES);
|
|
241
272
|
{
|
|
242
273
|
const sanityText = 'Hello world';
|
|
243
274
|
handle.change((doc: any) => {
|
|
244
275
|
doc.sanityText = sanityText;
|
|
245
276
|
});
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
expect(docOnClient.doc()
|
|
277
|
+
// Subduction-fork: `whenReady()` resolves on the handle's `ready` state
|
|
278
|
+
// (no longer waits for new data). Poll for the actual change to propagate.
|
|
279
|
+
await expect.poll(() => docOnClient.doc()?.sanityText, { timeout: 1000 }).toEqual(sanityText);
|
|
249
280
|
}
|
|
250
281
|
|
|
251
282
|
// Disrupt connection.
|
|
@@ -271,9 +302,7 @@ describe('AutomergeRepo', () => {
|
|
|
271
302
|
handle.change((doc: any) => {
|
|
272
303
|
doc.onlineText = onlineText;
|
|
273
304
|
});
|
|
274
|
-
await
|
|
275
|
-
await asyncTimeout(docOnClient.whenReady(), 1000);
|
|
276
|
-
expect(docOnClient.doc().onlineText).to.equal(onlineText);
|
|
305
|
+
await expect.poll(() => docOnClient.doc()?.onlineText, { timeout: 1000 }).toEqual(onlineText);
|
|
277
306
|
expect(docOnClient.doc().offlineText).to.equal(offlineText);
|
|
278
307
|
}
|
|
279
308
|
});
|
|
@@ -320,8 +349,8 @@ describe('AutomergeRepo', () => {
|
|
|
320
349
|
doc.text = 'Hello world';
|
|
321
350
|
});
|
|
322
351
|
|
|
323
|
-
const _ = repoB
|
|
324
|
-
const docC = await repoC
|
|
352
|
+
const _ = findInStates(repoB, docA.url, FIND_STATES);
|
|
353
|
+
const docC = await findInStates(repoC, docA.url, FIND_STATES);
|
|
325
354
|
await asyncTimeout(docC.whenReady(), 1000);
|
|
326
355
|
});
|
|
327
356
|
|
|
@@ -330,7 +359,6 @@ describe('AutomergeRepo', () => {
|
|
|
330
359
|
const [host, client] = repos;
|
|
331
360
|
const [[adapter1, adapter2]] = adapters;
|
|
332
361
|
|
|
333
|
-
const unavailable: HandleState = 'unavailable';
|
|
334
362
|
await connectAdapters(adapters, { noEmitPeerCandidate: true });
|
|
335
363
|
|
|
336
364
|
const docA = host.create();
|
|
@@ -339,21 +367,21 @@ describe('AutomergeRepo', () => {
|
|
|
339
367
|
doc.text = 'Hello world';
|
|
340
368
|
});
|
|
341
369
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
//
|
|
345
|
-
|
|
370
|
+
// Without peer candidates, the patched subduction source keeps the
|
|
371
|
+
// query pending (`'loading'`) instead of driving it to
|
|
372
|
+
// `'unavailable'` — it will retry on the next connection-state
|
|
373
|
+
// change.
|
|
374
|
+
const progress = client.findWithProgress(docA.url);
|
|
375
|
+
await expect.poll(() => progress.peek().state, { timeout: 500, interval: 50 }).toEqual('loading');
|
|
346
376
|
|
|
347
377
|
adapter1.peerCandidate(adapter2.peerId!);
|
|
348
378
|
await sleep(100);
|
|
349
379
|
adapter2.peerCandidate(adapter1.peerId!);
|
|
350
380
|
|
|
351
|
-
//
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
// Wait gets loaded after reconnect.
|
|
381
|
+
// Reconnecting cycles the peers and re-triggers sync; the doc
|
|
382
|
+
// becomes available.
|
|
355
383
|
await reconnectAdapters(adapters);
|
|
356
|
-
await
|
|
384
|
+
await waitForQueryState(progress, ['ready'], { timeout: 1_000 });
|
|
357
385
|
});
|
|
358
386
|
|
|
359
387
|
test('documents loaded from disk get replicated', async () => {
|
|
@@ -386,9 +414,13 @@ describe('AutomergeRepo', () => {
|
|
|
386
414
|
const repo = new Repo({ network: [] });
|
|
387
415
|
const serverHandle = repo.create<{ field?: string }>();
|
|
388
416
|
|
|
389
|
-
|
|
417
|
+
// The client must be bootstrapped via `A.load` on the first message
|
|
418
|
+
// (full save) — using `A.from({})` + `A.loadIncremental` panics
|
|
419
|
+
// automerge@3.2.5 with `PatchLogMismatch` when the donor history shares
|
|
420
|
+
// no lineage with the receiver's local actor.
|
|
421
|
+
let clientDoc: A.Doc<{ field?: string }> | undefined;
|
|
390
422
|
const receiveByClient = (blob: Uint8Array) => {
|
|
391
|
-
clientDoc = A.loadIncremental(clientDoc, blob);
|
|
423
|
+
clientDoc = clientDoc === undefined ? A.load(blob) : A.loadIncremental(clientDoc, blob);
|
|
392
424
|
};
|
|
393
425
|
|
|
394
426
|
// Sync handshake.
|
|
@@ -407,7 +439,7 @@ describe('AutomergeRepo', () => {
|
|
|
407
439
|
serverHandle.change((doc: any) => {
|
|
408
440
|
doc.field = value;
|
|
409
441
|
});
|
|
410
|
-
expect(clientDoc
|
|
442
|
+
expect(clientDoc?.field).to.deep.equal(value);
|
|
411
443
|
}
|
|
412
444
|
|
|
413
445
|
{
|
|
@@ -415,7 +447,7 @@ describe('AutomergeRepo', () => {
|
|
|
415
447
|
serverHandle.change((doc: any) => {
|
|
416
448
|
doc.field = value;
|
|
417
449
|
});
|
|
418
|
-
expect(clientDoc
|
|
450
|
+
expect(clientDoc?.field).to.deep.equal(value);
|
|
419
451
|
}
|
|
420
452
|
});
|
|
421
453
|
|
|
@@ -510,6 +542,149 @@ describe('AutomergeRepo', () => {
|
|
|
510
542
|
await asyncTimeout(handleB.whenReady(), 1000);
|
|
511
543
|
expect(handleB.doc()!.text).to.equal(text);
|
|
512
544
|
});
|
|
545
|
+
|
|
546
|
+
test('retry share config', async () => {
|
|
547
|
+
let announce = false;
|
|
548
|
+
|
|
549
|
+
const { repos, adapters } = await createRepoTopology({
|
|
550
|
+
peers: ['A', 'B'],
|
|
551
|
+
connections: [['A', 'B']],
|
|
552
|
+
options: {
|
|
553
|
+
shareConfig: {
|
|
554
|
+
access: async () => true,
|
|
555
|
+
announce: async () => announce,
|
|
556
|
+
},
|
|
557
|
+
},
|
|
558
|
+
});
|
|
559
|
+
const [repoA, repoB] = repos;
|
|
560
|
+
await connectAdapters(adapters);
|
|
561
|
+
|
|
562
|
+
const docA = repoA.create({ text: 'Hello world' });
|
|
563
|
+
|
|
564
|
+
// TODO(mykola): The classical-network `DocSynchronizer` doesn't drive
|
|
565
|
+
// the query to `'unavailable'` while the dormant subduction source is
|
|
566
|
+
// still attached — the query stays in `'loading'` until something
|
|
567
|
+
// actually delivers data. Once the fork drives source state
|
|
568
|
+
// correctly, switch this to a positive `'unavailable'` assertion.
|
|
569
|
+
// For now we assert "stuck in loading" + empty doc body.
|
|
570
|
+
const progress = repoB.findWithProgress<{ text: string }>(docA.url);
|
|
571
|
+
await expect.poll(() => progress.peek().state, { timeout: 500, interval: 50 }).toEqual('loading');
|
|
572
|
+
const docB = repoB.handles[parseAutomergeUrl(docA.url).documentId] as DocHandle<{ text: string }>;
|
|
573
|
+
expect(docB.doc()).to.deep.equal({});
|
|
574
|
+
|
|
575
|
+
// Flip announce on and kick the share-config retry; the query
|
|
576
|
+
// transitions to `'ready'` once the doc syncs through.
|
|
577
|
+
announce = true;
|
|
578
|
+
repoB.shareConfigChanged();
|
|
579
|
+
// Use `waitForQueryState` instead of `progress.whenReady()` so that a
|
|
580
|
+
// transient `'unavailable'` from the dormant subduction source
|
|
581
|
+
// doesn't reject the wait before classical sync delivers.
|
|
582
|
+
await waitForQueryState(progress, ['ready'], { timeout: 1_000 });
|
|
583
|
+
expect(progress.peek().state).to.equal('ready');
|
|
584
|
+
expect(docB.doc()).to.deep.equal({ text: 'Hello world' });
|
|
585
|
+
});
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
describe('create2 tests', () => {
|
|
589
|
+
test('basic document creation', async () => {
|
|
590
|
+
const repo = new Repo({ network: [] });
|
|
591
|
+
|
|
592
|
+
const handle = await repo.create2<{ text: string }>();
|
|
593
|
+
expect(handle.doc()).to.deep.equal({});
|
|
594
|
+
|
|
595
|
+
handle.change((doc: any) => {
|
|
596
|
+
doc.text = 'Hello World';
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
expect(handle.doc()?.text).to.equal('Hello World');
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
test('document mutation', async () => {
|
|
603
|
+
const repo = new Repo({ network: [] });
|
|
604
|
+
|
|
605
|
+
const handle = await repo.create2<{ counter: number }>({ counter: 0 });
|
|
606
|
+
|
|
607
|
+
for (let i = 1; i <= 10; i++) {
|
|
608
|
+
handle.change((doc: any) => {
|
|
609
|
+
doc.counter = i;
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
expect(handle.doc()?.counter).to.equal(10);
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
test('import document and mutate', async () => {
|
|
617
|
+
const repo = new Repo({ network: [] });
|
|
618
|
+
|
|
619
|
+
const original = await repo.create2<{ text: string }>({ text: 'original' });
|
|
620
|
+
const blob = A.save(original.doc()!);
|
|
621
|
+
|
|
622
|
+
const imported = repo.import<{ text: string }>(blob);
|
|
623
|
+
expect(imported.doc()?.text).to.equal('original');
|
|
624
|
+
|
|
625
|
+
imported.change((doc: any) => {
|
|
626
|
+
doc.text = 'mutated';
|
|
627
|
+
});
|
|
628
|
+
expect(imported.doc()?.text).to.equal('mutated');
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
test('reload document with flush', async () => {
|
|
632
|
+
const path = createTmpPath();
|
|
633
|
+
const text = 'Hello World!';
|
|
634
|
+
let url: AutomergeUrl;
|
|
635
|
+
|
|
636
|
+
{
|
|
637
|
+
const level = createTestLevel(path);
|
|
638
|
+
const storage = await createLevelAdapter(level);
|
|
639
|
+
const repo = new Repo({ network: [], storage });
|
|
640
|
+
const handle = await repo.create2<{ text: string }>();
|
|
641
|
+
url = handle.url;
|
|
642
|
+
handle.change((doc: any) => {
|
|
643
|
+
doc.text = text;
|
|
644
|
+
});
|
|
645
|
+
await repo.flush([handle.documentId]);
|
|
646
|
+
await level.close();
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
{
|
|
650
|
+
const level = createTestLevel(path);
|
|
651
|
+
const storage = await createLevelAdapter(level);
|
|
652
|
+
const repo = new Repo({ network: [], storage });
|
|
653
|
+
const handle = await repo.find<{ text: string }>(url);
|
|
654
|
+
await handle.whenReady();
|
|
655
|
+
expect(handle.doc()?.text).to.equal(text);
|
|
656
|
+
await level.close();
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
test('reload document without flush', async () => {
|
|
661
|
+
const path = createTmpPath();
|
|
662
|
+
const text = 'Hello World!';
|
|
663
|
+
let url: AutomergeUrl;
|
|
664
|
+
|
|
665
|
+
{
|
|
666
|
+
const level = createTestLevel(path);
|
|
667
|
+
const storage = await createLevelAdapter(level);
|
|
668
|
+
const repo = new Repo({ network: [], storage });
|
|
669
|
+
const handle = await repo.create2<{ text: string }>();
|
|
670
|
+
url = handle.url;
|
|
671
|
+
handle.change((doc: any) => {
|
|
672
|
+
doc.text = text;
|
|
673
|
+
});
|
|
674
|
+
// No explicit flush - rely on auto-save.
|
|
675
|
+
await sleep(200);
|
|
676
|
+
await level.close();
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
{
|
|
680
|
+
const level = createTestLevel(path);
|
|
681
|
+
const storage = await createLevelAdapter(level);
|
|
682
|
+
const repo = new Repo({ network: [], storage });
|
|
683
|
+
const handle = await repo.find<{ text: string }>(url);
|
|
684
|
+
await handle.whenReady();
|
|
685
|
+
expect(handle.doc()?.text).to.equal(text);
|
|
686
|
+
}
|
|
687
|
+
});
|
|
513
688
|
});
|
|
514
689
|
|
|
515
690
|
describe('teleport', () => {
|
|
@@ -596,11 +771,25 @@ describe('AutomergeRepo', () => {
|
|
|
596
771
|
});
|
|
597
772
|
await connectPeers(spaceKey, teleportBuilder, peerWithDocs.peer, peer2);
|
|
598
773
|
|
|
599
|
-
const
|
|
600
|
-
|
|
601
|
-
|
|
774
|
+
const shouldNotFindProgress = peer2.repo.findWithProgress(docNotInRemoteCollection.url);
|
|
775
|
+
// The doc in the remote collection should sync. Can't use
|
|
776
|
+
// `repo.find()` / `progress.whenReady()` here: the dormant
|
|
777
|
+
// subduction source transitions to `'unavailable'` mid-flight
|
|
778
|
+
// (before classical-network sync delivers the doc), which both APIs
|
|
779
|
+
// surface as a rejection. `findInStates` ignores transient
|
|
780
|
+
// `'unavailable'` and only rejects on `'failed'`.
|
|
781
|
+
const shouldFindDoc = await findInStates<any>(peer2.repo, docInRemoteCollection.url, ['ready']);
|
|
782
|
+
expect(shouldFindDoc.doc()).to.deep.eq(docInRemoteCollection.doc());
|
|
602
783
|
expect(shouldFindDoc.doc()).to.deep.eq(docInRemoteCollection.doc());
|
|
603
|
-
|
|
784
|
+
// The doc that's NOT in any remote collection must remain unsynced.
|
|
785
|
+
// TODO(mykola): Fork's classical-network sync doesn't transition the
|
|
786
|
+
// query to `'unavailable'` while a dormant subduction source is
|
|
787
|
+
// attached, so we can only assert "stays in loading" rather than
|
|
788
|
+
// "reaches unavailable". Re-enable a positive `'unavailable'`
|
|
789
|
+
// assertion when the fork drives the source state correctly.
|
|
790
|
+
await expect.poll(() => shouldNotFindProgress.peek().state, { timeout: 500, interval: 50 }).toEqual('loading');
|
|
791
|
+
const shouldNotFindDoc = peer2.repo.handles[parseAutomergeUrl(docNotInRemoteCollection.url).documentId];
|
|
792
|
+
expect(shouldNotFindDoc.doc()).to.deep.equal({});
|
|
604
793
|
});
|
|
605
794
|
|
|
606
795
|
/**
|
|
@@ -630,12 +819,23 @@ describe('AutomergeRepo', () => {
|
|
|
630
819
|
await connectPeers(spaceKey, teleportBuilder, peerWithDocs.peer, peer2);
|
|
631
820
|
await connectPeers(anotherSpaceKey, teleportBuilder, peer2, peerFromAnotherSpace);
|
|
632
821
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
const
|
|
638
|
-
|
|
822
|
+
// Can't use `repo.find()` / `progress.whenReady()`: the dormant
|
|
823
|
+
// subduction source can transition to `'unavailable'` mid-flight
|
|
824
|
+
// (before classical sync delivers), which both APIs surface as a
|
|
825
|
+
// rejection. `findInStates` ignores transient `'unavailable'`.
|
|
826
|
+
const loaded = await findInStates<any>(peer2.repo, document.url, ['ready']);
|
|
827
|
+
expect(loaded.doc()).to.deep.equal(document.doc());
|
|
828
|
+
|
|
829
|
+
// peerFromAnotherSpace is connected to peer2 over a different spaceKey,
|
|
830
|
+
// so it must NOT receive the doc.
|
|
831
|
+
// TODO(mykola): Fork's classical-network query doesn't reach
|
|
832
|
+
// `'unavailable'`; assert "stays in loading" instead. Switch to a
|
|
833
|
+
// positive `'unavailable'` assertion once the fork drives source
|
|
834
|
+
// state correctly.
|
|
835
|
+
const otherSpaceProgress = peerFromAnotherSpace.repo.findWithProgress(document.url);
|
|
836
|
+
await expect.poll(() => otherSpaceProgress.peek().state, { timeout: 500, interval: 50 }).toEqual('loading');
|
|
837
|
+
const otherSpaceDoc = peerFromAnotherSpace.repo.handles[parseAutomergeUrl(document.url).documentId];
|
|
838
|
+
expect(otherSpaceDoc.doc()).to.deep.equal({});
|
|
639
839
|
});
|
|
640
840
|
|
|
641
841
|
test('collection-sync with chain of peers', async () => {
|
|
@@ -664,12 +864,11 @@ describe('AutomergeRepo', () => {
|
|
|
664
864
|
await connectPeers(spaceKey, teleportBuilder, peerWithDocs.peer, peer2);
|
|
665
865
|
await connectPeers(spaceKey, teleportBuilder, peer2, peer3);
|
|
666
866
|
|
|
667
|
-
const loadedDocument = await peer2.repo
|
|
668
|
-
await loadedDocument.
|
|
867
|
+
const loadedDocument = await findInStates(peer2.repo, document.url, FIND_STATES);
|
|
868
|
+
await expect.poll(() => loadedDocument.doc(), { timeout: 1000 }).toEqual(document.doc());
|
|
669
869
|
|
|
670
|
-
const doc = await peer3.repo
|
|
671
|
-
await doc.
|
|
672
|
-
expect(doc.doc()).to.deep.eq(document.doc());
|
|
870
|
+
const doc = await findInStates(peer3.repo, document.url, FIND_STATES);
|
|
871
|
+
await expect.poll(() => doc.doc(), { timeout: 1000 }).toEqual(document.doc());
|
|
673
872
|
});
|
|
674
873
|
|
|
675
874
|
test('document is passively replicated to connected peers', async () => {
|
|
@@ -730,8 +929,7 @@ describe('AutomergeRepo', () => {
|
|
|
730
929
|
});
|
|
731
930
|
});
|
|
732
931
|
|
|
733
|
-
const createLevelAdapter = async () => {
|
|
734
|
-
const level = createTestLevel();
|
|
932
|
+
const createLevelAdapter = async (level = createTestLevel()) => {
|
|
735
933
|
const storage = new LevelDBStorageAdapter({ db: level.sublevel('automerge') });
|
|
736
934
|
await openAndClose(level, storage);
|
|
737
935
|
return storage;
|
|
@@ -753,19 +951,23 @@ describe('AutomergeRepo', () => {
|
|
|
753
951
|
};
|
|
754
952
|
});
|
|
755
953
|
|
|
954
|
+
type ShareConfig = Exclude<ConstructorParameters<typeof Repo>[0], undefined>['shareConfig'];
|
|
955
|
+
|
|
756
956
|
type ConnectedRepoOptions = {
|
|
757
957
|
storages?: StorageAdapterInterface[];
|
|
758
958
|
connectionStateProvider?: TestConnectionStateProvider;
|
|
759
959
|
sharePolicy?: SharePolicy;
|
|
960
|
+
shareConfig?: ShareConfig;
|
|
760
961
|
};
|
|
761
962
|
|
|
762
963
|
const createRepoTopology = async <Peers extends string[], Peer extends string = Peers[number]>(args: {
|
|
763
964
|
peers: Peers;
|
|
764
965
|
connections: [Peer, Peer][];
|
|
765
966
|
options?: ConnectedRepoOptions;
|
|
967
|
+
onMessage?: (message: Message) => void;
|
|
766
968
|
}) => {
|
|
767
969
|
const adapters = args.connections.map(
|
|
768
|
-
() => TestAdapter.createPair(args.options?.connectionStateProvider) as [TestAdapter, TestAdapter],
|
|
970
|
+
() => TestAdapter.createPair(args.options?.connectionStateProvider, args.onMessage) as [TestAdapter, TestAdapter],
|
|
769
971
|
);
|
|
770
972
|
const repos = args.peers.map((peerId, peerIndex) => {
|
|
771
973
|
const network = adapters
|
|
@@ -781,7 +983,8 @@ const createRepoTopology = async <Peers extends string[], Peer extends string =
|
|
|
781
983
|
peerId: peerId as PeerId,
|
|
782
984
|
storage: args.options?.storages?.[peerIndex],
|
|
783
985
|
network,
|
|
784
|
-
sharePolicy: args.options?.sharePolicy ??
|
|
986
|
+
sharePolicy: (args.options?.sharePolicy ?? args.options?.shareConfig) ? undefined : async () => true,
|
|
987
|
+
shareConfig: args.options?.shareConfig,
|
|
785
988
|
});
|
|
786
989
|
});
|
|
787
990
|
return { repos, adapters };
|
|
@@ -848,7 +1051,7 @@ const createTeleportTestPeer = async (
|
|
|
848
1051
|
});
|
|
849
1052
|
await echoAdapter.open();
|
|
850
1053
|
await echoAdapter.whenConnected();
|
|
851
|
-
await echoAdapter.addReplicator(meshAdapter);
|
|
1054
|
+
await echoAdapter.addReplicator(Context.default(), meshAdapter);
|
|
852
1055
|
const [teleport] = teleportBuilder.createPeers({ factory: () => new TeleportPeer() });
|
|
853
1056
|
return { repo, meshAdapter, teleport };
|
|
854
1057
|
};
|
|
@@ -867,3 +1070,7 @@ const connectPeers = async (
|
|
|
867
1070
|
};
|
|
868
1071
|
|
|
869
1072
|
type TeleportTestPeer = { repo: Repo; meshAdapter: MeshEchoReplicator; teleport: TeleportPeer };
|
|
1073
|
+
|
|
1074
|
+
export const createTmpPath = (): string => {
|
|
1075
|
+
return `/tmp/dxos-${PublicKey.random().toHex()}`;
|
|
1076
|
+
};
|