@dxos/echo-pipeline 0.6.0 → 0.6.1-main.09a92b0
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-HS77A4I4.mjs → chunk-A2LCXJVD.mjs} +16 -1
- package/dist/lib/browser/chunk-A2LCXJVD.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +323 -211
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +1 -1
- package/dist/lib/node/{chunk-Y5U7UXEL.cjs → chunk-GHBIMYZK.cjs} +19 -4
- package/dist/lib/node/chunk-GHBIMYZK.cjs.map +7 -0
- package/dist/lib/node/index.cjs +364 -258
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs +11 -11
- package/dist/types/src/automerge/automerge-host.d.ts +13 -9
- package/dist/types/src/automerge/automerge-host.d.ts.map +1 -1
- package/dist/types/src/automerge/echo-network-adapter.d.ts +2 -2
- package/dist/types/src/automerge/echo-network-adapter.d.ts.map +1 -1
- package/dist/types/src/automerge/echo-network-adapter.test.d.ts +2 -0
- package/dist/types/src/automerge/echo-network-adapter.test.d.ts.map +1 -0
- package/dist/types/src/automerge/echo-replicator.d.ts +5 -6
- package/dist/types/src/automerge/echo-replicator.d.ts.map +1 -1
- package/dist/types/src/automerge/heads-store.d.ts +13 -0
- package/dist/types/src/automerge/heads-store.d.ts.map +1 -0
- package/dist/types/src/automerge/mesh-echo-replicator-connection.d.ts +35 -0
- package/dist/types/src/automerge/mesh-echo-replicator-connection.d.ts.map +1 -0
- package/dist/types/src/automerge/mesh-echo-replicator.d.ts +2 -2
- package/dist/types/src/automerge/mesh-echo-replicator.d.ts.map +1 -1
- package/dist/types/src/db-host/data-service.d.ts +4 -2
- package/dist/types/src/db-host/data-service.d.ts.map +1 -1
- package/package.json +33 -33
- package/src/automerge/automerge-doc-loader.ts +1 -1
- package/src/automerge/automerge-host.test.ts +34 -17
- package/src/automerge/automerge-host.ts +61 -17
- package/src/automerge/automerge-repo.test.ts +76 -1
- package/src/automerge/echo-network-adapter.test.ts +131 -0
- package/src/automerge/echo-network-adapter.ts +10 -6
- package/src/automerge/echo-replicator.ts +6 -9
- package/src/automerge/heads-store.ts +39 -0
- package/src/automerge/mesh-echo-replicator-connection.ts +130 -0
- package/src/automerge/mesh-echo-replicator.ts +15 -123
- package/src/db-host/data-service.ts +22 -2
- package/dist/lib/browser/chunk-HS77A4I4.mjs.map +0 -7
- package/dist/lib/node/chunk-Y5U7UXEL.cjs.map +0 -7
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import type { Heads } from '@dxos/automerge/automerge';
|
|
6
|
+
import type { DocumentId } from '@dxos/automerge/automerge-repo';
|
|
7
|
+
import { headsEncoding } from '@dxos/indexing';
|
|
8
|
+
import type { BatchLevel, SublevelDB } from '@dxos/kv-store';
|
|
9
|
+
|
|
10
|
+
export type HeadsStoreParams = {
|
|
11
|
+
db: SublevelDB;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export class HeadsStore {
|
|
15
|
+
private readonly _db: SublevelDB;
|
|
16
|
+
|
|
17
|
+
constructor({ db }: HeadsStoreParams) {
|
|
18
|
+
this._db = db;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
setHeads(documentId: DocumentId, heads: Heads, batch: BatchLevel) {
|
|
22
|
+
batch.put<DocumentId, Heads>(documentId, heads, {
|
|
23
|
+
sublevel: this._db,
|
|
24
|
+
keyEncoding: 'utf8',
|
|
25
|
+
valueEncoding: headsEncoding,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async getHeads(documentId: DocumentId): Promise<Heads | undefined> {
|
|
30
|
+
try {
|
|
31
|
+
return await this._db.get<DocumentId, Heads>(documentId, { keyEncoding: 'utf8', valueEncoding: headsEncoding });
|
|
32
|
+
} catch (err: any) {
|
|
33
|
+
if (err.notFound) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
throw err;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { cbor, type Message } from '@dxos/automerge/automerge-repo';
|
|
6
|
+
import { Resource } from '@dxos/context';
|
|
7
|
+
import { invariant } from '@dxos/invariant';
|
|
8
|
+
import { type PublicKey } from '@dxos/keys';
|
|
9
|
+
import { log } from '@dxos/log';
|
|
10
|
+
import { AutomergeReplicator, type AutomergeReplicatorFactory } from '@dxos/teleport-extension-automerge-replicator';
|
|
11
|
+
|
|
12
|
+
import type { ReplicatorConnection, ShouldAdvertiseParams } from './echo-replicator';
|
|
13
|
+
|
|
14
|
+
const DEFAULT_FACTORY: AutomergeReplicatorFactory = (params) => new AutomergeReplicator(...params);
|
|
15
|
+
|
|
16
|
+
export type MeshReplicatorConnectionParams = {
|
|
17
|
+
ownPeerId: string;
|
|
18
|
+
onRemoteConnected: () => void;
|
|
19
|
+
onRemoteDisconnected: () => void;
|
|
20
|
+
shouldAdvertise: (params: ShouldAdvertiseParams) => Promise<boolean>;
|
|
21
|
+
replicatorFactory?: AutomergeReplicatorFactory;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export class MeshReplicatorConnection extends Resource implements ReplicatorConnection {
|
|
25
|
+
public readable: ReadableStream<Message>;
|
|
26
|
+
public writable: WritableStream<Message>;
|
|
27
|
+
public remoteDeviceKey: PublicKey | null = null;
|
|
28
|
+
|
|
29
|
+
public readonly replicatorExtension: AutomergeReplicator;
|
|
30
|
+
|
|
31
|
+
private _remotePeerId: string | null = null;
|
|
32
|
+
private _isEnabled = false;
|
|
33
|
+
|
|
34
|
+
constructor(private readonly _params: MeshReplicatorConnectionParams) {
|
|
35
|
+
super();
|
|
36
|
+
|
|
37
|
+
let readableStreamController!: ReadableStreamDefaultController<Message>;
|
|
38
|
+
this.readable = new ReadableStream<Message>({
|
|
39
|
+
start: (controller) => {
|
|
40
|
+
readableStreamController = controller;
|
|
41
|
+
this._ctx.onDispose(() => controller.close());
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
this.writable = new WritableStream<Message>({
|
|
46
|
+
write: async (message: Message, controller) => {
|
|
47
|
+
invariant(this._isEnabled, 'Writing to a disabled connection');
|
|
48
|
+
try {
|
|
49
|
+
await this.replicatorExtension.sendSyncMessage({ payload: cbor.encode(message) });
|
|
50
|
+
} catch (err) {
|
|
51
|
+
controller.error(err);
|
|
52
|
+
this._disconnectIfEnabled();
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const createAutomergeReplicator = this._params.replicatorFactory ?? DEFAULT_FACTORY;
|
|
58
|
+
this.replicatorExtension = createAutomergeReplicator([
|
|
59
|
+
{
|
|
60
|
+
peerId: this._params.ownPeerId,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
onStartReplication: async (info, remotePeerId /** Teleport ID */) => {
|
|
64
|
+
// Note: We store only one extension per peer.
|
|
65
|
+
// There can be a case where two connected peers have more than one teleport connection between them
|
|
66
|
+
// and each of them uses different teleport connections to send messages.
|
|
67
|
+
// It works because we receive messages from all teleport connections and Automerge Repo dedup them.
|
|
68
|
+
// TODO(mykola): Use only one teleport connection per peer.
|
|
69
|
+
|
|
70
|
+
// TODO(dmaretskyi): Critical bug.
|
|
71
|
+
// - two peers get connected via swarm 1
|
|
72
|
+
// - they get connected via swarm 2
|
|
73
|
+
// - swarm 1 gets disconnected
|
|
74
|
+
// - automerge repo thinks that peer 2 got disconnected even though swarm 2 is still active
|
|
75
|
+
|
|
76
|
+
this.remoteDeviceKey = remotePeerId;
|
|
77
|
+
|
|
78
|
+
// Set automerge id.
|
|
79
|
+
this._remotePeerId = info.id;
|
|
80
|
+
|
|
81
|
+
log('onStartReplication', { id: info.id, thisPeerId: this.peerId, remotePeerId: remotePeerId.toHex() });
|
|
82
|
+
|
|
83
|
+
this._params.onRemoteConnected();
|
|
84
|
+
},
|
|
85
|
+
onSyncMessage: async ({ payload }) => {
|
|
86
|
+
if (!this._isEnabled) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const message = cbor.decode(payload) as Message;
|
|
90
|
+
// Note: automerge Repo dedup messages.
|
|
91
|
+
readableStreamController.enqueue(message);
|
|
92
|
+
},
|
|
93
|
+
onClose: async () => {
|
|
94
|
+
this._disconnectIfEnabled();
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
]);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private _disconnectIfEnabled() {
|
|
101
|
+
if (this._isEnabled) {
|
|
102
|
+
this._params.onRemoteDisconnected();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
get peerId(): string {
|
|
107
|
+
invariant(this._remotePeerId != null, 'Remote peer has not connected yet.');
|
|
108
|
+
return this._remotePeerId;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async shouldAdvertise(params: ShouldAdvertiseParams): Promise<boolean> {
|
|
112
|
+
return this._params.shouldAdvertise(params);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Start exchanging messages with the remote peer.
|
|
117
|
+
* Call after the remote peer has connected.
|
|
118
|
+
*/
|
|
119
|
+
enable() {
|
|
120
|
+
invariant(this._remotePeerId != null, 'Remote peer has not connected yet.');
|
|
121
|
+
this._isEnabled = true;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Stop exchanging messages with the remote peer.
|
|
126
|
+
*/
|
|
127
|
+
disable() {
|
|
128
|
+
this._isEnabled = false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -2,20 +2,17 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { type Message, cbor } from '@dxos/automerge/automerge-repo';
|
|
6
|
-
import { Resource } from '@dxos/context';
|
|
7
5
|
import { invariant } from '@dxos/invariant';
|
|
8
6
|
import { PublicKey } from '@dxos/keys';
|
|
9
7
|
import { log } from '@dxos/log';
|
|
10
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
type AutomergeReplicator,
|
|
10
|
+
type AutomergeReplicatorFactory,
|
|
11
|
+
} from '@dxos/teleport-extension-automerge-replicator';
|
|
11
12
|
import { ComplexMap, ComplexSet, defaultMap } from '@dxos/util';
|
|
12
13
|
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
type EchoReplicatorContext,
|
|
16
|
-
type ReplicatorConnection,
|
|
17
|
-
type ShouldAdvertizeParams,
|
|
18
|
-
} from './echo-replicator';
|
|
14
|
+
import { type EchoReplicator, type EchoReplicatorContext, type ShouldAdvertiseParams } from './echo-replicator';
|
|
15
|
+
import { MeshReplicatorConnection } from './mesh-echo-replicator-connection';
|
|
19
16
|
|
|
20
17
|
// TODO(dmaretskyi): Move out of @dxos/echo-pipeline.
|
|
21
18
|
|
|
@@ -50,11 +47,12 @@ export class MeshEchoReplicator implements EchoReplicator {
|
|
|
50
47
|
this._context = null;
|
|
51
48
|
}
|
|
52
49
|
|
|
53
|
-
createExtension(): AutomergeReplicator {
|
|
50
|
+
createExtension(extensionFactory?: AutomergeReplicatorFactory): AutomergeReplicator {
|
|
54
51
|
invariant(this._context);
|
|
55
52
|
|
|
56
53
|
const connection: MeshReplicatorConnection = new MeshReplicatorConnection({
|
|
57
54
|
ownPeerId: this._context.peerId,
|
|
55
|
+
replicatorFactory: extensionFactory,
|
|
58
56
|
onRemoteConnected: async () => {
|
|
59
57
|
log('onRemoteConnected', { peerId: connection.peerId });
|
|
60
58
|
invariant(this._context);
|
|
@@ -64,18 +62,18 @@ export class MeshEchoReplicator implements EchoReplicator {
|
|
|
64
62
|
} else {
|
|
65
63
|
this._connectionsPerPeer.set(connection.peerId, connection);
|
|
66
64
|
this._context.onConnectionOpen(connection);
|
|
67
|
-
|
|
65
|
+
connection.enable();
|
|
68
66
|
}
|
|
69
67
|
},
|
|
70
68
|
onRemoteDisconnected: async () => {
|
|
71
69
|
log('onRemoteDisconnected', { peerId: connection.peerId });
|
|
72
70
|
this._context?.onConnectionClosed(connection);
|
|
73
71
|
this._connectionsPerPeer.delete(connection.peerId);
|
|
74
|
-
|
|
72
|
+
connection.disable();
|
|
75
73
|
this._connections.delete(connection);
|
|
76
74
|
},
|
|
77
|
-
|
|
78
|
-
log('
|
|
75
|
+
shouldAdvertise: async (params: ShouldAdvertiseParams) => {
|
|
76
|
+
log('shouldAdvertise', { peerId: connection.peerId, documentId: params.documentId });
|
|
79
77
|
invariant(this._context);
|
|
80
78
|
try {
|
|
81
79
|
const spaceKey = await this._context.getContainingSpaceForDocument(params.documentId);
|
|
@@ -123,116 +121,10 @@ export class MeshEchoReplicator implements EchoReplicator {
|
|
|
123
121
|
defaultMap(this._authorizedDevices, spaceKey, () => new ComplexSet(PublicKey.hash)).add(deviceKey);
|
|
124
122
|
for (const connection of this._connections) {
|
|
125
123
|
if (connection.remoteDeviceKey && connection.remoteDeviceKey.equals(deviceKey)) {
|
|
126
|
-
this.
|
|
124
|
+
if (this._connectionsPerPeer.has(connection.peerId)) {
|
|
125
|
+
this._context?.onConnectionAuthScopeChanged(connection);
|
|
126
|
+
}
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
|
-
|
|
132
|
-
type MeshReplicatorConnectionParams = {
|
|
133
|
-
ownPeerId: string;
|
|
134
|
-
onRemoteConnected: () => Promise<void>;
|
|
135
|
-
onRemoteDisconnected: () => Promise<void>;
|
|
136
|
-
shouldAdvertize: (params: ShouldAdvertizeParams) => Promise<boolean>;
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
class MeshReplicatorConnection extends Resource implements ReplicatorConnection {
|
|
140
|
-
public readable: ReadableStream<Message>;
|
|
141
|
-
public writable: WritableStream<Message>;
|
|
142
|
-
public remoteDeviceKey: PublicKey | null = null;
|
|
143
|
-
|
|
144
|
-
public readonly replicatorExtension: AutomergeReplicator;
|
|
145
|
-
|
|
146
|
-
private _remotePeerId: string | null = null;
|
|
147
|
-
private _isEnabled = false;
|
|
148
|
-
|
|
149
|
-
constructor(private readonly _params: MeshReplicatorConnectionParams) {
|
|
150
|
-
super();
|
|
151
|
-
|
|
152
|
-
let readableStreamController!: ReadableStreamDefaultController<Message>;
|
|
153
|
-
this.readable = new ReadableStream<Message>({
|
|
154
|
-
start: (controller) => {
|
|
155
|
-
readableStreamController = controller;
|
|
156
|
-
this._ctx.onDispose(() => controller.close());
|
|
157
|
-
},
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
this.writable = new WritableStream<Message>({
|
|
161
|
-
write: async (message: Message, controller) => {
|
|
162
|
-
// TODO(dmaretskyi): Show we block on RPC completing here?
|
|
163
|
-
this.replicatorExtension.sendSyncMessage({ payload: cbor.encode(message) }).catch((err) => {
|
|
164
|
-
controller.error(err);
|
|
165
|
-
});
|
|
166
|
-
},
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
this.replicatorExtension = new AutomergeReplicator(
|
|
170
|
-
{
|
|
171
|
-
peerId: this._params.ownPeerId,
|
|
172
|
-
},
|
|
173
|
-
{
|
|
174
|
-
onStartReplication: async (info, remotePeerId /** Teleport ID */) => {
|
|
175
|
-
// Note: We store only one extension per peer.
|
|
176
|
-
// There can be a case where two connected peers have more than one teleport connection between them
|
|
177
|
-
// and each of them uses different teleport connections to send messages.
|
|
178
|
-
// It works because we receive messages from all teleport connections and Automerge Repo dedup them.
|
|
179
|
-
// TODO(mykola): Use only one teleport connection per peer.
|
|
180
|
-
|
|
181
|
-
// TODO(dmaretskyi): Critical bug.
|
|
182
|
-
// - two peers get connected via swarm 1
|
|
183
|
-
// - they get connected via swarm 2
|
|
184
|
-
// - swarm 1 gets disconnected
|
|
185
|
-
// - automerge repo thinks that peer 2 got disconnected even though swarm 2 is still active
|
|
186
|
-
|
|
187
|
-
this.remoteDeviceKey = remotePeerId;
|
|
188
|
-
|
|
189
|
-
// Set automerge id.
|
|
190
|
-
this._remotePeerId = info.id;
|
|
191
|
-
|
|
192
|
-
log('onStartReplication', { id: info.id, thisPeerId: this.peerId, remotePeerId: remotePeerId.toHex() });
|
|
193
|
-
|
|
194
|
-
await this._params.onRemoteConnected();
|
|
195
|
-
},
|
|
196
|
-
onSyncMessage: async ({ payload }) => {
|
|
197
|
-
if (!this._isEnabled) {
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
const message = cbor.decode(payload) as Message;
|
|
201
|
-
// Note: automerge Repo dedup messages.
|
|
202
|
-
readableStreamController.enqueue(message);
|
|
203
|
-
},
|
|
204
|
-
onClose: async () => {
|
|
205
|
-
if (!this._isEnabled) {
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
await this._params.onRemoteDisconnected();
|
|
209
|
-
},
|
|
210
|
-
},
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
get peerId(): string {
|
|
215
|
-
invariant(this._remotePeerId != null, 'Remote peer has not connected yet.');
|
|
216
|
-
return this._remotePeerId;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
async shouldAdvertize(params: ShouldAdvertizeParams): Promise<boolean> {
|
|
220
|
-
return this._params.shouldAdvertize(params);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Start exchanging messages with the remote peer.
|
|
225
|
-
* Call after the remote peer has connected.
|
|
226
|
-
*/
|
|
227
|
-
async enable() {
|
|
228
|
-
invariant(this._remotePeerId != null, 'Remote peer has not connected yet.');
|
|
229
|
-
this._isEnabled = true;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Stop exchanging messages with the remote peer.
|
|
234
|
-
*/
|
|
235
|
-
async disable() {
|
|
236
|
-
this._isEnabled = false;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
@@ -2,20 +2,23 @@
|
|
|
2
2
|
// Copyright 2021 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { type Stream } from '@dxos/codec-protobuf';
|
|
5
|
+
import { type RequestOptions, type Stream } from '@dxos/codec-protobuf';
|
|
6
6
|
import {
|
|
7
7
|
type DataService,
|
|
8
8
|
type EchoEvent,
|
|
9
9
|
type FlushRequest,
|
|
10
|
+
type GetDocumentHeadsRequest,
|
|
11
|
+
type GetDocumentHeadsResponse,
|
|
10
12
|
type HostInfo,
|
|
11
13
|
type MutationReceipt,
|
|
14
|
+
type ReIndexHeadsRequest,
|
|
12
15
|
type SubscribeRequest,
|
|
13
16
|
type SyncRepoRequest,
|
|
14
17
|
type SyncRepoResponse,
|
|
15
18
|
type WriteRequest,
|
|
16
19
|
} from '@dxos/protocols/proto/dxos/echo/service';
|
|
17
20
|
|
|
18
|
-
import { type AutomergeHost } from '../automerge';
|
|
21
|
+
import { type AutomergeHost, type DocumentId } from '../automerge';
|
|
19
22
|
|
|
20
23
|
/**
|
|
21
24
|
* Data sync between client and services.
|
|
@@ -49,4 +52,21 @@ export class DataServiceImpl implements DataService {
|
|
|
49
52
|
sendSyncMessage(request: SyncRepoRequest): Promise<void> {
|
|
50
53
|
return this._automergeHost.sendSyncMessage(request);
|
|
51
54
|
}
|
|
55
|
+
|
|
56
|
+
async getDocumentHeads(request: GetDocumentHeadsRequest): Promise<GetDocumentHeadsResponse> {
|
|
57
|
+
const states = await Promise.all(
|
|
58
|
+
request.documentIds?.map(async (documentId): Promise<GetDocumentHeadsResponse.DocState> => {
|
|
59
|
+
const heads = await this._automergeHost.getHeads(documentId as DocumentId);
|
|
60
|
+
return {
|
|
61
|
+
documentId,
|
|
62
|
+
heads,
|
|
63
|
+
};
|
|
64
|
+
}) ?? [],
|
|
65
|
+
);
|
|
66
|
+
return { states };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async reIndexHeads(request: ReIndexHeadsRequest, options?: RequestOptions): Promise<void> {
|
|
70
|
+
await this._automergeHost.reIndexHeads((request.documentIds ?? []) as DocumentId[]);
|
|
71
|
+
}
|
|
52
72
|
}
|