@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
|
@@ -3,24 +3,27 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { next as A, type Heads } from '@automerge/automerge';
|
|
6
|
-
import { type
|
|
6
|
+
import { type DocumentId, type DocumentQuery } from '@automerge/automerge-repo';
|
|
7
7
|
|
|
8
|
-
import { UpdateScheduler
|
|
9
|
-
import { LifecycleState, Resource
|
|
8
|
+
import { UpdateScheduler } from '@dxos/async';
|
|
9
|
+
import { Context, LifecycleState, Resource } from '@dxos/context';
|
|
10
10
|
import { type DatabaseDirectory } from '@dxos/echo-protocol';
|
|
11
11
|
import { invariant } from '@dxos/invariant';
|
|
12
12
|
import { log } from '@dxos/log';
|
|
13
13
|
import { type BatchedDocumentUpdates, type DocumentUpdate } from '@dxos/protocols/proto/dxos/echo/service';
|
|
14
|
+
import { retry } from '@dxos/util';
|
|
15
|
+
|
|
16
|
+
import type { AutomergeHost } from '../automerge';
|
|
14
17
|
|
|
15
18
|
const MAX_UPDATE_FREQ = 10; // [updates/sec]
|
|
16
19
|
|
|
17
|
-
export type
|
|
18
|
-
|
|
20
|
+
export type DocumentsSynchronizerProps = {
|
|
21
|
+
automergeHost: AutomergeHost;
|
|
19
22
|
sendUpdates: (updates: BatchedDocumentUpdates) => void;
|
|
20
23
|
};
|
|
21
24
|
|
|
22
25
|
interface DocSyncState {
|
|
23
|
-
|
|
26
|
+
docQuery: DocumentQuery<DatabaseDirectory>;
|
|
24
27
|
lastSentHead?: Heads;
|
|
25
28
|
clearSubscriptions?: () => void;
|
|
26
29
|
}
|
|
@@ -39,41 +42,108 @@ export class DocumentsSynchronizer extends Resource {
|
|
|
39
42
|
*/
|
|
40
43
|
private readonly _pendingUpdates = new Set<DocumentId>();
|
|
41
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Documents whose on-disk probe completed negative and that need a
|
|
47
|
+
* mutation-less `requesting: true` transition update sent to the client.
|
|
48
|
+
* Cleared per-flush in `_checkAndSendUpdates`.
|
|
49
|
+
*/
|
|
50
|
+
private readonly _pendingRequesting = new Set<DocumentId>();
|
|
51
|
+
|
|
42
52
|
/**
|
|
43
53
|
* Job that schedules if there are pending updates.
|
|
44
54
|
*/
|
|
45
55
|
private _sendUpdatesJob?: UpdateScheduler = undefined;
|
|
46
56
|
|
|
47
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Test affordance: when true, `_checkAndSendUpdates` is a no-op so no
|
|
59
|
+
* document state is flushed to the client. `addDocuments` still loads
|
|
60
|
+
* documents on the worker side and accumulates `_pendingUpdates`, which
|
|
61
|
+
* flush automatically on resume. From the client's perspective every
|
|
62
|
+
* `RepoProxy.find()` made while paused returns a handle that stays
|
|
63
|
+
* `pending` until resume — used to deterministically reproduce
|
|
64
|
+
* worker-side hangs in query-pipeline tests.
|
|
65
|
+
*/
|
|
66
|
+
#sendUpdatesPaused = false;
|
|
67
|
+
|
|
68
|
+
constructor(private readonly _params: DocumentsSynchronizerProps) {
|
|
48
69
|
super();
|
|
49
70
|
}
|
|
50
71
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
): void {
|
|
56
|
-
if (
|
|
57
|
-
log.warn('Failed to load document, retry limit reached', { documentIds });
|
|
72
|
+
/**
|
|
73
|
+
* Test affordance: pause/resume flushing of document updates to the client.
|
|
74
|
+
* See `#sendUpdatesPaused` for details. Idempotent.
|
|
75
|
+
*/
|
|
76
|
+
setSendUpdatesPaused(paused: boolean): void {
|
|
77
|
+
if (this.#sendUpdatesPaused === paused) {
|
|
58
78
|
return;
|
|
59
79
|
}
|
|
80
|
+
this.#sendUpdatesPaused = paused;
|
|
81
|
+
if (!paused) {
|
|
82
|
+
this._sendUpdatesJob?.trigger();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
60
85
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
await
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
86
|
+
async addDocuments(documentIds: DocumentId[]): Promise<void> {
|
|
87
|
+
await Promise.all(
|
|
88
|
+
documentIds.map(async (documentId) => {
|
|
89
|
+
try {
|
|
90
|
+
await retry(
|
|
91
|
+
{ count: WRAP_AROUND_RETRY_LIMIT, delayMs: WRAP_AROUND_RETRY_INITIAL_DELAY, exponent: 2 },
|
|
92
|
+
async () => {
|
|
93
|
+
try {
|
|
94
|
+
log('loading document', { documentId });
|
|
95
|
+
const docQuery = this._params.automergeHost.findWithProgress<DatabaseDirectory>(
|
|
96
|
+
documentId as DocumentId,
|
|
97
|
+
);
|
|
98
|
+
this._startSync(docQuery);
|
|
99
|
+
this._pendingUpdates.add(docQuery.documentId);
|
|
100
|
+
this._sendUpdatesJob!.trigger();
|
|
101
|
+
// Background disk probe so the client can distinguish
|
|
102
|
+
// "not on disk, waiting for network" from "still loading".
|
|
103
|
+
// Fire-and-forget; the result feeds `_pendingRequesting`.
|
|
104
|
+
this._scheduleDiskProbe(docQuery.documentId);
|
|
105
|
+
} catch (err) {
|
|
106
|
+
log.warn('failed to load document', { err });
|
|
107
|
+
throw err;
|
|
108
|
+
}
|
|
109
|
+
},
|
|
74
110
|
);
|
|
75
|
-
})
|
|
76
|
-
|
|
111
|
+
} catch (err) {
|
|
112
|
+
log.catch(err);
|
|
113
|
+
}
|
|
114
|
+
}),
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Probe local storage for the document; if not present, enqueue a
|
|
120
|
+
* `requesting: true` transition update so the client moves the handle
|
|
121
|
+
* from `'pending'` to `'requesting'` and disk-only callers can give up
|
|
122
|
+
* without waiting on the network. If the doc became `ready` before the
|
|
123
|
+
* probe completes (e.g. delivered concurrently by network/peer), no
|
|
124
|
+
* transition update is sent.
|
|
125
|
+
*/
|
|
126
|
+
private _scheduleDiskProbe(documentId: DocumentId): void {
|
|
127
|
+
void Promise.resolve().then(async () => {
|
|
128
|
+
try {
|
|
129
|
+
const onDisk = await this._params.automergeHost.hasDocOnDisk(documentId);
|
|
130
|
+
if (onDisk) {
|
|
131
|
+
// Doc is on disk; the existing `heads-changed` flow will deliver
|
|
132
|
+
// a normal mutation update once the load completes.
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
// Skip the transition signal if the doc has since become `ready`
|
|
136
|
+
// via the network/peer race.
|
|
137
|
+
const syncState = this._syncStates.get(documentId);
|
|
138
|
+
if (!syncState || syncState.docQuery.peek().state === 'ready') {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
this._pendingRequesting.add(documentId);
|
|
142
|
+
this._sendUpdatesJob?.trigger();
|
|
143
|
+
} catch (err) {
|
|
144
|
+
log.warn('disk probe failed', { documentId, err });
|
|
145
|
+
}
|
|
146
|
+
});
|
|
77
147
|
}
|
|
78
148
|
|
|
79
149
|
removeDocuments(documentIds: DocumentId[]): void {
|
|
@@ -81,6 +151,7 @@ export class DocumentsSynchronizer extends Resource {
|
|
|
81
151
|
this._syncStates.get(documentId)?.clearSubscriptions?.();
|
|
82
152
|
this._syncStates.delete(documentId);
|
|
83
153
|
this._pendingUpdates.delete(documentId);
|
|
154
|
+
this._pendingRequesting.delete(documentId);
|
|
84
155
|
}
|
|
85
156
|
}
|
|
86
157
|
|
|
@@ -95,36 +166,48 @@ export class DocumentsSynchronizer extends Resource {
|
|
|
95
166
|
this._syncStates.clear();
|
|
96
167
|
}
|
|
97
168
|
|
|
98
|
-
async update(updates: DocumentUpdate[]): Promise<void> {
|
|
99
|
-
for (const { documentId, mutation
|
|
100
|
-
|
|
169
|
+
async update(ctx: Context, updates: DocumentUpdate[]): Promise<void> {
|
|
170
|
+
for (const { documentId, mutation } of updates) {
|
|
171
|
+
// Inbound (client -> worker) updates are always mutation-bearing; the
|
|
172
|
+
// `mutation`-less variant is only used for worker -> client transition
|
|
173
|
+
// signals (e.g. `requesting`). Defensive skip here to satisfy the
|
|
174
|
+
// optional proto field.
|
|
175
|
+
if (!mutation) {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
await this._writeMutation(documentId as DocumentId, mutation);
|
|
101
179
|
}
|
|
102
180
|
// TODO(mykola): This should not be required.
|
|
103
|
-
await this._params.
|
|
181
|
+
await this._params.automergeHost.flush(ctx, {
|
|
182
|
+
documentIds: updates.map(({ documentId }) => documentId as DocumentId),
|
|
183
|
+
});
|
|
104
184
|
}
|
|
105
185
|
|
|
106
|
-
private _startSync(
|
|
107
|
-
if (this._syncStates.has(
|
|
108
|
-
log('Document already being synced', { documentId:
|
|
186
|
+
private _startSync(docQuery: DocumentQuery<DatabaseDirectory>) {
|
|
187
|
+
if (this._syncStates.has(docQuery.documentId)) {
|
|
188
|
+
log('Document already being synced', { documentId: docQuery.documentId });
|
|
109
189
|
return;
|
|
110
190
|
}
|
|
111
191
|
|
|
112
|
-
const syncState: DocSyncState = {
|
|
192
|
+
const syncState: DocSyncState = { docQuery };
|
|
113
193
|
this._subscribeForChanges(syncState);
|
|
114
|
-
this._syncStates.set(
|
|
194
|
+
this._syncStates.set(docQuery.documentId, syncState);
|
|
115
195
|
return syncState;
|
|
116
196
|
}
|
|
117
197
|
|
|
118
198
|
_subscribeForChanges(syncState: DocSyncState): void {
|
|
119
199
|
const handler = () => {
|
|
120
|
-
this._pendingUpdates.add(syncState.
|
|
200
|
+
this._pendingUpdates.add(syncState.docQuery.documentId);
|
|
121
201
|
this._sendUpdatesJob!.trigger();
|
|
122
202
|
};
|
|
123
|
-
syncState.handle.on('heads-changed', handler);
|
|
124
|
-
syncState.clearSubscriptions = () => syncState.handle.off('heads-changed', handler);
|
|
203
|
+
syncState.docQuery.handle.on('heads-changed', handler);
|
|
204
|
+
syncState.clearSubscriptions = () => syncState.docQuery.handle.off('heads-changed', handler);
|
|
125
205
|
}
|
|
126
206
|
|
|
127
207
|
private async _checkAndSendUpdates(): Promise<void> {
|
|
208
|
+
if (this.#sendUpdatesPaused) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
128
211
|
const updates: DocumentUpdate[] = [];
|
|
129
212
|
|
|
130
213
|
const docsWithPendingUpdates = Array.from(this._pendingUpdates);
|
|
@@ -140,6 +223,23 @@ export class DocumentsSynchronizer extends Resource {
|
|
|
140
223
|
}
|
|
141
224
|
}
|
|
142
225
|
|
|
226
|
+
// Mutation-less transition updates: tell the client `requesting: true`
|
|
227
|
+
// for any documents whose disk probe completed negative. Skip docs that
|
|
228
|
+
// are already being delivered as a real mutation in this same batch
|
|
229
|
+
// (those go directly to `ready` on the client and a subsequent
|
|
230
|
+
// `requesting` would be ignored anyway).
|
|
231
|
+
const docsRequesting = Array.from(this._pendingRequesting);
|
|
232
|
+
this._pendingRequesting.clear();
|
|
233
|
+
if (docsRequesting.length > 0) {
|
|
234
|
+
const docsBeingFlushed = new Set(updates.map((update) => update.documentId));
|
|
235
|
+
for (const documentId of docsRequesting) {
|
|
236
|
+
if (docsBeingFlushed.has(documentId)) {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
updates.push({ documentId, requesting: true });
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
143
243
|
if (updates.length > 0) {
|
|
144
244
|
this._params.sendUpdates({ updates });
|
|
145
245
|
}
|
|
@@ -148,11 +248,14 @@ export class DocumentsSynchronizer extends Resource {
|
|
|
148
248
|
private _getPendingChanges(documentId: DocumentId): Uint8Array | void {
|
|
149
249
|
const syncState = this._syncStates.get(documentId);
|
|
150
250
|
invariant(syncState, 'Sync state for document not found');
|
|
151
|
-
const handle = syncState.handle;
|
|
152
|
-
if (!handle ||
|
|
251
|
+
const handle = syncState.docQuery.handle;
|
|
252
|
+
if (!handle || syncState.docQuery.peek().state !== 'ready') {
|
|
153
253
|
return;
|
|
154
254
|
}
|
|
155
255
|
const doc = handle.doc();
|
|
256
|
+
if (!doc) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
156
259
|
const mutation = syncState.lastSentHead ? A.saveSince(doc, syncState.lastSentHead) : A.save(doc);
|
|
157
260
|
if (mutation.length === 0) {
|
|
158
261
|
return;
|
|
@@ -161,25 +264,21 @@ export class DocumentsSynchronizer extends Resource {
|
|
|
161
264
|
return mutation;
|
|
162
265
|
}
|
|
163
266
|
|
|
164
|
-
private _writeMutation(documentId: DocumentId, mutation: Uint8Array
|
|
267
|
+
private async _writeMutation(documentId: DocumentId, mutation: Uint8Array): Promise<void> {
|
|
165
268
|
if (this._lifecycleState === LifecycleState.CLOSED) {
|
|
166
269
|
return;
|
|
167
270
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if (A.equals(headsBefore, syncState!.lastSentHead)) {
|
|
180
|
-
// No new mutations were discovered on network, so we do not need to send updates from worker to client.
|
|
181
|
-
syncState!.lastSentHead = A.getHeads(syncState.handle.doc());
|
|
182
|
-
}
|
|
271
|
+
log('write mutation', { documentId });
|
|
272
|
+
|
|
273
|
+
const syncState = this._syncStates.get(documentId);
|
|
274
|
+
invariant(syncState, 'Sync state for document not found');
|
|
275
|
+
const headsBefore = A.getHeads(syncState.docQuery.handle.doc());
|
|
276
|
+
// This will update corresponding handle in the repo.
|
|
277
|
+
await this._params.automergeHost.createDoc(mutation, { documentId, preserveHistory: true });
|
|
278
|
+
|
|
279
|
+
if (A.equals(headsBefore, syncState.lastSentHead)) {
|
|
280
|
+
// No new mutations were discovered on network, so we do not need to send updates from worker to client.
|
|
281
|
+
syncState.lastSentHead = A.getHeads(syncState.docQuery.handle.doc());
|
|
183
282
|
}
|
|
184
283
|
}
|
|
185
284
|
}
|