@dxos/echo-pipeline 0.3.11-main.d7d4c52 → 0.3.11-main.d8b8a39
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-W3SSYW3X.mjs → chunk-MPBRK5OV.mjs} +39 -30
- package/dist/lib/browser/chunk-MPBRK5OV.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +1 -1
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +29 -7
- package/dist/lib/browser/testing/index.mjs.map +4 -4
- package/dist/lib/node/{chunk-KTFCZMAY.cjs → chunk-GJQNRSA3.cjs} +42 -33
- package/dist/lib/node/chunk-GJQNRSA3.cjs.map +7 -0
- package/dist/lib/node/index.cjs +26 -26
- package/dist/lib/node/index.cjs.map +1 -1
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs +42 -21
- package/dist/lib/node/testing/index.cjs.map +4 -4
- package/dist/types/src/automerge/automerge-host.d.ts +26 -1
- package/dist/types/src/automerge/automerge-host.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/metadata/metadata-store.d.ts +1 -3
- package/dist/types/src/metadata/metadata-store.d.ts.map +1 -1
- package/dist/types/src/testing/change-metadata.d.ts +8 -0
- package/dist/types/src/testing/change-metadata.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +1 -0
- package/dist/types/src/testing/index.d.ts.map +1 -1
- package/package.json +33 -33
- package/src/automerge/automerge-host.test.ts +212 -36
- package/src/automerge/automerge-host.ts +21 -12
- package/src/automerge/index.ts +1 -1
- package/src/metadata/metadata-store.ts +12 -2
- package/src/testing/change-metadata.ts +27 -0
- package/src/testing/index.ts +1 -0
- package/dist/lib/browser/chunk-W3SSYW3X.mjs.map +0 -7
- package/dist/lib/node/chunk-KTFCZMAY.cjs.map +0 -7
|
@@ -2,16 +2,19 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { randomBytes } from 'crypto';
|
|
5
6
|
import expect from 'expect';
|
|
7
|
+
import waitForExpect from 'wait-for-expect';
|
|
6
8
|
|
|
7
|
-
import {
|
|
9
|
+
import { asyncTimeout, sleep } from '@dxos/async';
|
|
8
10
|
import { type Message, NetworkAdapter, type PeerId, Repo } from '@dxos/automerge/automerge-repo';
|
|
9
11
|
import { invariant } from '@dxos/invariant';
|
|
10
|
-
import { log } from '@dxos/log';
|
|
11
12
|
import { StorageType, createStorage } from '@dxos/random-access-storage';
|
|
12
|
-
import {
|
|
13
|
+
import { TestBuilder as TeleportBuilder, TestPeer as TeleportPeer } from '@dxos/teleport/testing';
|
|
14
|
+
import { afterTest, describe, test } from '@dxos/test';
|
|
15
|
+
import { arrayToBuffer, bufferToArray } from '@dxos/util';
|
|
13
16
|
|
|
14
|
-
import { AutomergeHost } from './automerge-host';
|
|
17
|
+
import { AutomergeHost, AutomergeStorageAdapter, MeshNetworkAdapter } from './automerge-host';
|
|
15
18
|
|
|
16
19
|
describe('AutomergeHost', () => {
|
|
17
20
|
test('can create documents', () => {
|
|
@@ -44,13 +47,12 @@ describe('AutomergeHost', () => {
|
|
|
44
47
|
});
|
|
45
48
|
|
|
46
49
|
test('basic networking', async () => {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const clientAdapter = new TestAdapter(context, 'client');
|
|
50
|
+
const hostAdapter: TestAdapter = new TestAdapter({
|
|
51
|
+
send: (message: Message) => clientAdapter.receive(message),
|
|
52
|
+
});
|
|
53
|
+
const clientAdapter: TestAdapter = new TestAdapter({
|
|
54
|
+
send: (message: Message) => hostAdapter.receive(message),
|
|
55
|
+
});
|
|
54
56
|
|
|
55
57
|
const host = new Repo({
|
|
56
58
|
network: [hostAdapter],
|
|
@@ -60,6 +62,8 @@ describe('AutomergeHost', () => {
|
|
|
60
62
|
});
|
|
61
63
|
hostAdapter.ready();
|
|
62
64
|
clientAdapter.ready();
|
|
65
|
+
hostAdapter.peerCandidate(clientAdapter.peerId!);
|
|
66
|
+
clientAdapter.peerCandidate(hostAdapter.peerId!);
|
|
63
67
|
|
|
64
68
|
const handle = host.create();
|
|
65
69
|
const text = 'Hello world';
|
|
@@ -68,17 +72,197 @@ describe('AutomergeHost', () => {
|
|
|
68
72
|
});
|
|
69
73
|
|
|
70
74
|
const docOnClient = client.find(handle.url);
|
|
71
|
-
await asyncTimeout(docOnClient.
|
|
72
|
-
expect(docOnClient.docSync().text).toEqual(text);
|
|
75
|
+
expect((await asyncTimeout(docOnClient.doc(), 1000)).text).toEqual(text);
|
|
73
76
|
});
|
|
74
77
|
|
|
75
|
-
test('
|
|
76
|
-
|
|
78
|
+
test('recovering from a lost connection', async () => {
|
|
79
|
+
let connectionState: 'on' | 'off' = 'on';
|
|
80
|
+
|
|
81
|
+
const hostAdapter: TestAdapter = new TestAdapter({
|
|
82
|
+
send: (message: Message) => connectionState === 'on' && sleep(10).then(() => clientAdapter.receive(message)),
|
|
83
|
+
});
|
|
84
|
+
const clientAdapter: TestAdapter = new TestAdapter({
|
|
85
|
+
send: (message: Message) => connectionState === 'on' && sleep(10).then(() => hostAdapter.receive(message)),
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const host = new Repo({
|
|
89
|
+
network: [hostAdapter],
|
|
90
|
+
});
|
|
91
|
+
const client = new Repo({
|
|
92
|
+
network: [clientAdapter],
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Establish connection.
|
|
96
|
+
hostAdapter.ready();
|
|
97
|
+
clientAdapter.ready();
|
|
98
|
+
hostAdapter.peerCandidate(clientAdapter.peerId!);
|
|
99
|
+
clientAdapter.peerCandidate(hostAdapter.peerId!);
|
|
100
|
+
|
|
101
|
+
const handle = host.create();
|
|
102
|
+
const docOnClient = client.find(handle.url);
|
|
103
|
+
{
|
|
104
|
+
const sanityText = 'Hello world';
|
|
105
|
+
handle.change((doc: any) => {
|
|
106
|
+
doc.sanityText = sanityText;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
expect((await asyncTimeout(docOnClient.doc(), 1000)).sanityText).toEqual(sanityText);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Disrupt connection.
|
|
113
|
+
const offlineText = 'This has been written while the connection was off';
|
|
114
|
+
{
|
|
115
|
+
connectionState = 'off';
|
|
116
|
+
|
|
117
|
+
handle.change((doc: any) => {
|
|
118
|
+
doc.offlineText = offlineText;
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
await sleep(100);
|
|
122
|
+
expect((await asyncTimeout(docOnClient.doc(), 1000)).offlineText).toBeUndefined();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Re-establish connection.
|
|
126
|
+
const onlineText = 'This has been written after the connection was re-established';
|
|
127
|
+
{
|
|
128
|
+
connectionState = 'on';
|
|
129
|
+
hostAdapter.peerDisconnected(clientAdapter.peerId!);
|
|
130
|
+
clientAdapter.peerDisconnected(hostAdapter.peerId!);
|
|
131
|
+
hostAdapter.peerCandidate(clientAdapter.peerId!);
|
|
132
|
+
clientAdapter.peerCandidate(hostAdapter.peerId!);
|
|
133
|
+
|
|
134
|
+
handle.change((doc: any) => {
|
|
135
|
+
doc.onlineText = onlineText;
|
|
136
|
+
});
|
|
137
|
+
await sleep(100);
|
|
138
|
+
expect((await asyncTimeout(docOnClient.doc(), 1000)).onlineText).toEqual(onlineText);
|
|
139
|
+
expect((await asyncTimeout(docOnClient.doc(), 1000)).offlineText).toEqual(offlineText);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
77
142
|
|
|
78
|
-
|
|
143
|
+
test('integration test with teleport', async () => {
|
|
144
|
+
const createAutomergeRepo = () => {
|
|
145
|
+
const meshAdapter = new MeshNetworkAdapter();
|
|
146
|
+
const repo = new Repo({
|
|
147
|
+
network: [meshAdapter],
|
|
148
|
+
});
|
|
149
|
+
meshAdapter.ready();
|
|
150
|
+
return { repo, meshAdapter };
|
|
151
|
+
};
|
|
152
|
+
const peer1 = createAutomergeRepo();
|
|
153
|
+
const peer2 = createAutomergeRepo();
|
|
154
|
+
const handle = peer1.repo.create();
|
|
155
|
+
|
|
156
|
+
const teleportBuilder = new TeleportBuilder();
|
|
157
|
+
afterTest(() => teleportBuilder.destroy());
|
|
158
|
+
|
|
159
|
+
const [teleportPeer1, teleportPeer2] = teleportBuilder.createPeers({ factory: () => new TeleportPeer() });
|
|
160
|
+
{
|
|
161
|
+
// Initiate connection.
|
|
162
|
+
const [connection1, connection2] = await teleportBuilder.connect(teleportPeer1, teleportPeer2);
|
|
163
|
+
connection1.teleport.addExtension('automerge', peer1.meshAdapter.createExtension());
|
|
164
|
+
connection2.teleport.addExtension('automerge', peer2.meshAdapter.createExtension());
|
|
165
|
+
|
|
166
|
+
// Test connection.
|
|
167
|
+
const text = 'Hello world';
|
|
168
|
+
handle.change((doc: any) => {
|
|
169
|
+
doc.text = text;
|
|
170
|
+
});
|
|
171
|
+
const docOnPeer2 = peer2.repo.find(handle.url);
|
|
172
|
+
await waitForExpect(async () => expect((await asyncTimeout(docOnPeer2.doc(), 1000)).text).toEqual(text), 1000);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const offlineText = 'This has been written while the connection was off';
|
|
176
|
+
{
|
|
177
|
+
// Disconnect peers.
|
|
178
|
+
await teleportBuilder.disconnect(teleportPeer1, teleportPeer2);
|
|
179
|
+
|
|
180
|
+
// Make offline changes.
|
|
181
|
+
const offlineText = 'This has been written while the connection was off';
|
|
182
|
+
handle.change((doc: any) => {
|
|
183
|
+
doc.offlineText = offlineText;
|
|
184
|
+
});
|
|
185
|
+
const docOnPeer2 = peer2.repo.find(handle.url);
|
|
186
|
+
await sleep(100);
|
|
187
|
+
expect((await asyncTimeout(docOnPeer2.doc(), 1000)).offlineText).toBeUndefined();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
{
|
|
191
|
+
// Reconnect peers.
|
|
192
|
+
const [connection1, connection2] = await teleportBuilder.connect(teleportPeer1, teleportPeer2);
|
|
193
|
+
connection1.teleport.addExtension('automerge', peer1.meshAdapter.createExtension());
|
|
194
|
+
connection2.teleport.addExtension('automerge', peer2.meshAdapter.createExtension());
|
|
195
|
+
|
|
196
|
+
// Wait for offline changes to be synced.
|
|
197
|
+
const docOnPeer2 = peer2.repo.find(handle.url);
|
|
198
|
+
await waitForExpect(
|
|
199
|
+
async () => expect((await asyncTimeout(docOnPeer2.doc(), 1000)).offlineText).toEqual(offlineText),
|
|
200
|
+
1000,
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
// Test connection.
|
|
204
|
+
const onlineText = 'This has been written after the connection was re-established';
|
|
205
|
+
handle.change((doc: any) => {
|
|
206
|
+
doc.onlineText = onlineText;
|
|
207
|
+
});
|
|
208
|
+
await waitForExpect(
|
|
209
|
+
async () => expect((await asyncTimeout(docOnPeer2.doc(), 1000)).onlineText).toEqual(onlineText),
|
|
210
|
+
1000,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
describe('storage', () => {
|
|
216
|
+
test('load range on node', async () => {
|
|
217
|
+
const root = `/tmp/${randomBytes(16).toString('hex')}`;
|
|
218
|
+
{
|
|
219
|
+
const storage = createStorage({ type: StorageType.NODE, root });
|
|
220
|
+
const adapter = new AutomergeStorageAdapter(storage.createDirectory());
|
|
221
|
+
|
|
222
|
+
await adapter.save(['test', '1'], bufferToArray(Buffer.from('one')));
|
|
223
|
+
await adapter.save(['test', '2'], bufferToArray(Buffer.from('two')));
|
|
224
|
+
await adapter.save(['bar', '1'], bufferToArray(Buffer.from('bar')));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
{
|
|
228
|
+
const storage = createStorage({ type: StorageType.NODE, root });
|
|
229
|
+
const adapter = new AutomergeStorageAdapter(storage.createDirectory());
|
|
230
|
+
|
|
231
|
+
const range = await adapter.loadRange(['test']);
|
|
232
|
+
expect(range.map((chunk) => arrayToBuffer(chunk.data!).toString())).toEqual(['one', 'two']);
|
|
233
|
+
expect(range.map((chunk) => chunk.key)).toEqual([
|
|
234
|
+
['test', '1'],
|
|
235
|
+
['test', '2'],
|
|
236
|
+
]);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test('removeRange on node', async () => {
|
|
241
|
+
const root = `/tmp/${randomBytes(16).toString('hex')}`;
|
|
242
|
+
{
|
|
243
|
+
const storage = createStorage({ type: StorageType.NODE, root });
|
|
244
|
+
const adapter = new AutomergeStorageAdapter(storage.createDirectory());
|
|
245
|
+
await adapter.save(['test', '1'], bufferToArray(Buffer.from('one')));
|
|
246
|
+
await adapter.save(['test', '2'], bufferToArray(Buffer.from('two')));
|
|
247
|
+
await adapter.save(['bar', '1'], bufferToArray(Buffer.from('bar')));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
{
|
|
251
|
+
const storage = createStorage({ type: StorageType.NODE, root });
|
|
252
|
+
const adapter = new AutomergeStorageAdapter(storage.createDirectory());
|
|
253
|
+
await adapter.removeRange(['test']);
|
|
254
|
+
const range = await adapter.loadRange(['test']);
|
|
255
|
+
expect(range.map((chunk) => arrayToBuffer(chunk.data!).toString())).toEqual([]);
|
|
256
|
+
const range2 = await adapter.loadRange(['bar']);
|
|
257
|
+
expect(range2.map((chunk) => arrayToBuffer(chunk.data!).toString())).toEqual(['bar']);
|
|
258
|
+
expect(range2.map((chunk) => chunk.key)).toEqual([['bar', '1']]);
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
});
|
|
79
263
|
|
|
80
264
|
class TestAdapter extends NetworkAdapter {
|
|
81
|
-
constructor(
|
|
265
|
+
constructor(private readonly _params: { send: (message: Message) => void }) {
|
|
82
266
|
super();
|
|
83
267
|
}
|
|
84
268
|
|
|
@@ -90,32 +274,24 @@ class TestAdapter extends NetworkAdapter {
|
|
|
90
274
|
|
|
91
275
|
override connect(peerId: PeerId) {
|
|
92
276
|
this.peerId = peerId;
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
peerCandidate(peerId: PeerId) {
|
|
280
|
+
invariant(peerId, 'PeerId is required');
|
|
281
|
+
this.emit('peer-candidate', { peerId });
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
peerDisconnected(peerId: PeerId) {
|
|
285
|
+
invariant(peerId, 'PeerId is required');
|
|
286
|
+
this.emit('peer-disconnected', { peerId });
|
|
103
287
|
}
|
|
104
288
|
|
|
105
289
|
override send(message: Message) {
|
|
106
|
-
this.
|
|
107
|
-
.wait()
|
|
108
|
-
.then((adapter) => {
|
|
109
|
-
adapter.receive(message);
|
|
110
|
-
})
|
|
111
|
-
.catch((error) => {
|
|
112
|
-
log.catch(error);
|
|
113
|
-
});
|
|
290
|
+
this._params.send(message);
|
|
114
291
|
}
|
|
115
292
|
|
|
116
293
|
override disconnect() {
|
|
117
294
|
this.peerId = undefined;
|
|
118
|
-
this.context[this.role].reset();
|
|
119
295
|
}
|
|
120
296
|
|
|
121
297
|
receive(message: Message) {
|
|
@@ -12,12 +12,13 @@ import {
|
|
|
12
12
|
type StorageKey,
|
|
13
13
|
cbor,
|
|
14
14
|
} from '@dxos/automerge/automerge-repo';
|
|
15
|
+
import { IndexedDBStorageAdapter } from '@dxos/automerge/automerge-repo-storage-indexeddb';
|
|
15
16
|
import { Stream } from '@dxos/codec-protobuf';
|
|
16
17
|
import { invariant } from '@dxos/invariant';
|
|
17
18
|
import { log } from '@dxos/log';
|
|
18
19
|
import { type HostInfo, type SyncRepoRequest, type SyncRepoResponse } from '@dxos/protocols/proto/dxos/echo/service';
|
|
19
20
|
import { type PeerInfo } from '@dxos/protocols/proto/dxos/mesh/teleport/automerge';
|
|
20
|
-
import { type Directory } from '@dxos/random-access-storage';
|
|
21
|
+
import { StorageType, type Directory } from '@dxos/random-access-storage';
|
|
21
22
|
import { AutomergeReplicator } from '@dxos/teleport-extension-automerge-replicator';
|
|
22
23
|
import { arrayToBuffer, bufferToArray } from '@dxos/util';
|
|
23
24
|
|
|
@@ -25,12 +26,17 @@ export class AutomergeHost {
|
|
|
25
26
|
private readonly _repo: Repo;
|
|
26
27
|
private readonly _meshNetwork: MeshNetworkAdapter;
|
|
27
28
|
private readonly _clientNetwork: LocalHostNetworkAdapter;
|
|
28
|
-
private readonly _storage:
|
|
29
|
+
private readonly _storage: StorageAdapter;
|
|
29
30
|
|
|
30
31
|
constructor(storageDirectory: Directory) {
|
|
31
32
|
this._meshNetwork = new MeshNetworkAdapter();
|
|
32
33
|
this._clientNetwork = new LocalHostNetworkAdapter();
|
|
33
|
-
|
|
34
|
+
|
|
35
|
+
// TODO(mykola): Delete specific handling of IDB storage.
|
|
36
|
+
this._storage =
|
|
37
|
+
storageDirectory.type === StorageType.IDB
|
|
38
|
+
? new IndexedDBStorageAdapter(storageDirectory.path, 'data')
|
|
39
|
+
: new AutomergeStorageAdapter(storageDirectory);
|
|
34
40
|
this._repo = new Repo({
|
|
35
41
|
network: [this._clientNetwork, this._meshNetwork],
|
|
36
42
|
storage: this._storage,
|
|
@@ -166,7 +172,7 @@ class LocalHostNetworkAdapter extends NetworkAdapter {
|
|
|
166
172
|
/**
|
|
167
173
|
* Used to replicate with other peers over the network.
|
|
168
174
|
*/
|
|
169
|
-
class MeshNetworkAdapter extends NetworkAdapter {
|
|
175
|
+
export class MeshNetworkAdapter extends NetworkAdapter {
|
|
170
176
|
private readonly _extensions: Map<string, AutomergeReplicator> = new Map();
|
|
171
177
|
|
|
172
178
|
/**
|
|
@@ -227,10 +233,13 @@ class MeshNetworkAdapter extends NetworkAdapter {
|
|
|
227
233
|
this.emit('message', message);
|
|
228
234
|
},
|
|
229
235
|
onClose: async () => {
|
|
230
|
-
peerInfo
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
236
|
+
if (!peerInfo) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
this.emit('peer-disconnected', {
|
|
240
|
+
peerId: peerInfo.id as PeerId,
|
|
241
|
+
});
|
|
242
|
+
this._extensions.delete(peerInfo.id);
|
|
234
243
|
},
|
|
235
244
|
},
|
|
236
245
|
);
|
|
@@ -238,7 +247,7 @@ class MeshNetworkAdapter extends NetworkAdapter {
|
|
|
238
247
|
}
|
|
239
248
|
}
|
|
240
249
|
|
|
241
|
-
class AutomergeStorageAdapter extends StorageAdapter {
|
|
250
|
+
export class AutomergeStorageAdapter extends StorageAdapter {
|
|
242
251
|
constructor(private readonly _directory: Directory) {
|
|
243
252
|
super();
|
|
244
253
|
}
|
|
@@ -267,7 +276,7 @@ class AutomergeStorageAdapter extends StorageAdapter {
|
|
|
267
276
|
// TODO(dmaretskyi): Better deletion.
|
|
268
277
|
const filename = this._getFilename(key);
|
|
269
278
|
const file = this._directory.getOrCreateFile(filename);
|
|
270
|
-
await file.
|
|
279
|
+
await file.destroy();
|
|
271
280
|
}
|
|
272
281
|
|
|
273
282
|
override async loadRange(keyPrefix: StorageKey): Promise<Chunk[]> {
|
|
@@ -295,8 +304,8 @@ class AutomergeStorageAdapter extends StorageAdapter {
|
|
|
295
304
|
entries
|
|
296
305
|
.filter((entry) => entry.startsWith(filename))
|
|
297
306
|
.map(async (entry): Promise<void> => {
|
|
298
|
-
const file = this._directory.getOrCreateFile(
|
|
299
|
-
await file.
|
|
307
|
+
const file = this._directory.getOrCreateFile(entry);
|
|
308
|
+
await file.destroy();
|
|
300
309
|
}),
|
|
301
310
|
);
|
|
302
311
|
}
|
package/src/automerge/index.ts
CHANGED
|
@@ -48,7 +48,14 @@ export class MetadataStore {
|
|
|
48
48
|
|
|
49
49
|
public readonly update = new Event<EchoMetadata>();
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
/**
|
|
52
|
+
* @internal
|
|
53
|
+
*/
|
|
54
|
+
readonly _directory: Directory;
|
|
55
|
+
|
|
56
|
+
constructor(directory: Directory) {
|
|
57
|
+
this._directory = directory;
|
|
58
|
+
}
|
|
52
59
|
|
|
53
60
|
get metadata(): EchoMetadata {
|
|
54
61
|
return this._metadata;
|
|
@@ -94,7 +101,10 @@ export class MetadataStore {
|
|
|
94
101
|
}
|
|
95
102
|
}
|
|
96
103
|
|
|
97
|
-
|
|
104
|
+
/**
|
|
105
|
+
* @internal
|
|
106
|
+
*/
|
|
107
|
+
async _writeFile<T>(file: File, codec: Codec<T>, data: T): Promise<void> {
|
|
98
108
|
const encoded = arrayToBuffer(codec.encode(data));
|
|
99
109
|
const checksum = CRC32.buf(encoded);
|
|
100
110
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { log } from '@dxos/log';
|
|
6
|
+
import { schema } from '@dxos/protocols';
|
|
7
|
+
import type { Storage } from '@dxos/random-access-storage';
|
|
8
|
+
|
|
9
|
+
import { MetadataStore } from '../metadata';
|
|
10
|
+
|
|
11
|
+
const EchoMetadata = schema.getCodecForType('dxos.echo.metadata.EchoMetadata');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* This function will change the storage version in the metadata.
|
|
15
|
+
* This will break your storage and make it unusable.
|
|
16
|
+
* Use this only for testing purposes.
|
|
17
|
+
*/
|
|
18
|
+
export const changeStorageVersionInMetadata = async (storage: Storage, version: number) => {
|
|
19
|
+
log.info('Changing storage version in metadata. USE ONLY FOR TESTING.');
|
|
20
|
+
const metadata = new MetadataStore(storage.createDirectory('metadata'));
|
|
21
|
+
await metadata.load();
|
|
22
|
+
const echoMetadata = metadata.metadata;
|
|
23
|
+
echoMetadata.version = version;
|
|
24
|
+
const file = metadata._directory.getOrCreateFile('EchoMetadata');
|
|
25
|
+
await metadata._writeFile(file, EchoMetadata, echoMetadata);
|
|
26
|
+
await metadata._directory.flush();
|
|
27
|
+
};
|