@dxos/echo-pipeline 0.8.2 → 0.8.3-main.7f5a14c
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/{chunk-3XSXS5EX.mjs → chunk-35I6ERLG.mjs} +2 -2
- package/dist/lib/browser/chunk-35I6ERLG.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +355 -352
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +4 -4
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/node/{chunk-SG2PL5RH.cjs → chunk-JXX6LF5U.cjs} +5 -5
- package/dist/lib/node/chunk-JXX6LF5U.cjs.map +7 -0
- package/dist/lib/node/index.cjs +321 -320
- package/dist/lib/node/index.cjs.map +3 -3
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs +13 -13
- package/dist/lib/node/testing/index.cjs.map +3 -3
- package/dist/lib/node-esm/{chunk-3BZP75TJ.mjs → chunk-5BHLPT24.mjs} +2 -2
- package/dist/lib/node-esm/chunk-5BHLPT24.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +355 -352
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +4 -4
- package/dist/lib/node-esm/testing/index.mjs.map +3 -3
- package/dist/types/src/automerge/echo-network-adapter.d.ts +3 -3
- package/dist/types/src/automerge/echo-network-adapter.d.ts.map +1 -1
- package/dist/types/src/automerge/mesh-echo-replicator.d.ts.map +1 -1
- package/dist/types/src/db-host/documents-synchronizer.d.ts +1 -1
- package/dist/types/src/db-host/documents-synchronizer.d.ts.map +1 -1
- package/dist/types/src/edge/echo-edge-replicator.d.ts.map +1 -1
- package/dist/types/src/query/query-executor.d.ts.map +1 -1
- package/package.json +35 -35
- package/src/automerge/automerge-host.ts +3 -3
- package/src/automerge/automerge-repo.test.ts +48 -4
- package/src/automerge/echo-network-adapter.test.ts +7 -8
- package/src/automerge/echo-network-adapter.ts +50 -46
- package/src/automerge/mesh-echo-replicator.ts +1 -0
- package/src/db-host/data-service.ts +1 -1
- package/src/db-host/documents-iterator.ts +1 -1
- package/src/db-host/documents-synchronizer.test.ts +1 -1
- package/src/db-host/documents-synchronizer.ts +5 -3
- package/src/db-host/query-service.ts +3 -3
- package/src/edge/echo-edge-replicator.ts +2 -3
- package/src/pipeline/pipeline.ts +1 -1
- package/src/query/query-executor.ts +6 -6
- package/src/testing/change-metadata.ts +1 -1
- package/src/testing/test-replicator.ts +2 -2
- package/dist/lib/browser/chunk-3XSXS5EX.mjs.map +0 -7
- package/dist/lib/node/chunk-SG2PL5RH.cjs.map +0 -7
- package/dist/lib/node-esm/chunk-3BZP75TJ.mjs.map +0 -7
|
@@ -43,6 +43,13 @@ export type EchoNetworkAdapterParams = {
|
|
|
43
43
|
monitor?: NetworkDataMonitor;
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
+
type ConnectionEntry = {
|
|
47
|
+
isOpen: boolean;
|
|
48
|
+
connection: ReplicatorConnection;
|
|
49
|
+
reader: ReadableStreamDefaultReader<AutomergeProtocolMessage>;
|
|
50
|
+
writer: WritableStreamDefaultWriter<AutomergeProtocolMessage>;
|
|
51
|
+
};
|
|
52
|
+
|
|
46
53
|
/**
|
|
47
54
|
* Manages a set of {@link EchoReplicator} instances.
|
|
48
55
|
*/
|
|
@@ -110,6 +117,13 @@ export class EchoNetworkAdapter extends NetworkAdapter {
|
|
|
110
117
|
await this._connected.wait({ timeout: 10_000 });
|
|
111
118
|
}
|
|
112
119
|
|
|
120
|
+
public onConnectionAuthScopeChanged(peer: PeerId): void {
|
|
121
|
+
const entry = this._connections.get(peer);
|
|
122
|
+
if (entry) {
|
|
123
|
+
this._onConnectionAuthScopeChanged(entry.connection);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
113
127
|
@synchronized
|
|
114
128
|
async addReplicator(replicator: EchoReplicator): Promise<void> {
|
|
115
129
|
invariant(this._lifecycleState === LifecycleState.OPEN);
|
|
@@ -178,52 +192,57 @@ export class EchoNetworkAdapter extends NetworkAdapter {
|
|
|
178
192
|
this._send(message);
|
|
179
193
|
}
|
|
180
194
|
|
|
195
|
+
// TODO(dmaretskyi): Remove.
|
|
196
|
+
getPeersInterestedInCollection(collectionId: string): PeerId[] {
|
|
197
|
+
return Array.from(this._connections.values())
|
|
198
|
+
.map((connection) => {
|
|
199
|
+
return connection.connection.shouldSyncCollection({ collectionId })
|
|
200
|
+
? (connection.connection.peerId as PeerId)
|
|
201
|
+
: null;
|
|
202
|
+
})
|
|
203
|
+
.filter(isNonNullable);
|
|
204
|
+
}
|
|
205
|
+
|
|
181
206
|
private _send(message: Message): void {
|
|
182
207
|
const connectionEntry = this._connections.get(message.targetId);
|
|
183
208
|
if (!connectionEntry) {
|
|
184
209
|
throw new Error('Connection not found.');
|
|
185
210
|
}
|
|
186
211
|
|
|
187
|
-
const writeStart = Date.now();
|
|
188
212
|
// TODO(dmaretskyi): Find a way to enforce backpressure on AM-repo.
|
|
213
|
+
const start = Date.now();
|
|
189
214
|
connectionEntry.writer
|
|
190
215
|
.write(message as AutomergeProtocolMessage)
|
|
191
216
|
.then(() => {
|
|
192
|
-
|
|
193
|
-
this._params.monitor?.recordMessageSent(message, durationMs);
|
|
217
|
+
this._params.monitor?.recordMessageSent(message, Date.now() - start);
|
|
194
218
|
})
|
|
195
219
|
.catch((err) => {
|
|
196
220
|
if (connectionEntry.isOpen) {
|
|
197
221
|
log.catch(err);
|
|
198
222
|
}
|
|
223
|
+
|
|
199
224
|
this._params.monitor?.recordMessageSendingFailed(message);
|
|
200
225
|
});
|
|
201
226
|
}
|
|
202
227
|
|
|
203
|
-
// TODO(dmaretskyi): Remove.
|
|
204
|
-
getPeersInterestedInCollection(collectionId: string): PeerId[] {
|
|
205
|
-
return Array.from(this._connections.values())
|
|
206
|
-
.map((connection) => {
|
|
207
|
-
return connection.connection.shouldSyncCollection({ collectionId })
|
|
208
|
-
? (connection.connection.peerId as PeerId)
|
|
209
|
-
: null;
|
|
210
|
-
})
|
|
211
|
-
.filter(isNonNullable);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
228
|
private _onConnectionOpen(connection: ReplicatorConnection): void {
|
|
215
|
-
log('
|
|
229
|
+
log('connection opened', { peerId: connection.peerId });
|
|
216
230
|
invariant(!this._connections.has(connection.peerId as PeerId));
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
231
|
+
const connectionEntry: ConnectionEntry = {
|
|
232
|
+
isOpen: true,
|
|
233
|
+
connection,
|
|
234
|
+
reader: connection.readable.getReader(),
|
|
235
|
+
writer: connection.writable.getWriter(),
|
|
236
|
+
};
|
|
237
|
+
|
|
220
238
|
this._connections.set(connection.peerId as PeerId, connectionEntry);
|
|
221
239
|
|
|
240
|
+
// Read inbound messages.
|
|
222
241
|
queueMicrotask(async () => {
|
|
223
242
|
try {
|
|
224
243
|
while (true) {
|
|
225
244
|
// TODO(dmaretskyi): Find a way to enforce backpressure on AM-repo.
|
|
226
|
-
const { done, value } = await reader.read();
|
|
245
|
+
const { done, value } = await connectionEntry.reader.read();
|
|
227
246
|
if (done) {
|
|
228
247
|
break;
|
|
229
248
|
}
|
|
@@ -253,11 +272,18 @@ export class EchoNetworkAdapter extends NetworkAdapter {
|
|
|
253
272
|
this._params.monitor?.recordMessageReceived(message);
|
|
254
273
|
}
|
|
255
274
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
275
|
+
private _onConnectionClosed(connection: ReplicatorConnection): void {
|
|
276
|
+
log('connection closed', { peerId: connection.peerId });
|
|
277
|
+
const entry = this._connections.get(connection.peerId as PeerId);
|
|
278
|
+
invariant(entry);
|
|
279
|
+
|
|
280
|
+
entry.isOpen = false;
|
|
281
|
+
this.emit('peer-disconnected', { peerId: connection.peerId as PeerId });
|
|
282
|
+
this._params.monitor?.recordPeerDisconnected(connection.peerId);
|
|
283
|
+
|
|
284
|
+
void entry.reader.cancel().catch((err) => log.catch(err));
|
|
285
|
+
void entry.writer.abort().catch((err) => log.catch(err));
|
|
286
|
+
this._connections.delete(connection.peerId as PeerId);
|
|
261
287
|
}
|
|
262
288
|
|
|
263
289
|
/**
|
|
@@ -272,21 +298,6 @@ export class EchoNetworkAdapter extends NetworkAdapter {
|
|
|
272
298
|
this._emitPeerCandidate(connection);
|
|
273
299
|
}
|
|
274
300
|
|
|
275
|
-
private _onConnectionClosed(connection: ReplicatorConnection): void {
|
|
276
|
-
log('Connection closed', { peerId: connection.peerId });
|
|
277
|
-
const entry = this._connections.get(connection.peerId as PeerId);
|
|
278
|
-
invariant(entry);
|
|
279
|
-
|
|
280
|
-
entry.isOpen = false;
|
|
281
|
-
this.emit('peer-disconnected', { peerId: connection.peerId as PeerId });
|
|
282
|
-
this._params.monitor?.recordPeerDisconnected(connection.peerId);
|
|
283
|
-
|
|
284
|
-
void entry.reader.cancel().catch((err) => log.catch(err));
|
|
285
|
-
void entry.writer.abort().catch((err) => log.catch(err));
|
|
286
|
-
|
|
287
|
-
this._connections.delete(connection.peerId as PeerId);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
301
|
private _emitPeerCandidate(connection: ReplicatorConnection): void {
|
|
291
302
|
this.emit('peer-candidate', {
|
|
292
303
|
peerId: connection.peerId as PeerId,
|
|
@@ -295,13 +306,6 @@ export class EchoNetworkAdapter extends NetworkAdapter {
|
|
|
295
306
|
}
|
|
296
307
|
}
|
|
297
308
|
|
|
298
|
-
type ConnectionEntry = {
|
|
299
|
-
connection: ReplicatorConnection;
|
|
300
|
-
reader: ReadableStreamDefaultReader<AutomergeProtocolMessage>;
|
|
301
|
-
writer: WritableStreamDefaultWriter<AutomergeProtocolMessage>;
|
|
302
|
-
isOpen: boolean;
|
|
303
|
-
};
|
|
304
|
-
|
|
305
309
|
export const createEchoPeerMetadata = (): PeerMetadata =>
|
|
306
310
|
({
|
|
307
311
|
// TODO(dmaretskyi): Refactor this.
|
|
@@ -125,6 +125,7 @@ export class MeshEchoReplicator implements EchoReplicator {
|
|
|
125
125
|
documentId: params.documentId,
|
|
126
126
|
acceptDocument: remoteDocumentExists,
|
|
127
127
|
});
|
|
128
|
+
|
|
128
129
|
// If a document is not present locally return true if another peer claims to have it.
|
|
129
130
|
// Simply returning true will add the peer to "generous peers list" for this document which will
|
|
130
131
|
// start replication of the document after we receive, even if the peer is not in the corresponding space.
|
|
@@ -92,7 +92,7 @@ export class DataServiceImpl implements DataService {
|
|
|
92
92
|
const synchronizer = this._subscriptions.get(request.subscriptionId);
|
|
93
93
|
invariant(synchronizer, 'Subscription not found');
|
|
94
94
|
|
|
95
|
-
synchronizer.update(request.updates);
|
|
95
|
+
await synchronizer.update(request.updates);
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
async flush(request: FlushRequest): Promise<void> {
|
|
@@ -42,7 +42,7 @@ export const createSelectedDocumentsIterator = (automergeHost: AutomergeHost) =>
|
|
|
42
42
|
doc = A.view(doc, heads);
|
|
43
43
|
const end = Date.now();
|
|
44
44
|
if (end - begin > LOG_VIEW_OPERATION_THRESHOLD) {
|
|
45
|
-
log
|
|
45
|
+
log('Checking out document version is taking too long', {
|
|
46
46
|
duration: end - begin,
|
|
47
47
|
requestedHeads: heads,
|
|
48
48
|
originalHeads: currentHeads,
|
|
@@ -23,7 +23,7 @@ describe('DocumentsSynchronizer', () => {
|
|
|
23
23
|
});
|
|
24
24
|
await openAndClose(synchronizer);
|
|
25
25
|
|
|
26
|
-
synchronizer.update([
|
|
26
|
+
await synchronizer.update([
|
|
27
27
|
{
|
|
28
28
|
documentId: parseAutomergeUrl(generateAutomergeUrl()).documentId,
|
|
29
29
|
mutation: A.save(A.from({ text: 'hello' })),
|
|
@@ -12,6 +12,8 @@ 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
14
|
|
|
15
|
+
import { FIND_PARAMS } from '../automerge';
|
|
16
|
+
|
|
15
17
|
const MAX_UPDATE_FREQ = 10; // [updates/sec]
|
|
16
18
|
|
|
17
19
|
export type DocumentsSynchronizerParams = {
|
|
@@ -86,10 +88,10 @@ export class DocumentsSynchronizer extends Resource {
|
|
|
86
88
|
this._syncStates.clear();
|
|
87
89
|
}
|
|
88
90
|
|
|
89
|
-
update(updates: DocumentUpdate[]): void {
|
|
91
|
+
async update(updates: DocumentUpdate[]): Promise<void> {
|
|
90
92
|
for (const { documentId, mutation, isNew } of updates) {
|
|
91
93
|
if (isNew) {
|
|
92
|
-
const
|
|
94
|
+
const doc = await this._params.repo.find<DatabaseDirectory>(documentId as DocumentId, FIND_PARAMS);
|
|
93
95
|
doc.update((doc) => A.loadIncremental(doc, mutation));
|
|
94
96
|
this._startSync(doc);
|
|
95
97
|
} else {
|
|
@@ -100,7 +102,7 @@ export class DocumentsSynchronizer extends Resource {
|
|
|
100
102
|
|
|
101
103
|
private _startSync(doc: DocHandle<DatabaseDirectory>): void {
|
|
102
104
|
if (this._syncStates.has(doc.documentId)) {
|
|
103
|
-
log
|
|
105
|
+
log('Document already being synced', { documentId: doc.documentId });
|
|
104
106
|
return;
|
|
105
107
|
}
|
|
106
108
|
|
|
@@ -138,7 +138,7 @@ export class QueryServiceImpl extends Resource implements QueryService {
|
|
|
138
138
|
* Re-index all loaded documents.
|
|
139
139
|
*/
|
|
140
140
|
async reindex(): Promise<void> {
|
|
141
|
-
log
|
|
141
|
+
log('Reindexing all documents...');
|
|
142
142
|
const iterator = createDocumentsIterator(this._params.automergeHost);
|
|
143
143
|
const ids: IdToHeads = new Map();
|
|
144
144
|
for await (const documents of iterator()) {
|
|
@@ -146,11 +146,11 @@ export class QueryServiceImpl extends Resource implements QueryService {
|
|
|
146
146
|
ids.set(id, heads);
|
|
147
147
|
}
|
|
148
148
|
if (ids.size % 100 === 0) {
|
|
149
|
-
log
|
|
149
|
+
log('Collected documents...', { count: ids.size });
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
log
|
|
153
|
+
log('Marking all documents as dirty...', { count: ids.size });
|
|
154
154
|
await this._params.indexer.reindex(ids);
|
|
155
155
|
}
|
|
156
156
|
}
|
|
@@ -58,9 +58,8 @@ export class EchoEdgeReplicator implements EchoReplicator {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
async connect(context: EchoReplicatorContext): Promise<void> {
|
|
61
|
-
log
|
|
61
|
+
log('connecting...', { peerId: context.peerId, connectedSpaces: this._connectedSpaces.size });
|
|
62
62
|
this._context = context;
|
|
63
|
-
|
|
64
63
|
this._ctx = Context.default();
|
|
65
64
|
this._ctx.onDispose(
|
|
66
65
|
this._edgeConnection.onReconnected(() => {
|
|
@@ -146,7 +145,7 @@ export class EchoEdgeReplicator implements EchoReplicator {
|
|
|
146
145
|
const restartDelay =
|
|
147
146
|
Math.min(MAX_RESTART_DELAY, INITIAL_RESTART_DELAY * reconnects) + Math.random() * RESTART_DELAY_JITTER;
|
|
148
147
|
|
|
149
|
-
log
|
|
148
|
+
log('connection restart scheduled', { spaceId, reconnects, restartDelay });
|
|
150
149
|
|
|
151
150
|
restartScheduled = true;
|
|
152
151
|
scheduleTask(
|
package/src/pipeline/pipeline.ts
CHANGED
|
@@ -409,7 +409,7 @@ export class Pipeline implements PipelineAccessor {
|
|
|
409
409
|
if (err) {
|
|
410
410
|
// log.warn(err); // TODO(burdon): Feed is closed/Download was cancelled.
|
|
411
411
|
} else {
|
|
412
|
-
log
|
|
412
|
+
log('downloaded', { data }); // TODO(burdon): Never called.
|
|
413
413
|
}
|
|
414
414
|
});
|
|
415
415
|
|
|
@@ -97,6 +97,8 @@ type StepExecutionResult = {
|
|
|
97
97
|
trace: ExecutionTrace;
|
|
98
98
|
};
|
|
99
99
|
|
|
100
|
+
const TRACE_QUERY_EXECUTION = false;
|
|
101
|
+
|
|
100
102
|
/**
|
|
101
103
|
* Executes query plans against the Indexer and AutomergeHost.
|
|
102
104
|
*
|
|
@@ -186,12 +188,10 @@ export class QueryExecutor extends Resource {
|
|
|
186
188
|
workingSet[index].documentId !== item.documentId,
|
|
187
189
|
);
|
|
188
190
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
// eslint-disable-next-line no-console
|
|
194
|
-
// console.log(ExecutionTrace.format(trace));
|
|
191
|
+
if (TRACE_QUERY_EXECUTION) {
|
|
192
|
+
// eslint-disable-next-line no-console
|
|
193
|
+
console.log(ExecutionTrace.format(trace));
|
|
194
|
+
}
|
|
195
195
|
|
|
196
196
|
return {
|
|
197
197
|
changed,
|
|
@@ -16,7 +16,7 @@ const EchoMetadata = schema.getCodecForType('dxos.echo.metadata.EchoMetadata');
|
|
|
16
16
|
* Use this only for testing purposes.
|
|
17
17
|
*/
|
|
18
18
|
export const changeStorageVersionInMetadata = async (storage: Storage, version: number) => {
|
|
19
|
-
log
|
|
19
|
+
log('Changing storage version in metadata. USE ONLY FOR TESTING.');
|
|
20
20
|
const metadata = new MetadataStore(storage.createDirectory('metadata'));
|
|
21
21
|
await metadata.load();
|
|
22
22
|
const echoMetadata = metadata.metadata;
|
|
@@ -83,7 +83,7 @@ export class TestReplicationNetwork extends Resource {
|
|
|
83
83
|
const forward = new TransformStream({
|
|
84
84
|
transform: async (message, controller) => {
|
|
85
85
|
if (LOG) {
|
|
86
|
-
log
|
|
86
|
+
log('replicate', { from: peer1, to: peer2, message });
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
if (this._latency !== undefined) {
|
|
@@ -96,7 +96,7 @@ export class TestReplicationNetwork extends Resource {
|
|
|
96
96
|
const backwards = new TransformStream({
|
|
97
97
|
transform: async (message, controller) => {
|
|
98
98
|
if (LOG) {
|
|
99
|
-
log
|
|
99
|
+
log('replicate', { from: peer2, to: peer1, message });
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
if (this._latency !== undefined) {
|