@dxos/echo-pipeline 0.8.3 → 0.8.4-main.1068cf700f
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/neutral/chunk-FJPXA75J.mjs +235 -0
- package/dist/lib/neutral/chunk-FJPXA75J.mjs.map +7 -0
- package/dist/lib/{browser/chunk-35I6ERLG.mjs → neutral/chunk-WIQUYO7H.mjs} +300 -213
- package/dist/lib/neutral/chunk-WIQUYO7H.mjs.map +7 -0
- package/dist/lib/{browser → neutral}/filter/index.mjs +3 -2
- package/dist/lib/{browser → neutral}/index.mjs +2832 -946
- 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 +73 -224
- package/dist/lib/neutral/testing/index.mjs.map +7 -0
- package/dist/types/src/automerge/automerge-host.d.ts +53 -42
- 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 +6 -4
- package/dist/types/src/automerge/collection-synchronizer.d.ts.map +1 -1
- package/dist/types/src/automerge/echo-network-adapter.d.ts +22 -10
- package/dist/types/src/automerge/echo-network-adapter.d.ts.map +1 -1
- package/dist/types/src/automerge/echo-replicator.d.ts +37 -15
- 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/index.d.ts +1 -1
- package/dist/types/src/automerge/index.d.ts.map +1 -1
- package/dist/types/src/automerge/leveldb-storage-adapter.d.ts +5 -5
- package/dist/types/src/automerge/leveldb-storage-adapter.d.ts.map +1 -1
- package/dist/types/src/automerge/mesh-echo-replicator-connection.d.ts +9 -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 +5 -5
- package/dist/types/src/automerge/mesh-echo-replicator.d.ts.map +1 -1
- package/dist/types/src/common/codec.d.ts +1 -1
- package/dist/types/src/common/codec.d.ts.map +1 -1
- package/dist/types/src/db-host/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 +5 -4
- package/dist/types/src/db-host/data-service.d.ts.map +1 -1
- package/dist/types/src/db-host/database-root.d.ts.map +1 -1
- package/dist/types/src/db-host/documents-synchronizer.d.ts +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 +44 -23
- 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 +16 -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 +14 -9
- 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 +2 -1
- 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 +8 -6
- package/dist/types/src/edge/echo-edge-replicator.d.ts.map +1 -1
- package/dist/types/src/filter/filter-match.d.ts +4 -1
- package/dist/types/src/filter/filter-match.d.ts.map +1 -1
- package/dist/types/src/metadata/metadata-store.d.ts +1 -1
- package/dist/types/src/metadata/metadata-store.d.ts.map +1 -1
- package/dist/types/src/pipeline/pipeline.d.ts +3 -3
- package/dist/types/src/pipeline/pipeline.d.ts.map +1 -1
- package/dist/types/src/query/errors.d.ts +21 -9
- package/dist/types/src/query/errors.d.ts.map +1 -1
- package/dist/types/src/query/plan.d.ts +57 -5
- package/dist/types/src/query/plan.d.ts.map +1 -1
- package/dist/types/src/query/query-executor.d.ts +49 -10
- package/dist/types/src/query/query-executor.d.ts.map +1 -1
- package/dist/types/src/query/query-planner.d.ts +9 -0
- package/dist/types/src/query/query-planner.d.ts.map +1 -1
- package/dist/types/src/space/admission-discovery-extension.d.ts.map +1 -1
- package/dist/types/src/space/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 +3 -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 +6 -6
- package/dist/types/src/space/space-protocol.d.ts.map +1 -1
- package/dist/types/src/space/space.d.ts +4 -4
- 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-agent-builder.d.ts +2 -2
- package/dist/types/src/testing/test-agent-builder.d.ts.map +1 -1
- package/dist/types/src/testing/test-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 +14 -13
- package/dist/types/src/testing/test-replicator.d.ts.map +1 -1
- package/dist/types/src/util.d.ts +1 -1
- package/dist/types/src/util.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +53 -45
- package/src/automerge/automerge-host.test.ts +55 -24
- package/src/automerge/automerge-host.ts +399 -126
- package/src/automerge/automerge-repo.test.ts +214 -46
- package/src/automerge/collection-synchronizer.test.ts +22 -15
- package/src/automerge/collection-synchronizer.ts +57 -22
- package/src/automerge/echo-data-monitor.ts +1 -1
- package/src/automerge/echo-network-adapter.test.ts +3 -3
- package/src/automerge/echo-network-adapter.ts +73 -28
- package/src/automerge/echo-replicator.ts +40 -15
- package/src/automerge/heads-store.ts +42 -3
- package/src/automerge/index.ts +1 -1
- package/src/automerge/leveldb-storage-adapter.ts +11 -11
- package/src/automerge/mesh-echo-replicator-connection.ts +12 -8
- package/src/automerge/mesh-echo-replicator.ts +15 -11
- package/src/automerge/storage-adapter.test.ts +1 -1
- package/src/common/space-id.ts +1 -1
- package/src/db-host/automerge-data-source.test.ts +292 -0
- package/src/db-host/automerge-data-source.ts +144 -0
- package/src/db-host/data-service.ts +19 -20
- package/src/db-host/database-root.ts +2 -2
- package/src/db-host/documents-synchronizer.test.ts +73 -13
- package/src/db-host/documents-synchronizer.ts +58 -44
- package/src/db-host/echo-host.ts +194 -84
- package/src/db-host/index.ts +1 -0
- package/src/db-host/local-queue-service.ts +147 -0
- package/src/db-host/query-service.ts +28 -95
- package/src/db-host/queue-data-source.ts +132 -0
- package/src/db-host/queue-service.test.ts +145 -0
- package/src/db-host/space-state-manager.ts +6 -2
- package/src/db-host/stub.ts +28 -0
- package/src/edge/echo-edge-replicator.test.ts +8 -6
- package/src/edge/echo-edge-replicator.ts +124 -42
- package/src/filter/filter-match.test.ts +58 -32
- package/src/filter/filter-match.ts +163 -15
- package/src/metadata/metadata-store.ts +8 -5
- package/src/pipeline/pipeline-stress.test.ts +4 -2
- package/src/pipeline/pipeline.test.ts +3 -2
- package/src/pipeline/pipeline.ts +10 -7
- package/src/query/errors.ts +3 -1
- package/src/query/plan.ts +75 -5
- package/src/query/query-executor.ts +812 -117
- package/src/query/query-planner.test.ts +652 -34
- package/src/query/query-planner.ts +276 -13
- package/src/space/admission-discovery-extension.ts +3 -3
- package/src/space/auth.ts +6 -6
- package/src/space/control-pipeline.test.ts +4 -3
- package/src/space/control-pipeline.ts +11 -8
- package/src/space/space-manager.browser.test.ts +1 -1
- package/src/space/space-manager.ts +11 -10
- package/src/space/space-protocol.browser.test.ts +2 -2
- package/src/space/space-protocol.test.ts +3 -2
- package/src/space/space-protocol.ts +17 -14
- package/src/space/space.test.ts +1 -1
- package/src/space/space.ts +6 -5
- package/src/testing/index.ts +0 -2
- package/src/testing/test-agent-builder.ts +4 -3
- package/src/testing/test-network-adapter.ts +16 -3
- package/src/testing/test-replicator.ts +23 -18
- package/src/util.ts +7 -3
- package/dist/lib/browser/chunk-35I6ERLG.mjs.map +0 -7
- package/dist/lib/browser/chunk-CGS2ULMK.mjs +0 -11
- package/dist/lib/browser/chunk-CGS2ULMK.mjs.map +0 -7
- package/dist/lib/browser/chunk-TQJTKNMS.mjs +0 -126
- package/dist/lib/browser/chunk-TQJTKNMS.mjs.map +0 -7
- 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/chunk-HOPOFWAL.cjs +0 -147
- package/dist/lib/node/chunk-HOPOFWAL.cjs.map +0 -7
- package/dist/lib/node/chunk-JXX6LF5U.cjs +0 -2084
- package/dist/lib/node/chunk-JXX6LF5U.cjs.map +0 -7
- package/dist/lib/node/chunk-Q7SFCCGT.cjs +0 -33
- package/dist/lib/node/chunk-Q7SFCCGT.cjs.map +0 -7
- package/dist/lib/node/filter/index.cjs +0 -32
- package/dist/lib/node/filter/index.cjs.map +0 -7
- package/dist/lib/node/index.cjs +0 -4699
- package/dist/lib/node/index.cjs.map +0 -7
- package/dist/lib/node/meta.json +0 -1
- package/dist/lib/node/testing/index.cjs +0 -753
- package/dist/lib/node/testing/index.cjs.map +0 -7
- package/dist/lib/node-esm/chunk-5BHLPT24.mjs +0 -2067
- package/dist/lib/node-esm/chunk-5BHLPT24.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-RVK35BS7.mjs +0 -126
- package/dist/lib/node-esm/chunk-RVK35BS7.mjs.map +0 -7
- package/dist/lib/node-esm/filter/index.mjs +0 -11
- package/dist/lib/node-esm/filter/index.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -4743
- 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 -742
- 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,21 +3,22 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { next as A, type Heads } from '@automerge/automerge';
|
|
6
|
-
import { type
|
|
6
|
+
import { type DocHandle, type DocumentId } from '@automerge/automerge-repo';
|
|
7
7
|
|
|
8
8
|
import { UpdateScheduler } from '@dxos/async';
|
|
9
|
-
import { Resource } from '@dxos/context';
|
|
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';
|
|
14
15
|
|
|
15
|
-
import {
|
|
16
|
+
import type { AutomergeHost } from '../automerge';
|
|
16
17
|
|
|
17
18
|
const MAX_UPDATE_FREQ = 10; // [updates/sec]
|
|
18
19
|
|
|
19
|
-
export type
|
|
20
|
-
|
|
20
|
+
export type DocumentsSynchronizerProps = {
|
|
21
|
+
automergeHost: AutomergeHost;
|
|
21
22
|
sendUpdates: (updates: BatchedDocumentUpdates) => void;
|
|
22
23
|
};
|
|
23
24
|
|
|
@@ -27,6 +28,9 @@ interface DocSyncState {
|
|
|
27
28
|
clearSubscriptions?: () => void;
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
const WRAP_AROUND_RETRY_LIMIT = 3;
|
|
32
|
+
const WRAP_AROUND_RETRY_INITIAL_DELAY = 100; // [ms]
|
|
33
|
+
|
|
30
34
|
/**
|
|
31
35
|
* Manages a connection and replication between worker's Automerge Repo and the client's Repo.
|
|
32
36
|
*/
|
|
@@ -43,30 +47,38 @@ export class DocumentsSynchronizer extends Resource {
|
|
|
43
47
|
*/
|
|
44
48
|
private _sendUpdatesJob?: UpdateScheduler = undefined;
|
|
45
49
|
|
|
46
|
-
constructor(private readonly _params:
|
|
50
|
+
constructor(private readonly _params: DocumentsSynchronizerProps) {
|
|
47
51
|
super();
|
|
48
52
|
}
|
|
49
53
|
|
|
50
|
-
addDocuments(documentIds: DocumentId[]
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
54
|
+
async addDocuments(documentIds: DocumentId[]): Promise<void> {
|
|
55
|
+
await Promise.all(
|
|
56
|
+
documentIds.map(async (documentId) => {
|
|
57
|
+
try {
|
|
58
|
+
await retry(
|
|
59
|
+
{ count: WRAP_AROUND_RETRY_LIMIT, delayMs: WRAP_AROUND_RETRY_INITIAL_DELAY, exponent: 2 },
|
|
60
|
+
async () => {
|
|
61
|
+
try {
|
|
62
|
+
log('loading document', { documentId });
|
|
63
|
+
const doc = await this._params.automergeHost.loadDoc<DatabaseDirectory>(
|
|
64
|
+
Context.default(),
|
|
65
|
+
documentId as DocumentId,
|
|
66
|
+
{ fetchFromNetwork: true },
|
|
67
|
+
);
|
|
68
|
+
this._startSync(doc);
|
|
69
|
+
this._pendingUpdates.add(doc.documentId);
|
|
70
|
+
this._sendUpdatesJob!.trigger();
|
|
71
|
+
} catch (err) {
|
|
72
|
+
log.warn('failed to load document', { err });
|
|
73
|
+
throw err;
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
} catch (err) {
|
|
78
|
+
log.catch(err);
|
|
79
|
+
}
|
|
80
|
+
}),
|
|
81
|
+
);
|
|
70
82
|
}
|
|
71
83
|
|
|
72
84
|
removeDocuments(documentIds: DocumentId[]): void {
|
|
@@ -89,18 +101,14 @@ export class DocumentsSynchronizer extends Resource {
|
|
|
89
101
|
}
|
|
90
102
|
|
|
91
103
|
async update(updates: DocumentUpdate[]): Promise<void> {
|
|
92
|
-
for (const { documentId, mutation
|
|
93
|
-
|
|
94
|
-
const doc = await this._params.repo.find<DatabaseDirectory>(documentId as DocumentId, FIND_PARAMS);
|
|
95
|
-
doc.update((doc) => A.loadIncremental(doc, mutation));
|
|
96
|
-
this._startSync(doc);
|
|
97
|
-
} else {
|
|
98
|
-
this._writeMutation(documentId as DocumentId, mutation);
|
|
99
|
-
}
|
|
104
|
+
for (const { documentId, mutation } of updates) {
|
|
105
|
+
await this._writeMutation(documentId as DocumentId, mutation);
|
|
100
106
|
}
|
|
107
|
+
// TODO(mykola): This should not be required.
|
|
108
|
+
await this._params.automergeHost.flush({ documentIds: updates.map(({ documentId }) => documentId as DocumentId) });
|
|
101
109
|
}
|
|
102
110
|
|
|
103
|
-
private _startSync(doc: DocHandle<DatabaseDirectory>)
|
|
111
|
+
private _startSync(doc: DocHandle<DatabaseDirectory>) {
|
|
104
112
|
if (this._syncStates.has(doc.documentId)) {
|
|
105
113
|
log('Document already being synced', { documentId: doc.documentId });
|
|
106
114
|
return;
|
|
@@ -109,6 +117,7 @@ export class DocumentsSynchronizer extends Resource {
|
|
|
109
117
|
const syncState: DocSyncState = { handle: doc };
|
|
110
118
|
this._subscribeForChanges(syncState);
|
|
111
119
|
this._syncStates.set(doc.documentId, syncState);
|
|
120
|
+
return syncState;
|
|
112
121
|
}
|
|
113
122
|
|
|
114
123
|
_subscribeForChanges(syncState: DocSyncState): void {
|
|
@@ -157,16 +166,21 @@ export class DocumentsSynchronizer extends Resource {
|
|
|
157
166
|
return mutation;
|
|
158
167
|
}
|
|
159
168
|
|
|
160
|
-
private _writeMutation(documentId: DocumentId, mutation: Uint8Array): void {
|
|
169
|
+
private async _writeMutation(documentId: DocumentId, mutation: Uint8Array): Promise<void> {
|
|
170
|
+
if (this._lifecycleState === LifecycleState.CLOSED) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
log('write mutation', { documentId });
|
|
174
|
+
|
|
161
175
|
const syncState = this._syncStates.get(documentId);
|
|
162
176
|
invariant(syncState, 'Sync state for document not found');
|
|
163
|
-
syncState.handle.
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
177
|
+
const headsBefore = A.getHeads(syncState.handle.doc());
|
|
178
|
+
// This will update corresponding handle in the repo.
|
|
179
|
+
await this._params.automergeHost.createDoc(mutation, { documentId, preserveHistory: true });
|
|
180
|
+
|
|
181
|
+
if (A.equals(headsBefore, syncState.lastSentHead)) {
|
|
182
|
+
// No new mutations were discovered on network, so we do not need to send updates from worker to client.
|
|
183
|
+
syncState.lastSentHead = A.getHeads(syncState.handle.doc());
|
|
184
|
+
}
|
|
171
185
|
}
|
|
172
186
|
}
|
package/src/db-host/echo-host.ts
CHANGED
|
@@ -2,66 +2,63 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
type Repo,
|
|
11
|
-
} from '@automerge/automerge-repo';
|
|
12
|
-
|
|
13
|
-
import { LifecycleState, Resource, type Context } from '@dxos/context';
|
|
5
|
+
import { type AnyDocumentId, type AutomergeUrl, type DocHandle, type DocumentId } from '@automerge/automerge-repo';
|
|
6
|
+
import type * as SqlClient from '@effect/sql/SqlClient';
|
|
7
|
+
|
|
8
|
+
import { DeferredTask, sleep } from '@dxos/async';
|
|
9
|
+
import { Context, LifecycleState, Resource } from '@dxos/context';
|
|
14
10
|
import { todo } from '@dxos/debug';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
11
|
+
import { type DatabaseDirectory, SpaceDocVersion, createIdFromSpaceKey } from '@dxos/echo-protocol';
|
|
12
|
+
import { RuntimeProvider } from '@dxos/effect';
|
|
13
|
+
import { FeedStore } from '@dxos/feed';
|
|
14
|
+
import { IndexEngine } from '@dxos/index-core';
|
|
17
15
|
import { invariant } from '@dxos/invariant';
|
|
18
16
|
import { type PublicKey, type SpaceId } from '@dxos/keys';
|
|
19
17
|
import { type LevelDB } from '@dxos/kv-store';
|
|
20
|
-
import {
|
|
18
|
+
import { log } from '@dxos/log';
|
|
19
|
+
import { type FeedProtocol } from '@dxos/protocols';
|
|
20
|
+
import type * as SqlTransaction from '@dxos/sql-sqlite/SqlTransaction';
|
|
21
21
|
import { trace } from '@dxos/tracing';
|
|
22
22
|
|
|
23
|
-
import { DataServiceImpl } from './data-service';
|
|
24
|
-
import { type DatabaseRoot } from './database-root';
|
|
25
|
-
import { createSelectedDocumentsIterator } from './documents-iterator';
|
|
26
|
-
import { QueryServiceImpl } from './query-service';
|
|
27
|
-
import { SpaceStateManager } from './space-state-manager';
|
|
28
23
|
import {
|
|
29
24
|
AutomergeHost,
|
|
30
|
-
|
|
31
|
-
EchoDataMonitor,
|
|
32
|
-
deriveCollectionIdFromSpaceId,
|
|
33
|
-
type LoadDocOptions,
|
|
25
|
+
type AutomergeReplicator,
|
|
34
26
|
type CreateDocOptions,
|
|
35
|
-
|
|
27
|
+
EchoDataMonitor,
|
|
36
28
|
type EchoDataStats,
|
|
29
|
+
type LoadDocOptions,
|
|
37
30
|
type PeerIdProvider,
|
|
38
31
|
type RootDocumentSpaceKeyProvider,
|
|
32
|
+
deriveCollectionIdFromSpaceId,
|
|
39
33
|
} from '../automerge';
|
|
40
34
|
|
|
41
|
-
|
|
35
|
+
import { AutomergeDataSource } from './automerge-data-source';
|
|
36
|
+
import { DataServiceImpl } from './data-service';
|
|
37
|
+
import { type DatabaseRoot } from './database-root';
|
|
38
|
+
import { LocalQueueServiceImpl } from './local-queue-service';
|
|
39
|
+
import { QueryServiceImpl } from './query-service';
|
|
40
|
+
import { QueueDataSource } from './queue-data-source';
|
|
41
|
+
import { SpaceStateManager } from './space-state-manager';
|
|
42
|
+
import { QueueServiceStub } from './stub';
|
|
43
|
+
|
|
44
|
+
export type EchoHostProps = {
|
|
45
|
+
kv: LevelDB;
|
|
46
|
+
peerIdProvider?: PeerIdProvider;
|
|
47
|
+
getSpaceKeyByRootDocumentId?: RootDocumentSpaceKeyProvider;
|
|
48
|
+
|
|
49
|
+
runtime: RuntimeProvider.RuntimeProvider<SqlClient.SqlClient | SqlTransaction.SqlTransaction>;
|
|
50
|
+
|
|
42
51
|
/**
|
|
43
|
-
*
|
|
52
|
+
* Use SQLite-backed local-first durable queues.
|
|
53
|
+
* Otherwise defaults to in-memory impl.
|
|
44
54
|
*/
|
|
45
|
-
|
|
55
|
+
localQueues?: boolean;
|
|
46
56
|
|
|
47
57
|
/**
|
|
58
|
+
* This peer is allowed to assign positions (global-order) to items appended to the queue.
|
|
48
59
|
* @default false
|
|
49
60
|
*/
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const DEFAULT_INDEXING_CONFIG: EchoHostIndexingConfig = {
|
|
54
|
-
// TODO(dmaretskyi): Disabled by default since embedding generation is expensive.
|
|
55
|
-
fullText: false,
|
|
56
|
-
vector: false,
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
export type EchoHostParams = {
|
|
60
|
-
kv: LevelDB;
|
|
61
|
-
peerIdProvider?: PeerIdProvider;
|
|
62
|
-
getSpaceKeyByRootDocumentId?: RootDocumentSpaceKeyProvider;
|
|
63
|
-
|
|
64
|
-
indexing?: Partial<EchoHostIndexingConfig>;
|
|
61
|
+
assignQueuePositions?: boolean;
|
|
65
62
|
};
|
|
66
63
|
|
|
67
64
|
/**
|
|
@@ -71,51 +68,64 @@ export type EchoHostParams = {
|
|
|
71
68
|
* Can sync with pluggable data replicators.
|
|
72
69
|
*/
|
|
73
70
|
export class EchoHost extends Resource {
|
|
74
|
-
private readonly _indexMetadataStore: IndexMetadataStore;
|
|
75
|
-
private readonly _indexer: Indexer;
|
|
76
71
|
private readonly _automergeHost: AutomergeHost;
|
|
77
72
|
private readonly _queryService: QueryServiceImpl;
|
|
78
73
|
private readonly _dataService: DataServiceImpl;
|
|
79
74
|
private readonly _spaceStateManager = new SpaceStateManager();
|
|
80
75
|
private readonly _echoDataMonitor: EchoDataMonitor;
|
|
81
76
|
|
|
82
|
-
|
|
83
|
-
|
|
77
|
+
private readonly _automergeDataSource: AutomergeDataSource;
|
|
78
|
+
private readonly _indexEngine: IndexEngine;
|
|
79
|
+
private readonly _runtime: RuntimeProvider.RuntimeProvider<SqlClient.SqlClient | SqlTransaction.SqlTransaction>;
|
|
80
|
+
private readonly _feedStore?: FeedStore;
|
|
81
|
+
private readonly _queueDataSource?: QueueDataSource;
|
|
82
|
+
|
|
83
|
+
private _updateIndexes!: DeferredTask;
|
|
84
|
+
|
|
85
|
+
private _queuesService: FeedProtocol.QueueService;
|
|
86
|
+
|
|
87
|
+
private _indexesUpToDate = false;
|
|
84
88
|
|
|
85
|
-
|
|
89
|
+
constructor({
|
|
90
|
+
kv,
|
|
91
|
+
localQueues,
|
|
92
|
+
peerIdProvider,
|
|
93
|
+
getSpaceKeyByRootDocumentId,
|
|
94
|
+
runtime,
|
|
95
|
+
assignQueuePositions = false,
|
|
96
|
+
}: EchoHostProps) {
|
|
97
|
+
super();
|
|
86
98
|
|
|
87
|
-
this._indexMetadataStore = new IndexMetadataStore({ db: kv.sublevel('index-metadata') });
|
|
88
99
|
this._echoDataMonitor = new EchoDataMonitor();
|
|
89
100
|
this._automergeHost = new AutomergeHost({
|
|
90
101
|
db: kv,
|
|
91
102
|
dataMonitor: this._echoDataMonitor,
|
|
92
|
-
indexMetadataStore: this._indexMetadataStore,
|
|
93
103
|
peerIdProvider,
|
|
94
104
|
getSpaceKeyByRootDocumentId,
|
|
95
105
|
});
|
|
96
106
|
|
|
97
|
-
this.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
});
|
|
107
|
+
this._runtime = runtime;
|
|
108
|
+
this._automergeDataSource = new AutomergeDataSource(this._automergeHost);
|
|
109
|
+
|
|
110
|
+
if (localQueues) {
|
|
111
|
+
this._feedStore = new FeedStore({ assignPositions: assignQueuePositions, localActorId: crypto.randomUUID() });
|
|
112
|
+
this._queueDataSource = new QueueDataSource({
|
|
113
|
+
feedStore: this._feedStore,
|
|
114
|
+
runtime: this._runtime,
|
|
115
|
+
getSpaceIds: () => this._spaceStateManager.spaceIds,
|
|
116
|
+
});
|
|
117
|
+
this._queuesService = new LocalQueueServiceImpl(runtime, this._feedStore);
|
|
118
|
+
} else {
|
|
119
|
+
this._queuesService = new QueueServiceStub();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// SQLite-based index engine for all queries.
|
|
123
|
+
this._indexEngine = new IndexEngine();
|
|
115
124
|
|
|
116
125
|
this._queryService = new QueryServiceImpl({
|
|
117
126
|
automergeHost: this._automergeHost,
|
|
118
|
-
|
|
127
|
+
indexEngine: this._indexEngine,
|
|
128
|
+
runtime: this._runtime,
|
|
119
129
|
spaceStateManager: this._spaceStateManager,
|
|
120
130
|
});
|
|
121
131
|
|
|
@@ -123,7 +133,9 @@ export class EchoHost extends Resource {
|
|
|
123
133
|
automergeHost: this._automergeHost,
|
|
124
134
|
spaceStateManager: this._spaceStateManager,
|
|
125
135
|
updateIndexes: async () => {
|
|
126
|
-
|
|
136
|
+
do {
|
|
137
|
+
await this._updateIndexes.runBlocking();
|
|
138
|
+
} while (!this._indexesUpToDate);
|
|
127
139
|
},
|
|
128
140
|
});
|
|
129
141
|
|
|
@@ -168,6 +180,10 @@ export class EchoHost extends Resource {
|
|
|
168
180
|
});
|
|
169
181
|
}
|
|
170
182
|
|
|
183
|
+
get spaceIds(): SpaceId[] {
|
|
184
|
+
return this._spaceStateManager.spaceIds;
|
|
185
|
+
}
|
|
186
|
+
|
|
171
187
|
get queryService(): QueryServiceImpl {
|
|
172
188
|
return this._queryService;
|
|
173
189
|
}
|
|
@@ -176,29 +192,45 @@ export class EchoHost extends Resource {
|
|
|
176
192
|
return this._dataService;
|
|
177
193
|
}
|
|
178
194
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
*/
|
|
182
|
-
get automergeRepo(): Repo {
|
|
183
|
-
return this._automergeHost.repo;
|
|
195
|
+
get queuesService(): FeedProtocol.QueueService {
|
|
196
|
+
return this._queuesService;
|
|
184
197
|
}
|
|
185
198
|
|
|
186
199
|
get roots(): ReadonlyMap<DocumentId, DatabaseRoot> {
|
|
187
200
|
return this._spaceStateManager.roots;
|
|
188
201
|
}
|
|
189
202
|
|
|
203
|
+
get feedStore(): FeedStore | undefined {
|
|
204
|
+
return this._feedStore;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Index engine for queries.
|
|
209
|
+
*/
|
|
210
|
+
get indexEngine(): IndexEngine {
|
|
211
|
+
return this._indexEngine;
|
|
212
|
+
}
|
|
213
|
+
|
|
190
214
|
protected override async _open(ctx: Context): Promise<void> {
|
|
191
215
|
await this._automergeHost.open();
|
|
192
|
-
await this._indexer.open(ctx);
|
|
193
216
|
await this._queryService.open(ctx);
|
|
194
217
|
await this._spaceStateManager.open(ctx);
|
|
195
218
|
|
|
219
|
+
if (this._feedStore) {
|
|
220
|
+
await RuntimeProvider.runPromise(this._runtime)(this._feedStore.migrate());
|
|
221
|
+
this._feedStore.onNewBlocks.on(this._ctx, () => {
|
|
222
|
+
this._queryService.invalidateQueries();
|
|
223
|
+
this._updateIndexes.schedule();
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
await RuntimeProvider.runPromise(this._runtime)(this._indexEngine.migrate());
|
|
228
|
+
this._updateIndexes = new DeferredTask(this._ctx, this._runUpdateIndexes);
|
|
229
|
+
|
|
196
230
|
this._spaceStateManager.spaceDocumentListUpdated.on(this._ctx, (e) => {
|
|
197
231
|
if (e.previousRootId) {
|
|
198
232
|
void this._automergeHost.clearLocalCollectionState(deriveCollectionIdFromSpaceId(e.spaceId, e.previousRootId));
|
|
199
233
|
}
|
|
200
|
-
// TODO(yaroslav): remove collection without spaceRootId after release (production<->staging interop)
|
|
201
|
-
void this._automergeHost.updateLocalCollectionState(deriveCollectionIdFromSpaceId(e.spaceId), e.documentIds);
|
|
202
234
|
void this._automergeHost.updateLocalCollectionState(
|
|
203
235
|
deriveCollectionIdFromSpaceId(e.spaceId, e.spaceRootId),
|
|
204
236
|
e.documentIds,
|
|
@@ -206,13 +238,14 @@ export class EchoHost extends Resource {
|
|
|
206
238
|
});
|
|
207
239
|
this._automergeHost.documentsSaved.on(this._ctx, () => {
|
|
208
240
|
this._queryService.invalidateQueries();
|
|
241
|
+
this._updateIndexes.schedule();
|
|
209
242
|
});
|
|
243
|
+
this._updateIndexes.schedule();
|
|
210
244
|
}
|
|
211
245
|
|
|
212
246
|
protected override async _close(ctx: Context): Promise<void> {
|
|
213
247
|
await this._queryService.close(ctx);
|
|
214
248
|
await this._spaceStateManager.close(ctx);
|
|
215
|
-
await this._indexer.close(ctx);
|
|
216
249
|
await this._automergeHost.close();
|
|
217
250
|
}
|
|
218
251
|
|
|
@@ -220,14 +253,16 @@ export class EchoHost extends Resource {
|
|
|
220
253
|
* Flush all pending writes to the underlying storage.
|
|
221
254
|
*/
|
|
222
255
|
async flush(): Promise<void> {
|
|
223
|
-
await this._automergeHost.
|
|
256
|
+
await this._automergeHost.flush();
|
|
224
257
|
}
|
|
225
258
|
|
|
226
259
|
/**
|
|
227
260
|
* Perform any pending index updates.
|
|
228
261
|
*/
|
|
229
262
|
async updateIndexes(): Promise<void> {
|
|
230
|
-
|
|
263
|
+
do {
|
|
264
|
+
await this._updateIndexes.runBlocking();
|
|
265
|
+
} while (!this._indexesUpToDate);
|
|
231
266
|
}
|
|
232
267
|
|
|
233
268
|
/**
|
|
@@ -244,7 +279,7 @@ export class EchoHost extends Resource {
|
|
|
244
279
|
/**
|
|
245
280
|
* Create new persisted document.
|
|
246
281
|
*/
|
|
247
|
-
createDoc<T>(initialValue?: T, opts?: CreateDocOptions): DocHandle<T
|
|
282
|
+
async createDoc<T>(initialValue?: T, opts?: CreateDocOptions): Promise<DocHandle<T>> {
|
|
248
283
|
return this._automergeHost.createDoc(initialValue, opts);
|
|
249
284
|
}
|
|
250
285
|
|
|
@@ -255,7 +290,7 @@ export class EchoHost extends Resource {
|
|
|
255
290
|
invariant(this._lifecycleState === LifecycleState.OPEN);
|
|
256
291
|
const spaceId = await createIdFromSpaceKey(spaceKey);
|
|
257
292
|
|
|
258
|
-
const automergeRoot = this._automergeHost.createDoc<DatabaseDirectory>({
|
|
293
|
+
const automergeRoot = await this._automergeHost.createDoc<DatabaseDirectory>({
|
|
259
294
|
version: SpaceDocVersion.CURRENT,
|
|
260
295
|
access: { spaceKey: spaceKey.toHex() },
|
|
261
296
|
|
|
@@ -272,30 +307,105 @@ export class EchoHost extends Resource {
|
|
|
272
307
|
// TODO(dmaretskyi): Change to document id.
|
|
273
308
|
async openSpaceRoot(spaceId: SpaceId, automergeUrl: AutomergeUrl): Promise<DatabaseRoot> {
|
|
274
309
|
invariant(this._lifecycleState === LifecycleState.OPEN);
|
|
275
|
-
const handle = await this._automergeHost.
|
|
310
|
+
const handle = await this._automergeHost.loadDoc<DatabaseDirectory>(Context.default(), automergeUrl, {
|
|
311
|
+
fetchFromNetwork: true,
|
|
312
|
+
});
|
|
276
313
|
await handle.whenReady();
|
|
277
314
|
|
|
278
315
|
return this._spaceStateManager.assignRootToSpace(spaceId, handle);
|
|
279
316
|
}
|
|
280
317
|
|
|
281
318
|
// TODO(dmaretskyi): Change to document id.
|
|
282
|
-
async closeSpaceRoot(
|
|
319
|
+
async closeSpaceRoot(_automergeUrl: AutomergeUrl): Promise<void> {
|
|
283
320
|
todo();
|
|
284
321
|
}
|
|
285
322
|
|
|
286
323
|
/**
|
|
287
324
|
* Install data replicator.
|
|
288
325
|
*/
|
|
289
|
-
async addReplicator(replicator:
|
|
326
|
+
async addReplicator(replicator: AutomergeReplicator): Promise<void> {
|
|
290
327
|
await this._automergeHost.addReplicator(replicator);
|
|
291
328
|
}
|
|
292
329
|
|
|
293
330
|
/**
|
|
294
331
|
* Remove data replicator.
|
|
295
332
|
*/
|
|
296
|
-
async removeReplicator(replicator:
|
|
333
|
+
async removeReplicator(replicator: AutomergeReplicator): Promise<void> {
|
|
297
334
|
await this._automergeHost.removeReplicator(replicator);
|
|
298
335
|
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Run collection sync for the given space.
|
|
339
|
+
* Does not wait for the sync to complete.
|
|
340
|
+
*/
|
|
341
|
+
async runCollectionSync(spaceId: SpaceId) {
|
|
342
|
+
const root = this._spaceStateManager.getRootBySpaceId(spaceId);
|
|
343
|
+
if (!root) {
|
|
344
|
+
throw new Error(`Space not found: ${spaceId}`);
|
|
345
|
+
}
|
|
346
|
+
this._automergeHost.refreshCollection(deriveCollectionIdFromSpaceId(spaceId, root.documentId));
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
private _runUpdateIndexes = async (): Promise<void> => {
|
|
350
|
+
let totalUpdated = 0;
|
|
351
|
+
let totalDone = true;
|
|
352
|
+
|
|
353
|
+
{
|
|
354
|
+
performance.mark('indexEngine.update.automerge:start');
|
|
355
|
+
const { updated, done } = await this._indexEngine
|
|
356
|
+
.update(this._automergeDataSource, { spaceId: null, limit: 50 })
|
|
357
|
+
.pipe(RuntimeProvider.runPromise(this._runtime));
|
|
358
|
+
totalUpdated += updated;
|
|
359
|
+
totalDone &&= done;
|
|
360
|
+
performance.measure('Index Automerge', {
|
|
361
|
+
start: 'indexEngine.update.automerge:start',
|
|
362
|
+
detail: {
|
|
363
|
+
devtools: {
|
|
364
|
+
dataType: 'track-entry',
|
|
365
|
+
track: 'Indexing',
|
|
366
|
+
trackGroup: 'ECHO', // Group related tracks together
|
|
367
|
+
color: 'tertiary-dark',
|
|
368
|
+
properties: [['count', updated]],
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (this._queueDataSource) {
|
|
375
|
+
performance.mark('indexEngine.update.queue:start');
|
|
376
|
+
const { updated, done } = await this._indexEngine
|
|
377
|
+
.update(this._queueDataSource, { spaceId: null, limit: 50 })
|
|
378
|
+
.pipe(RuntimeProvider.runPromise(this._runtime));
|
|
379
|
+
totalUpdated += updated;
|
|
380
|
+
totalDone &&= done;
|
|
381
|
+
performance.measure('Index Queues', {
|
|
382
|
+
start: 'indexEngine.update.queue:start',
|
|
383
|
+
detail: {
|
|
384
|
+
devtools: {
|
|
385
|
+
dataType: 'track-entry',
|
|
386
|
+
track: 'Indexing',
|
|
387
|
+
trackGroup: 'ECHO',
|
|
388
|
+
color: 'tertiary-dark',
|
|
389
|
+
properties: [['count', updated]],
|
|
390
|
+
},
|
|
391
|
+
},
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
log.verbose('indexEngine update completed', { updated: totalUpdated, done: totalDone });
|
|
396
|
+
await sleep(1);
|
|
397
|
+
if (!totalDone) {
|
|
398
|
+
this._indexesUpToDate = false;
|
|
399
|
+
this._updateIndexes!.schedule();
|
|
400
|
+
} else {
|
|
401
|
+
this._indexesUpToDate = true;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Invalidate queries after index update completes so queries can see newly indexed data.
|
|
405
|
+
if (totalUpdated > 0) {
|
|
406
|
+
this._queryService.invalidateQueries();
|
|
407
|
+
}
|
|
408
|
+
};
|
|
299
409
|
}
|
|
300
410
|
|
|
301
411
|
export type { EchoDataStats };
|