@peers-app/peers-device 0.0.1
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/chunk-download-manager.d.ts +14 -0
- package/dist/chunk-download-manager.js +132 -0
- package/dist/chunk-download-manager.js.map +1 -0
- package/dist/chunk-download-manager.test.d.ts +1 -0
- package/dist/chunk-download-manager.test.js +166 -0
- package/dist/chunk-download-manager.test.js.map +1 -0
- package/dist/chunk-download.types.d.ts +15 -0
- package/dist/chunk-download.types.js +3 -0
- package/dist/chunk-download.types.js.map +1 -0
- package/dist/connection-manager/connection-manager.d.ts +32 -0
- package/dist/connection-manager/connection-manager.js +235 -0
- package/dist/connection-manager/connection-manager.js.map +1 -0
- package/dist/connection-manager/least-preferred-connection.d.ts +3 -0
- package/dist/connection-manager/least-preferred-connection.js +45 -0
- package/dist/connection-manager/least-preferred-connection.js.map +1 -0
- package/dist/connection-manager/least-preferred-connection.test.d.ts +1 -0
- package/dist/connection-manager/least-preferred-connection.test.js +300 -0
- package/dist/connection-manager/least-preferred-connection.test.js.map +1 -0
- package/dist/device-sync.d.ts +78 -0
- package/dist/device-sync.js +541 -0
- package/dist/device-sync.js.map +1 -0
- package/dist/device-sync.test.d.ts +1 -0
- package/dist/device-sync.test.js +1618 -0
- package/dist/device-sync.test.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/json-diff.d.ts +9 -0
- package/dist/json-diff.js +137 -0
- package/dist/json-diff.js.map +1 -0
- package/dist/local.data-source.d.ts +19 -0
- package/dist/local.data-source.js +146 -0
- package/dist/local.data-source.js.map +1 -0
- package/dist/local.data-source.test.d.ts +1 -0
- package/dist/local.data-source.test.js +68 -0
- package/dist/local.data-source.test.js.map +1 -0
- package/dist/main.d.ts +4 -0
- package/dist/main.js +138 -0
- package/dist/main.js.map +1 -0
- package/dist/packages.tracked-data-source.d.ts +10 -0
- package/dist/packages.tracked-data-source.js +28 -0
- package/dist/packages.tracked-data-source.js.map +1 -0
- package/dist/pvars.tracked-data-source.d.ts +5 -0
- package/dist/pvars.tracked-data-source.js +54 -0
- package/dist/pvars.tracked-data-source.js.map +1 -0
- package/dist/tracked-data-source.d.ts +46 -0
- package/dist/tracked-data-source.js +387 -0
- package/dist/tracked-data-source.js.map +1 -0
- package/dist/tracked-data-source.test.d.ts +1 -0
- package/dist/tracked-data-source.test.js +825 -0
- package/dist/tracked-data-source.test.js.map +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1,1618 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const peers_sdk_1 = require("@peers-app/peers-sdk");
|
|
4
|
+
const lodash_1 = require("lodash");
|
|
5
|
+
const zod_1 = require("zod");
|
|
6
|
+
const device_sync_1 = require("./device-sync");
|
|
7
|
+
const local_data_source_1 = require("./local.data-source");
|
|
8
|
+
const tracked_data_source_1 = require("./tracked-data-source");
|
|
9
|
+
jest.setTimeout(240_000);
|
|
10
|
+
describe("peer-device", () => {
|
|
11
|
+
const allPeers = [];
|
|
12
|
+
afterEach(async () => {
|
|
13
|
+
await (0, peers_sdk_1.sleep)(1);
|
|
14
|
+
await Promise.all(allPeers.map(peer => peer.dispose()));
|
|
15
|
+
});
|
|
16
|
+
afterAll(async () => {
|
|
17
|
+
await Promise.all(allPeers.map(peer => peer.dispose()));
|
|
18
|
+
await (0, peers_sdk_1.sleep)(200);
|
|
19
|
+
console.log("List changes count: ", device_sync_1.DeviceSync.listChangesCount);
|
|
20
|
+
console.log("Apply changes count: ", device_sync_1.DeviceSync.applyChangesCount);
|
|
21
|
+
console.log("Network info count: ", device_sync_1.DeviceSync.getNetworkInfoCount);
|
|
22
|
+
console.log("Elections count: ", device_sync_1.DeviceSync.electPreferredConnectionsCount);
|
|
23
|
+
console.log("Sync with remote device count: ", device_sync_1.DeviceSync.syncWithRemoteDeviceCount);
|
|
24
|
+
console.log("Total count: ", device_sync_1.DeviceSync.listChangesCount + device_sync_1.DeviceSync.applyChangesCount + device_sync_1.DeviceSync.getNetworkInfoCount + device_sync_1.DeviceSync.electPreferredConnectionsCount);
|
|
25
|
+
});
|
|
26
|
+
const notesSchema = zod_1.z.object({
|
|
27
|
+
noteId: zod_1.z.string(),
|
|
28
|
+
title: zod_1.z.string(),
|
|
29
|
+
completed: zod_1.z.boolean().optional(),
|
|
30
|
+
number: zod_1.z.number().optional(),
|
|
31
|
+
string: zod_1.z.string().optional(),
|
|
32
|
+
date: zod_1.z.date().optional(),
|
|
33
|
+
});
|
|
34
|
+
const notesTableName = "notes-test";
|
|
35
|
+
const notesMetaData = {
|
|
36
|
+
name: notesTableName,
|
|
37
|
+
description: 'A table for notes',
|
|
38
|
+
primaryKeyName: 'noteId',
|
|
39
|
+
fields: (0, peers_sdk_1.schemaToFields)(notesSchema),
|
|
40
|
+
};
|
|
41
|
+
function getNotesTable(peer) {
|
|
42
|
+
return peer.tableContainer.getTable(notesMetaData, notesSchema);
|
|
43
|
+
}
|
|
44
|
+
let dbId = 1;
|
|
45
|
+
function buildPeer(userId = (0, peers_sdk_1.newid)(), deviceId = (0, peers_sdk_1.newid)()) {
|
|
46
|
+
const db = new local_data_source_1.DBLocal(':memory:');
|
|
47
|
+
const changeTrackingTable = new peers_sdk_1.ChangeTrackingTable({ db });
|
|
48
|
+
// Create a mock UserContext and DataContext for testing
|
|
49
|
+
const mockUserContext = {
|
|
50
|
+
userId: () => userId,
|
|
51
|
+
deviceId: () => deviceId,
|
|
52
|
+
dataSourceFactory: (metaData, schema) => {
|
|
53
|
+
const sqlDS = new peers_sdk_1.SQLDataSource(db, metaData, schema);
|
|
54
|
+
if (metaData.localOnly) {
|
|
55
|
+
return sqlDS;
|
|
56
|
+
}
|
|
57
|
+
const trackedDS = new tracked_data_source_1.TrackedDataSource(sqlDS, changeTrackingTable);
|
|
58
|
+
return trackedDS;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const dataContext = new peers_sdk_1.DataContext(mockUserContext);
|
|
62
|
+
const peer = new device_sync_1.DeviceSync(dataContext, changeTrackingTable);
|
|
63
|
+
allPeers.push(peer);
|
|
64
|
+
return peer;
|
|
65
|
+
}
|
|
66
|
+
function buildRemotePeer({ userId = (0, peers_sdk_1.newid)(), deviceId = (0, peers_sdk_1.newid)(), errorRate = 0, latencyMs = 0 } = {}) {
|
|
67
|
+
const peer = buildPeer(userId, deviceId);
|
|
68
|
+
return {
|
|
69
|
+
peer,
|
|
70
|
+
remotePeer: {
|
|
71
|
+
deviceId: peer.deviceId,
|
|
72
|
+
userId: peer.userId,
|
|
73
|
+
role: peer.role,
|
|
74
|
+
listChanges: async (filter, opts) => {
|
|
75
|
+
latencyMs && await (0, peers_sdk_1.sleep)(latencyMs);
|
|
76
|
+
if (errorRate && Math.random() < errorRate) {
|
|
77
|
+
throw new Error("Simulated error");
|
|
78
|
+
}
|
|
79
|
+
return peer.listChanges(filter, opts);
|
|
80
|
+
},
|
|
81
|
+
getNetworkInfo: async () => {
|
|
82
|
+
latencyMs && await (0, peers_sdk_1.sleep)(latencyMs);
|
|
83
|
+
if (errorRate && Math.random() < errorRate) {
|
|
84
|
+
throw new Error("Simulated error");
|
|
85
|
+
}
|
|
86
|
+
return peer.getNetworkInfo();
|
|
87
|
+
},
|
|
88
|
+
notifyOfChanges: async (deviceId, timestampLastApplied) => {
|
|
89
|
+
latencyMs && await (0, peers_sdk_1.sleep)(latencyMs);
|
|
90
|
+
if (errorRate && Math.random() < errorRate) {
|
|
91
|
+
throw new Error("Simulated error");
|
|
92
|
+
}
|
|
93
|
+
return peer.notifyOfChanges(deviceId, timestampLastApplied);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
it("should have notes tables", async () => {
|
|
99
|
+
const peer = buildPeer();
|
|
100
|
+
const peerNotes = getNotesTable(peer);
|
|
101
|
+
expect(peerNotes).toBeDefined();
|
|
102
|
+
const note = await peerNotes.save({ noteId: (0, peers_sdk_1.newid)(), title: "Hello, World!" });
|
|
103
|
+
const notes = await peerNotes.list();
|
|
104
|
+
expect(notes.length).toBe(1);
|
|
105
|
+
expect(notes).toEqual([note]);
|
|
106
|
+
});
|
|
107
|
+
it("should sync with another peer", async () => {
|
|
108
|
+
const peer1 = buildPeer();
|
|
109
|
+
const peer1Notes = getNotesTable(peer1);
|
|
110
|
+
await peer1Notes.save({ noteId: (0, peers_sdk_1.newid)(), title: "Hello, World!" });
|
|
111
|
+
let notes1 = await peer1Notes.list();
|
|
112
|
+
expect(notes1.length).toBe(1);
|
|
113
|
+
const peer2 = buildPeer();
|
|
114
|
+
const peer2Notes = getNotesTable(peer2);
|
|
115
|
+
let notes2 = await peer2Notes.list();
|
|
116
|
+
expect(notes2.length).toBe(0);
|
|
117
|
+
await peer2.addConnection(peer1);
|
|
118
|
+
notes2 = await peer2Notes.list();
|
|
119
|
+
expect(notes2.length).toBe(1);
|
|
120
|
+
await peer1.addConnection(peer2);
|
|
121
|
+
notes1 = await peer1Notes.list();
|
|
122
|
+
notes2 = await peer2Notes.list();
|
|
123
|
+
expect(notes1).toEqual(notes2);
|
|
124
|
+
});
|
|
125
|
+
it("should handle applyChanges called in sequence with out-of-order changes", async () => {
|
|
126
|
+
const peer = buildPeer();
|
|
127
|
+
const peerNotes = getNotesTable(peer);
|
|
128
|
+
const noteId = (0, peers_sdk_1.newid)();
|
|
129
|
+
const now = Date.now();
|
|
130
|
+
const change2 = {
|
|
131
|
+
changeId: (0, peers_sdk_1.newid)(),
|
|
132
|
+
tableName: notesTableName,
|
|
133
|
+
recordId: noteId,
|
|
134
|
+
timestamp: now - 5000, // 5 seconds before change3
|
|
135
|
+
timestampApplied: now - 5000,
|
|
136
|
+
changeType: 'update',
|
|
137
|
+
oldRecord: { noteId, title: "note inserted" },
|
|
138
|
+
newRecord: { noteId: noteId, title: "note updated" },
|
|
139
|
+
jsonDiff: [{ op: 'replace', path: '/title', value: 'note updated' }]
|
|
140
|
+
};
|
|
141
|
+
await peer.applyChanges([change2]);
|
|
142
|
+
let note = await peerNotes.get(noteId);
|
|
143
|
+
expect(note).toEqual(change2.newRecord);
|
|
144
|
+
await peer.applyChanges([{
|
|
145
|
+
changeId: (0, peers_sdk_1.newid)(),
|
|
146
|
+
tableName: notesTableName,
|
|
147
|
+
recordId: noteId,
|
|
148
|
+
timestamp: now - 6000,
|
|
149
|
+
timestampApplied: now - 6000,
|
|
150
|
+
changeType: 'update',
|
|
151
|
+
newRecord: { noteId: noteId, title: "note updated first" },
|
|
152
|
+
jsonDiff: [{ op: 'replace', path: '/title', value: 'note updated first' }]
|
|
153
|
+
}]);
|
|
154
|
+
note = await peerNotes.get(noteId);
|
|
155
|
+
expect(note).toEqual(change2.newRecord);
|
|
156
|
+
// Create changes with different timestamps (newer to older)
|
|
157
|
+
const change3 = {
|
|
158
|
+
changeId: (0, peers_sdk_1.newid)(),
|
|
159
|
+
tableName: notesTableName,
|
|
160
|
+
recordId: noteId,
|
|
161
|
+
timestamp: now,
|
|
162
|
+
timestampApplied: now,
|
|
163
|
+
changeType: 'delete',
|
|
164
|
+
oldRecord: { noteId, title: "note deleted" }
|
|
165
|
+
};
|
|
166
|
+
await peer.applyChanges([change3]);
|
|
167
|
+
note = await peerNotes.get(noteId);
|
|
168
|
+
expect(note).toBeUndefined();
|
|
169
|
+
const change1 = {
|
|
170
|
+
changeId: (0, peers_sdk_1.newid)(),
|
|
171
|
+
tableName: notesTableName,
|
|
172
|
+
recordId: noteId,
|
|
173
|
+
timestamp: now - 10000, // 10 seconds before change3
|
|
174
|
+
timestampApplied: now - 10000,
|
|
175
|
+
changeType: 'insert',
|
|
176
|
+
newRecord: { noteId: noteId, title: "Note inserted" }
|
|
177
|
+
};
|
|
178
|
+
await peer.applyChanges([change1]);
|
|
179
|
+
note = await peerNotes.get(noteId);
|
|
180
|
+
expect(note).toBeUndefined();
|
|
181
|
+
});
|
|
182
|
+
it("should push changes to connected peers", async () => {
|
|
183
|
+
// Create two peers
|
|
184
|
+
const peer1 = buildPeer();
|
|
185
|
+
const peer2 = buildPeer();
|
|
186
|
+
// Connect them bidirectionally
|
|
187
|
+
await peer1.addConnection(peer2);
|
|
188
|
+
await peer2.addConnection(peer1);
|
|
189
|
+
// Get notes tables
|
|
190
|
+
const peer1Notes = getNotesTable(peer1);
|
|
191
|
+
const peer2Notes = getNotesTable(peer2);
|
|
192
|
+
// Create a note on peer1
|
|
193
|
+
const noteId = (0, peers_sdk_1.newid)();
|
|
194
|
+
const originalNote = { noteId, title: "Original Note" };
|
|
195
|
+
await peer1Notes.save(originalNote);
|
|
196
|
+
// Wait a moment for change to propagate
|
|
197
|
+
await (0, peers_sdk_1.sleep)(100);
|
|
198
|
+
// Verify the note was pushed to peer2
|
|
199
|
+
let peer2Note = await peer2Notes.get(noteId);
|
|
200
|
+
expect(peer2Note).toBeDefined();
|
|
201
|
+
expect(peer2Note?.title).toBe("Original Note");
|
|
202
|
+
// Update the note on peer1
|
|
203
|
+
const updatedNote = { noteId, title: "Updated Note" };
|
|
204
|
+
await peer1Notes.save(updatedNote);
|
|
205
|
+
// Wait a moment for change to propagate
|
|
206
|
+
await (0, peers_sdk_1.sleep)(100);
|
|
207
|
+
// Verify the update was pushed to peer2
|
|
208
|
+
peer2Note = await peer2Notes.get(noteId);
|
|
209
|
+
expect(peer2Note?.title).toBe("Updated Note");
|
|
210
|
+
// Delete the note on peer1
|
|
211
|
+
await peer1Notes.delete(noteId);
|
|
212
|
+
// Wait a moment for change to propagate
|
|
213
|
+
await (0, peers_sdk_1.sleep)(100);
|
|
214
|
+
// Verify the deletion was pushed to peer2
|
|
215
|
+
peer2Note = await peer2Notes.get(noteId);
|
|
216
|
+
expect(peer2Note).toBeUndefined();
|
|
217
|
+
// Test changes in the other direction too (peer2 -> peer1)
|
|
218
|
+
const note2Id = (0, peers_sdk_1.newid)();
|
|
219
|
+
await peer2Notes.save({ noteId: note2Id, title: "Note from Peer2" });
|
|
220
|
+
// Wait a moment for change to propagate
|
|
221
|
+
await (0, peers_sdk_1.sleep)(100);
|
|
222
|
+
// Verify the note was pushed to peer1
|
|
223
|
+
const peer1Note2 = await peer1Notes.get(note2Id);
|
|
224
|
+
expect(peer1Note2).toBeDefined();
|
|
225
|
+
expect(peer1Note2?.title).toBe("Note from Peer2");
|
|
226
|
+
});
|
|
227
|
+
// this is unreliable because it's based on the machine it runs on.
|
|
228
|
+
// just use it for verifying performance if changes are made to syncing algorithm
|
|
229
|
+
it.skip("should only sync changes that were written after the last sync", async () => {
|
|
230
|
+
// Create two peers
|
|
231
|
+
const peer1 = buildPeer();
|
|
232
|
+
const peer2 = buildPeer();
|
|
233
|
+
// Get notes tables
|
|
234
|
+
const peer1Notes = getNotesTable(peer1);
|
|
235
|
+
const peer2Notes = getNotesTable(peer2);
|
|
236
|
+
// add a whole bunch of notes to both
|
|
237
|
+
for (let i = 0; i < 3_000; i++) {
|
|
238
|
+
await peer1Notes.save({ noteId: (0, peers_sdk_1.newid)(), title: `Note ${i}` });
|
|
239
|
+
await peer2Notes.save({ noteId: (0, peers_sdk_1.newid)(), title: `Note ${i}` });
|
|
240
|
+
}
|
|
241
|
+
// Connect peer2 to peer1 (only one direction)
|
|
242
|
+
const sync1 = peer2.addConnection(peer1);
|
|
243
|
+
const sync2 = peer1.addConnection(peer2);
|
|
244
|
+
// time the sync
|
|
245
|
+
const start = Date.now();
|
|
246
|
+
await Promise.all([sync1, sync2]);
|
|
247
|
+
const end = Date.now();
|
|
248
|
+
const syncTime = end - start;
|
|
249
|
+
expect(syncTime).toBeGreaterThan(400);
|
|
250
|
+
// Disconnect the peers
|
|
251
|
+
await peer1.removeConnection(peer2.deviceId);
|
|
252
|
+
await peer2.removeConnection(peer1.deviceId);
|
|
253
|
+
// add some more notes
|
|
254
|
+
await peer1Notes.save({ noteId: (0, peers_sdk_1.newid)(), title: `Note final` });
|
|
255
|
+
await peer2Notes.save({ noteId: (0, peers_sdk_1.newid)(), title: `Note final` });
|
|
256
|
+
// Reconnect the peers
|
|
257
|
+
const sync3 = peer2.addConnection(peer1);
|
|
258
|
+
const sync4 = peer1.addConnection(peer2);
|
|
259
|
+
const start2 = Date.now();
|
|
260
|
+
await Promise.all([sync3, sync4]);
|
|
261
|
+
const end2 = Date.now();
|
|
262
|
+
const syncTime2 = end2 - start2;
|
|
263
|
+
expect(syncTime2).toBeLessThan(200);
|
|
264
|
+
// verify they have the same notes
|
|
265
|
+
const notes1 = await peer1Notes.list();
|
|
266
|
+
const notes2 = await peer2Notes.list();
|
|
267
|
+
expect(notes1).toEqual(notes2);
|
|
268
|
+
});
|
|
269
|
+
it("should keep track of latency and errors for each connection", async () => {
|
|
270
|
+
// Create two peers
|
|
271
|
+
const remotePeer1 = buildRemotePeer();
|
|
272
|
+
const remotePeer2 = buildRemotePeer({ latencyMs: 10, errorRate: 0.9 });
|
|
273
|
+
const peer1 = remotePeer1.peer;
|
|
274
|
+
const peer2 = remotePeer2.peer;
|
|
275
|
+
// Get notes tables
|
|
276
|
+
const peer1Notes = getNotesTable(peer1);
|
|
277
|
+
// Connect them bidirectionally
|
|
278
|
+
await peer1.addConnection(remotePeer2.remotePeer);
|
|
279
|
+
await peer2.addConnection(remotePeer1.remotePeer);
|
|
280
|
+
for (let i = 0; i < 10; i++) {
|
|
281
|
+
await peer1Notes.save({ noteId: (0, peers_sdk_1.newid)(), title: `Hello, World! ${i}` });
|
|
282
|
+
}
|
|
283
|
+
await (0, peers_sdk_1.sleep)(200);
|
|
284
|
+
const conn1 = peer1.getConnections()[0];
|
|
285
|
+
expect(conn1.latencyMs).toBeGreaterThan(10);
|
|
286
|
+
expect(conn1.errorRate).toBeGreaterThan(0.1);
|
|
287
|
+
});
|
|
288
|
+
it("should efficiently sync large sets of changes (this is a flaky test and should be redone or skipped)", async () => {
|
|
289
|
+
// Create two peers
|
|
290
|
+
const peer1 = buildPeer();
|
|
291
|
+
const peer2 = buildPeer();
|
|
292
|
+
// Get notes tables
|
|
293
|
+
const peer1Notes = getNotesTable(peer1);
|
|
294
|
+
const peer2Notes = getNotesTable(peer2);
|
|
295
|
+
async function assertSyncMs(msLt, msGt = 1) {
|
|
296
|
+
// time the sync
|
|
297
|
+
const start = Date.now();
|
|
298
|
+
await Promise.all([
|
|
299
|
+
peer1.syncWithRemoteDevice(peer2),
|
|
300
|
+
peer2.syncWithRemoteDevice(peer1)
|
|
301
|
+
]);
|
|
302
|
+
const end = Date.now();
|
|
303
|
+
const syncTime = end - start;
|
|
304
|
+
// console.log(`Sync time: ${syncTime}ms`);
|
|
305
|
+
expect(syncTime).toBeLessThanOrEqual(msLt);
|
|
306
|
+
expect(syncTime).toBeGreaterThan(msGt);
|
|
307
|
+
}
|
|
308
|
+
// add a whole bunch of notes to both
|
|
309
|
+
for (let i = 0; i < 100; i++) {
|
|
310
|
+
await peer1Notes.save({ noteId: (0, peers_sdk_1.newid)(), title: `Note ${i}` });
|
|
311
|
+
await peer2Notes.save({ noteId: (0, peers_sdk_1.newid)(), title: `Note ${i}` });
|
|
312
|
+
}
|
|
313
|
+
await assertSyncMs(1000, 0);
|
|
314
|
+
let syncBatch = 0;
|
|
315
|
+
for (const n of (0, lodash_1.range)(10)) {
|
|
316
|
+
syncBatch++;
|
|
317
|
+
// update a bunch of notes
|
|
318
|
+
const allNotes = await peer1Notes.list();
|
|
319
|
+
for (const note of allNotes) {
|
|
320
|
+
note.title = `Updated ${note.title} 1 - ${syncBatch}`;
|
|
321
|
+
await peer1Notes.save(note);
|
|
322
|
+
note.title = `Updated ${note.title} 2 - ${syncBatch}`;
|
|
323
|
+
await peer2Notes.save(note);
|
|
324
|
+
}
|
|
325
|
+
await assertSyncMs(1000);
|
|
326
|
+
// verify they have the same notes
|
|
327
|
+
const notes1 = await peer1Notes.list();
|
|
328
|
+
const notes2 = await peer2Notes.list();
|
|
329
|
+
expect(notes1).toEqual(notes2);
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
it("should handle conflicts from two peers being offline and many changes occurring", async () => {
|
|
333
|
+
// Create two peers
|
|
334
|
+
const peer1 = buildPeer();
|
|
335
|
+
const peer2 = buildPeer();
|
|
336
|
+
// Get notes tables
|
|
337
|
+
const peer1Notes = getNotesTable(peer1);
|
|
338
|
+
const peer2Notes = getNotesTable(peer2);
|
|
339
|
+
async function assertSync() {
|
|
340
|
+
// time the sync
|
|
341
|
+
const start = Date.now();
|
|
342
|
+
await Promise.all([
|
|
343
|
+
peer1.syncWithRemoteDevice(peer2),
|
|
344
|
+
peer2.syncWithRemoteDevice(peer1)
|
|
345
|
+
]);
|
|
346
|
+
const end = Date.now();
|
|
347
|
+
const syncTime = end - start;
|
|
348
|
+
// console.log(`Sync time: ${syncTime}ms`);
|
|
349
|
+
// verify they have the same notes
|
|
350
|
+
const notes1 = await peer1Notes.list();
|
|
351
|
+
const notes2 = await peer2Notes.list();
|
|
352
|
+
expect(notes1).toEqual(notes2);
|
|
353
|
+
}
|
|
354
|
+
// add a whole bunch of notes to both
|
|
355
|
+
for (let i = 0; i < 100; i++) {
|
|
356
|
+
await peer1Notes.save({ noteId: (0, peers_sdk_1.newid)(), title: `Note ${i}` });
|
|
357
|
+
await peer2Notes.save({ noteId: (0, peers_sdk_1.newid)(), title: `Note ${i}` });
|
|
358
|
+
}
|
|
359
|
+
await assertSync();
|
|
360
|
+
let syncBatch = 0;
|
|
361
|
+
for (const n of (0, lodash_1.range)(10)) {
|
|
362
|
+
syncBatch++;
|
|
363
|
+
for (const table of [peer1Notes, peer2Notes]) {
|
|
364
|
+
const allNotes = await table.list();
|
|
365
|
+
for (const note of allNotes) {
|
|
366
|
+
const rand = Math.random();
|
|
367
|
+
if (rand < 0.05) {
|
|
368
|
+
await table.delete(note.noteId);
|
|
369
|
+
}
|
|
370
|
+
else if (rand < 0.95) {
|
|
371
|
+
note.title = `Updated ${note.title} ${Math.random().toString().substring(1, 2)} - ${syncBatch}`;
|
|
372
|
+
await table.save(note);
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
await table.save({ noteId: (0, peers_sdk_1.newid)(), title: `new note - ${syncBatch}` });
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
// verify they have the same notes
|
|
381
|
+
const notes1 = await peer1Notes.list();
|
|
382
|
+
const notes2 = await peer2Notes.list();
|
|
383
|
+
const diffs = [];
|
|
384
|
+
const ids = (0, lodash_1.uniq)([...notes1.map(n => n.noteId), ...notes2.map(n => n.noteId)]);
|
|
385
|
+
for (const id of ids) {
|
|
386
|
+
const note1 = notes1.find(n => n.noteId === id);
|
|
387
|
+
const note2 = notes2.find(n => n.noteId === id);
|
|
388
|
+
if ((0, lodash_1.isEqual)(note1, note2))
|
|
389
|
+
continue;
|
|
390
|
+
diffs.push({ id, note1, note2 });
|
|
391
|
+
}
|
|
392
|
+
expect(diffs.length).toBeGreaterThan(100);
|
|
393
|
+
await assertSync();
|
|
394
|
+
});
|
|
395
|
+
it("should correctly page through changes when syncing with a remote device", async () => {
|
|
396
|
+
// Create two peers
|
|
397
|
+
const peer1 = buildPeer();
|
|
398
|
+
const peer2 = buildPeer();
|
|
399
|
+
// Get notes tables
|
|
400
|
+
const peer1Notes = getNotesTable(peer1);
|
|
401
|
+
const peer2Notes = getNotesTable(peer2);
|
|
402
|
+
// add a whole bunch of notes to both
|
|
403
|
+
for (let i = 0; i < 4; i++) {
|
|
404
|
+
await peer1Notes.save({ noteId: (0, peers_sdk_1.newid)(), title: `Note ${i}` });
|
|
405
|
+
await peer2Notes.save({ noteId: (0, peers_sdk_1.newid)(), title: `Note ${i}` });
|
|
406
|
+
}
|
|
407
|
+
// time the sync
|
|
408
|
+
await peer1.syncWithRemoteDevice(peer2, { pageSize: 2 });
|
|
409
|
+
await peer2.syncWithRemoteDevice(peer1, { pageSize: 2 });
|
|
410
|
+
// verify they have the same notes
|
|
411
|
+
const notes1 = await peer1Notes.list();
|
|
412
|
+
const notes2 = await peer2Notes.list();
|
|
413
|
+
expect(notes1).toEqual(notes2);
|
|
414
|
+
});
|
|
415
|
+
it("should handle conflicts from three peers being offline and many changes occurring", async () => {
|
|
416
|
+
// Create two peers
|
|
417
|
+
const peer1 = buildPeer();
|
|
418
|
+
const peer2 = buildPeer();
|
|
419
|
+
const peer3 = buildPeer();
|
|
420
|
+
// Get notes tables
|
|
421
|
+
const peer1Notes = getNotesTable(peer1);
|
|
422
|
+
const peer2Notes = getNotesTable(peer2);
|
|
423
|
+
const peer3Notes = getNotesTable(peer3);
|
|
424
|
+
async function assertSync() {
|
|
425
|
+
// time the sync
|
|
426
|
+
const start = Date.now();
|
|
427
|
+
await Promise.all([
|
|
428
|
+
peer1.syncWithRemoteDevice(peer2),
|
|
429
|
+
peer1.syncWithRemoteDevice(peer3),
|
|
430
|
+
peer2.syncWithRemoteDevice(peer1),
|
|
431
|
+
peer2.syncWithRemoteDevice(peer3),
|
|
432
|
+
peer3.syncWithRemoteDevice(peer1),
|
|
433
|
+
peer3.syncWithRemoteDevice(peer2),
|
|
434
|
+
]);
|
|
435
|
+
const end = Date.now();
|
|
436
|
+
const syncTime = end - start;
|
|
437
|
+
// console.log(`Sync time: ${syncTime}ms`);
|
|
438
|
+
// verify they have the same notes
|
|
439
|
+
const notes1 = await peer1Notes.list();
|
|
440
|
+
const notes2 = await peer2Notes.list();
|
|
441
|
+
const notes3 = await peer3Notes.list();
|
|
442
|
+
expect(notes1).toEqual(notes2);
|
|
443
|
+
expect(notes1).toEqual(notes3);
|
|
444
|
+
}
|
|
445
|
+
// add notes to all of them and sync
|
|
446
|
+
for (let i = 0; i < 40; i++) {
|
|
447
|
+
await peer1Notes.save({ noteId: (0, peers_sdk_1.newid)(), title: `Note ${i}` });
|
|
448
|
+
await peer2Notes.save({ noteId: (0, peers_sdk_1.newid)(), title: `Note ${i}` });
|
|
449
|
+
await peer3Notes.save({ noteId: (0, peers_sdk_1.newid)(), title: `Note ${i}` });
|
|
450
|
+
}
|
|
451
|
+
await assertSync();
|
|
452
|
+
// create many inserts, updates, and deletes in all of them
|
|
453
|
+
for (const syncBatch of (0, lodash_1.range)(10)) {
|
|
454
|
+
for (const table of [peer1Notes, peer2Notes, peer3Notes]) {
|
|
455
|
+
const allNotes = await table.list();
|
|
456
|
+
for (const note of allNotes) {
|
|
457
|
+
const rand = Math.random();
|
|
458
|
+
if (rand < 0.05) {
|
|
459
|
+
await table.delete(note.noteId);
|
|
460
|
+
}
|
|
461
|
+
else if (rand < 0.95) {
|
|
462
|
+
note.title = `Updated ${note.title} ${Math.random().toString().substring(1, 2)} - ${syncBatch}`;
|
|
463
|
+
await table.save(note);
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
await table.save({ noteId: (0, peers_sdk_1.newid)(), title: `new note - ${syncBatch}` });
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
// verify they sync to the same state
|
|
472
|
+
await assertSync();
|
|
473
|
+
});
|
|
474
|
+
it("should work with multiple tables and multiple peers with all different field types", async () => {
|
|
475
|
+
const peer1 = buildPeer();
|
|
476
|
+
const peer2 = buildPeer();
|
|
477
|
+
peer1.addConnection(peer2, { resyncInterval: 200 });
|
|
478
|
+
peer2.addConnection(peer1, { resyncInterval: 200 });
|
|
479
|
+
const tasksSchema = zod_1.z.object({
|
|
480
|
+
taskId: zod_1.z.string(),
|
|
481
|
+
title: zod_1.z.string(),
|
|
482
|
+
completed: zod_1.z.boolean().optional(),
|
|
483
|
+
number: zod_1.z.number().optional(),
|
|
484
|
+
string: zod_1.z.string().optional(),
|
|
485
|
+
date: zod_1.z.date().optional(),
|
|
486
|
+
});
|
|
487
|
+
const tasksMetaData = {
|
|
488
|
+
name: "tasks-test",
|
|
489
|
+
description: 'A table for tasks',
|
|
490
|
+
primaryKeyName: 'taskId',
|
|
491
|
+
fields: (0, peers_sdk_1.schemaToFields)(tasksSchema),
|
|
492
|
+
};
|
|
493
|
+
// Get notes tables
|
|
494
|
+
const peer1Notes = getNotesTable(peer1);
|
|
495
|
+
const peer2Notes = getNotesTable(peer2);
|
|
496
|
+
const peer1Tasks = peer1.tableContainer.getTable(tasksMetaData);
|
|
497
|
+
const peer2Tasks = peer2.tableContainer.getTable(tasksMetaData);
|
|
498
|
+
// create notes
|
|
499
|
+
for (let i = 0; i < 10; i++) {
|
|
500
|
+
await peer1Notes.save({ noteId: (0, peers_sdk_1.newid)(), title: `Note ${i}`, completed: false, number: i, string: `String ${i}`, date: new Date() });
|
|
501
|
+
await peer2Notes.save({ noteId: (0, peers_sdk_1.newid)(), title: `Note ${i}`, completed: false, number: i, string: `String ${i}`, date: new Date() });
|
|
502
|
+
await peer1Tasks.save({ taskId: (0, peers_sdk_1.newid)(), title: `Task ${i}`, completed: false, number: i, string: `String ${i}`, date: new Date() });
|
|
503
|
+
await peer2Tasks.save({ taskId: (0, peers_sdk_1.newid)(), title: `Task ${i}`, completed: false, number: i, string: `String ${i}`, date: new Date() });
|
|
504
|
+
}
|
|
505
|
+
await (0, peers_sdk_1.sleep)(1000);
|
|
506
|
+
let peer1NotesList = await peer1Notes.list();
|
|
507
|
+
let peer2NotesList = await peer2Notes.list();
|
|
508
|
+
let peer1TasksList = await peer1Tasks.list();
|
|
509
|
+
let peer2TasksList = await peer2Tasks.list();
|
|
510
|
+
expect(peer1NotesList.length).toBe(20);
|
|
511
|
+
expect(peer1TasksList.length).toBe(20);
|
|
512
|
+
expect(peer1NotesList).toEqual(peer2NotesList);
|
|
513
|
+
expect(peer1TasksList).toEqual(peer2TasksList);
|
|
514
|
+
for (const note of peer1NotesList) {
|
|
515
|
+
note.title = `Updated ${note.title}`;
|
|
516
|
+
note.completed = true;
|
|
517
|
+
note.number = Math.random();
|
|
518
|
+
note.string = `Updated ${note.string}`;
|
|
519
|
+
note.date = new Date();
|
|
520
|
+
await peer1Notes.save(note);
|
|
521
|
+
}
|
|
522
|
+
for (const task of peer1TasksList) {
|
|
523
|
+
task.title = `Updated ${task.title}`;
|
|
524
|
+
task.completed = true;
|
|
525
|
+
task.number = Math.random();
|
|
526
|
+
task.string = `Updated ${task.string}`;
|
|
527
|
+
task.date = new Date();
|
|
528
|
+
await peer2Tasks.save(task);
|
|
529
|
+
}
|
|
530
|
+
await (0, peers_sdk_1.sleep)(1000);
|
|
531
|
+
peer1NotesList = await peer1Notes.list();
|
|
532
|
+
peer2NotesList = await peer2Notes.list();
|
|
533
|
+
peer1TasksList = await peer1Tasks.list();
|
|
534
|
+
peer2TasksList = await peer2Tasks.list();
|
|
535
|
+
expect(peer1NotesList).toEqual(peer2NotesList);
|
|
536
|
+
expect(peer1TasksList).toEqual(peer2TasksList);
|
|
537
|
+
expect(peer1NotesList.length).toBe(20);
|
|
538
|
+
expect(peer1TasksList.length).toBe(20);
|
|
539
|
+
for (const note of peer1NotesList) {
|
|
540
|
+
await peer1Notes.delete(note);
|
|
541
|
+
}
|
|
542
|
+
for (const task of peer1TasksList) {
|
|
543
|
+
await peer2Tasks.delete(task);
|
|
544
|
+
}
|
|
545
|
+
await (0, peers_sdk_1.sleep)(1000);
|
|
546
|
+
peer1NotesList = await peer1Notes.list();
|
|
547
|
+
peer2NotesList = await peer2Notes.list();
|
|
548
|
+
peer1TasksList = await peer1Tasks.list();
|
|
549
|
+
peer2TasksList = await peer2Tasks.list();
|
|
550
|
+
expect(peer1NotesList).toEqual(peer2NotesList);
|
|
551
|
+
expect(peer1TasksList).toEqual(peer2TasksList);
|
|
552
|
+
expect(peer1NotesList.length).toBe(0);
|
|
553
|
+
expect(peer1TasksList.length).toBe(0);
|
|
554
|
+
});
|
|
555
|
+
it.skip("should not excessively call peer functions", async () => {
|
|
556
|
+
// this has to be run by itself while we use static variables to count calls
|
|
557
|
+
// Create two peers
|
|
558
|
+
const peer1 = buildRemotePeer();
|
|
559
|
+
const peer2 = buildRemotePeer();
|
|
560
|
+
function assertCounts(counts) {
|
|
561
|
+
expect(device_sync_1.DeviceSync.electPreferredConnectionsCount).toBe(counts.electPreferredConnectionsCount);
|
|
562
|
+
expect(device_sync_1.DeviceSync.syncWithRemoteDeviceCount).toBe(counts.syncWithRemoteDeviceCount);
|
|
563
|
+
expect(device_sync_1.DeviceSync.getNetworkInfoCount).toBe(counts.getNetworkInfoCount);
|
|
564
|
+
expect(device_sync_1.DeviceSync.applyChangesCount).toBe(counts.applyChangesCount);
|
|
565
|
+
expect(device_sync_1.DeviceSync.listChangesCount).toBe(counts.listChangesCount);
|
|
566
|
+
device_sync_1.DeviceSync.listChangesCount = 0;
|
|
567
|
+
device_sync_1.DeviceSync.applyChangesCount = 0;
|
|
568
|
+
device_sync_1.DeviceSync.getNetworkInfoCount = 0;
|
|
569
|
+
device_sync_1.DeviceSync.electPreferredConnectionsCount = 0;
|
|
570
|
+
device_sync_1.DeviceSync.syncWithRemoteDeviceCount = 0;
|
|
571
|
+
}
|
|
572
|
+
assertCounts({
|
|
573
|
+
listChangesCount: 0,
|
|
574
|
+
applyChangesCount: 0,
|
|
575
|
+
getNetworkInfoCount: 0,
|
|
576
|
+
electPreferredConnectionsCount: 0,
|
|
577
|
+
syncWithRemoteDeviceCount: 0,
|
|
578
|
+
});
|
|
579
|
+
await peer1.peer.addConnection(peer2.remotePeer);
|
|
580
|
+
await peer2.peer.addConnection(peer1.remotePeer);
|
|
581
|
+
await (0, peers_sdk_1.sleep)(1);
|
|
582
|
+
assertCounts({
|
|
583
|
+
electPreferredConnectionsCount: 2, // once for each peer
|
|
584
|
+
syncWithRemoteDeviceCount: 2, // once for each peer
|
|
585
|
+
listChangesCount: 2, // each peer syncs with the other
|
|
586
|
+
getNetworkInfoCount: 2, // network info on elections (use same network info for sync)
|
|
587
|
+
applyChangesCount: 0, // no changes to apply
|
|
588
|
+
});
|
|
589
|
+
// expect them to be synced with each other
|
|
590
|
+
const peer1NetworkInfo = await peer1.peer.getNetworkInfo();
|
|
591
|
+
const peer1Conns = peer1NetworkInfo.connections;
|
|
592
|
+
expect(peer1Conns.length).toBe(1);
|
|
593
|
+
expect(peer1NetworkInfo.preferredDeviceIds).toEqual([peer2.peer.deviceId]);
|
|
594
|
+
const peer2NetworkInfo = await peer2.peer.getNetworkInfo();
|
|
595
|
+
const peer2Conns = peer2NetworkInfo.connections;
|
|
596
|
+
expect(peer2Conns.length).toBe(1);
|
|
597
|
+
expect(peer2NetworkInfo.preferredDeviceIds).toEqual([peer1.peer.deviceId]);
|
|
598
|
+
assertCounts({
|
|
599
|
+
electPreferredConnectionsCount: 0,
|
|
600
|
+
syncWithRemoteDeviceCount: 0,
|
|
601
|
+
listChangesCount: 0,
|
|
602
|
+
getNetworkInfoCount: 2, // manually called above
|
|
603
|
+
applyChangesCount: 0,
|
|
604
|
+
});
|
|
605
|
+
// Get notes tables
|
|
606
|
+
const peer1Notes = getNotesTable(peer1.peer);
|
|
607
|
+
const peer2Notes = getNotesTable(peer2.peer);
|
|
608
|
+
// add one note
|
|
609
|
+
await peer1Notes.save({ noteId: (0, peers_sdk_1.newid)(), title: `Note 1` });
|
|
610
|
+
await (0, peers_sdk_1.sleep)(300);
|
|
611
|
+
device_sync_1.DeviceSync.syncWithRemoteDeviceCount;
|
|
612
|
+
assertCounts({
|
|
613
|
+
electPreferredConnectionsCount: 0,
|
|
614
|
+
syncWithRemoteDeviceCount: 2, // once for each peer (peer1Changes -> peer2 -> peer2Changes -> peer1 (verifies it already has those changes))
|
|
615
|
+
getNetworkInfoCount: 2, // network info as part of sync
|
|
616
|
+
listChangesCount: 4, // I thinks the cursor uses a minimum of 2 list calls => 4 (2 for each peer)
|
|
617
|
+
applyChangesCount: 2, // each peer applies the other's changes
|
|
618
|
+
});
|
|
619
|
+
// add one note
|
|
620
|
+
await peer2Notes.save({ noteId: (0, peers_sdk_1.newid)(), title: `Note 2` });
|
|
621
|
+
await (0, peers_sdk_1.sleep)(300);
|
|
622
|
+
device_sync_1.DeviceSync.syncWithRemoteDeviceCount;
|
|
623
|
+
assertCounts({
|
|
624
|
+
electPreferredConnectionsCount: 0,
|
|
625
|
+
syncWithRemoteDeviceCount: 2,
|
|
626
|
+
getNetworkInfoCount: 2,
|
|
627
|
+
listChangesCount: 4,
|
|
628
|
+
applyChangesCount: 2,
|
|
629
|
+
});
|
|
630
|
+
// await sleep(300);
|
|
631
|
+
// verify they have the same notes
|
|
632
|
+
const notes1 = await peer1Notes.list();
|
|
633
|
+
const notes2 = await peer2Notes.list();
|
|
634
|
+
expect(notes1).toEqual(notes2);
|
|
635
|
+
await Promise.all([
|
|
636
|
+
peer1.peer.dispose(),
|
|
637
|
+
peer2.peer.dispose(),
|
|
638
|
+
]);
|
|
639
|
+
});
|
|
640
|
+
it("should keep synced three connected peers that are all connected to each other", async () => {
|
|
641
|
+
// Create peers
|
|
642
|
+
const peer1 = buildPeer();
|
|
643
|
+
const peer2 = buildPeer();
|
|
644
|
+
const peer3 = buildPeer();
|
|
645
|
+
const peers = [peer1, peer2, peer3];
|
|
646
|
+
// Get notes tables
|
|
647
|
+
const peer1Notes = getNotesTable(peer1);
|
|
648
|
+
const peer2Notes = getNotesTable(peer2);
|
|
649
|
+
const peer3Notes = getNotesTable(peer3);
|
|
650
|
+
// add notes to all peers
|
|
651
|
+
for (let i = 0; i < 20; i++) {
|
|
652
|
+
for (const peer of peers) {
|
|
653
|
+
await getNotesTable(peer).save({ noteId: (0, peers_sdk_1.newid)(), title: `Note ${i}` });
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
// connect all peers
|
|
657
|
+
for (const peer of peers) {
|
|
658
|
+
for (const otherPeer of peers) {
|
|
659
|
+
if (peer !== otherPeer) {
|
|
660
|
+
await peer.addConnection(otherPeer, { resyncInterval: 9000 });
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
// create inserts, updates, and deletes in all peers
|
|
665
|
+
for (const changeBatch of (0, lodash_1.range)(10)) {
|
|
666
|
+
for (const table of [peer1Notes, peer2Notes, peer3Notes]) {
|
|
667
|
+
const allNotes = await table.list();
|
|
668
|
+
for (const note of allNotes) {
|
|
669
|
+
const rand = Math.random();
|
|
670
|
+
try {
|
|
671
|
+
if (rand < 0.05) {
|
|
672
|
+
await table.delete(note);
|
|
673
|
+
}
|
|
674
|
+
else if (rand < 0.95) {
|
|
675
|
+
note.title = `Updated ${note.title} ${Math.random().toString().substring(1, 2)} - ${changeBatch}`;
|
|
676
|
+
const exists = await table.get(note.noteId);
|
|
677
|
+
if (!exists) {
|
|
678
|
+
note.noteId = (0, peers_sdk_1.newid)();
|
|
679
|
+
}
|
|
680
|
+
await table.save(note);
|
|
681
|
+
}
|
|
682
|
+
else {
|
|
683
|
+
await table.save({ noteId: (0, peers_sdk_1.newid)(), title: `new note - ${changeBatch}` });
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
catch (error) {
|
|
687
|
+
console.error(error);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
for (let i = 0; i < 20; i++) {
|
|
693
|
+
// verify they sync to the same state
|
|
694
|
+
const notes1 = await peer1Notes.list();
|
|
695
|
+
const notes2 = await peer2Notes.list();
|
|
696
|
+
const notes3 = await peer3Notes.list();
|
|
697
|
+
if ((0, lodash_1.isEqual)(notes1, notes2) && (0, lodash_1.isEqual)(notes1, notes3)) {
|
|
698
|
+
break;
|
|
699
|
+
}
|
|
700
|
+
console.log("Not fully synced yet...");
|
|
701
|
+
await (0, peers_sdk_1.sleep)(1000);
|
|
702
|
+
}
|
|
703
|
+
const notes1 = await peer1Notes.list();
|
|
704
|
+
const notes2 = await peer2Notes.list();
|
|
705
|
+
const notes3 = await peer3Notes.list();
|
|
706
|
+
expect(notes1).toEqual(notes2);
|
|
707
|
+
expect(notes1).toEqual(notes3);
|
|
708
|
+
});
|
|
709
|
+
it.skip("should keep synced three peers connected in a loop", async () => {
|
|
710
|
+
// This is slow and not realistic because it pretends the peers can have one-way connections
|
|
711
|
+
// The good news is that it does eventually sync which is a nice proof of concept
|
|
712
|
+
// Create two peers
|
|
713
|
+
const peer1 = buildPeer();
|
|
714
|
+
const peer2 = buildPeer();
|
|
715
|
+
const peer3 = buildPeer();
|
|
716
|
+
const peers = [peer1, peer2, peer3];
|
|
717
|
+
// Get notes tables
|
|
718
|
+
const peer1Notes = getNotesTable(peer1);
|
|
719
|
+
const peer2Notes = getNotesTable(peer2);
|
|
720
|
+
const peer3Notes = getNotesTable(peer3);
|
|
721
|
+
// add notes to all of them and sync
|
|
722
|
+
for (let i = 0; i < 20; i++) {
|
|
723
|
+
for (const peer of peers) {
|
|
724
|
+
await getNotesTable(peer).save({ noteId: (0, peers_sdk_1.newid)(), title: `Note ${i}` });
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
const syncPromises = [
|
|
728
|
+
// peer1 -> peer2 -> peer3 -> peer1
|
|
729
|
+
peer1.addConnection(peer2, { resyncInterval: 9000 }),
|
|
730
|
+
peer2.addConnection(peer3, { resyncInterval: 9000 }),
|
|
731
|
+
peer3.addConnection(peer1, { resyncInterval: 9000 }),
|
|
732
|
+
];
|
|
733
|
+
await Promise.all(syncPromises);
|
|
734
|
+
// create many inserts, updates, and deletes in all of them
|
|
735
|
+
for (const changeBatch of (0, lodash_1.range)(10)) {
|
|
736
|
+
for (const table of [peer1Notes, peer2Notes, peer3Notes]) {
|
|
737
|
+
const allNotes = await table.list();
|
|
738
|
+
for (const note of allNotes) {
|
|
739
|
+
const rand = Math.random();
|
|
740
|
+
try {
|
|
741
|
+
if (rand < 0.05) {
|
|
742
|
+
await table.delete(note);
|
|
743
|
+
}
|
|
744
|
+
else if (rand < 0.95) {
|
|
745
|
+
note.title = `Updated ${note.title} ${Math.random().toString().substring(1, 2)} - ${changeBatch}`;
|
|
746
|
+
const exists = await table.get(note.noteId);
|
|
747
|
+
if (!exists) {
|
|
748
|
+
note.noteId = (0, peers_sdk_1.newid)();
|
|
749
|
+
}
|
|
750
|
+
await table.save(note);
|
|
751
|
+
}
|
|
752
|
+
else {
|
|
753
|
+
await table.save({ noteId: (0, peers_sdk_1.newid)(), title: `new note - ${changeBatch}` });
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
catch (error) {
|
|
757
|
+
console.error(error);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
for (let i = 0; i < 20; i++) {
|
|
763
|
+
// verify they sync to the same state
|
|
764
|
+
const notes1 = await peer1Notes.list();
|
|
765
|
+
const notes2 = await peer2Notes.list();
|
|
766
|
+
const notes3 = await peer3Notes.list();
|
|
767
|
+
if ((0, lodash_1.isEqual)(notes1, notes2) && (0, lodash_1.isEqual)(notes1, notes3)) {
|
|
768
|
+
console.log("All peers are synced!");
|
|
769
|
+
break;
|
|
770
|
+
}
|
|
771
|
+
console.log("Not fully synced yet...");
|
|
772
|
+
await (0, peers_sdk_1.sleep)(1000);
|
|
773
|
+
}
|
|
774
|
+
const notes1 = await peer1Notes.list();
|
|
775
|
+
const notes2 = await peer2Notes.list();
|
|
776
|
+
const notes3 = await peer3Notes.list();
|
|
777
|
+
expect(notes1).toEqual(notes2);
|
|
778
|
+
expect(notes1).toEqual(notes3);
|
|
779
|
+
});
|
|
780
|
+
it("should keep synced three peers connected via a hub", async () => {
|
|
781
|
+
// Create two peers
|
|
782
|
+
const peer1 = buildPeer();
|
|
783
|
+
const peer2 = buildPeer();
|
|
784
|
+
const peer3 = buildPeer();
|
|
785
|
+
// Get notes tables
|
|
786
|
+
const peer1Notes = getNotesTable(peer1);
|
|
787
|
+
const peer2Notes = getNotesTable(peer2);
|
|
788
|
+
const peer3Notes = getNotesTable(peer3);
|
|
789
|
+
// add notes to all of them and sync
|
|
790
|
+
for (let i = 0; i < 20; i++) {
|
|
791
|
+
await peer1Notes.save({ noteId: (0, peers_sdk_1.newid)(), title: `Note ${i}` });
|
|
792
|
+
await peer2Notes.save({ noteId: (0, peers_sdk_1.newid)(), title: `Note ${i}` });
|
|
793
|
+
await peer3Notes.save({ noteId: (0, peers_sdk_1.newid)(), title: `Note ${i}` });
|
|
794
|
+
}
|
|
795
|
+
const syncPromises = [
|
|
796
|
+
// peer1 <-> peer2 <-> peer3
|
|
797
|
+
peer1.addConnection(peer2, { resyncInterval: 9000 }),
|
|
798
|
+
peer2.addConnection(peer1, { resyncInterval: 9000 }),
|
|
799
|
+
peer2.addConnection(peer3, { resyncInterval: 9000 }),
|
|
800
|
+
peer3.addConnection(peer2, { resyncInterval: 9000 }),
|
|
801
|
+
];
|
|
802
|
+
// create many inserts, updates, and deletes in all of them
|
|
803
|
+
for (const changeBatch of (0, lodash_1.range)(10)) {
|
|
804
|
+
for (const table of [peer1Notes, peer2Notes, peer3Notes]) {
|
|
805
|
+
const allNotes = await table.list();
|
|
806
|
+
for (const note of allNotes) {
|
|
807
|
+
const rand = Math.random();
|
|
808
|
+
try {
|
|
809
|
+
if (rand < 0.05) {
|
|
810
|
+
await table.delete(note);
|
|
811
|
+
}
|
|
812
|
+
else if (rand < 0.95) {
|
|
813
|
+
note.title = `Updated ${note.title} ${Math.random().toString().substring(1, 2)} - ${changeBatch}`;
|
|
814
|
+
const exists = await table.get(note.noteId);
|
|
815
|
+
if (!exists) {
|
|
816
|
+
note.noteId = (0, peers_sdk_1.newid)();
|
|
817
|
+
}
|
|
818
|
+
await table.save(note);
|
|
819
|
+
}
|
|
820
|
+
else {
|
|
821
|
+
await table.save({ noteId: (0, peers_sdk_1.newid)(), title: `new note - ${changeBatch}` });
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
catch (error) {
|
|
825
|
+
console.error(error);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
for (let i = 0; i < 10; i++) {
|
|
831
|
+
const stillSyncing = peer1.getConnections().some(c => !c.timestampLastApplied) ||
|
|
832
|
+
peer2.getConnections().some(c => !c.timestampLastApplied) ||
|
|
833
|
+
peer3.getConnections().some(c => !c.timestampLastApplied);
|
|
834
|
+
await (0, peers_sdk_1.sleep)(100);
|
|
835
|
+
if (!stillSyncing)
|
|
836
|
+
break;
|
|
837
|
+
console.log("Still syncing...");
|
|
838
|
+
}
|
|
839
|
+
for (let i = 0; i < 20; i++) {
|
|
840
|
+
// verify they sync to the same state
|
|
841
|
+
const notes1 = await peer1Notes.list();
|
|
842
|
+
const notes2 = await peer2Notes.list();
|
|
843
|
+
const notes3 = await peer3Notes.list();
|
|
844
|
+
if ((0, lodash_1.isEqual)(notes1, notes2) && (0, lodash_1.isEqual)(notes1, notes3)) {
|
|
845
|
+
break;
|
|
846
|
+
}
|
|
847
|
+
console.log("Not fully synced yet...");
|
|
848
|
+
await (0, peers_sdk_1.sleep)(1000);
|
|
849
|
+
}
|
|
850
|
+
const notes1 = await peer1Notes.list();
|
|
851
|
+
const notes2 = await peer2Notes.list();
|
|
852
|
+
const notes3 = await peer3Notes.list();
|
|
853
|
+
expect(notes1).toEqual(notes2);
|
|
854
|
+
expect(notes1).toEqual(notes3);
|
|
855
|
+
});
|
|
856
|
+
it("should keep synced four peers connected via two hubs", async () => {
|
|
857
|
+
// Create two peers
|
|
858
|
+
const peer1 = buildPeer();
|
|
859
|
+
const peer2 = buildPeer();
|
|
860
|
+
const peer3 = buildPeer();
|
|
861
|
+
const peer4 = buildPeer();
|
|
862
|
+
// Get notes tables
|
|
863
|
+
const peer1Notes = getNotesTable(peer1);
|
|
864
|
+
const peer2Notes = getNotesTable(peer2);
|
|
865
|
+
const peer3Notes = getNotesTable(peer3);
|
|
866
|
+
const peer4Notes = getNotesTable(peer4);
|
|
867
|
+
// add notes to all of them and sync
|
|
868
|
+
for (let i = 0; i < 10; i++) {
|
|
869
|
+
await peer1Notes.save({ noteId: (0, peers_sdk_1.newid)(), title: `Note ${i}` });
|
|
870
|
+
await peer2Notes.save({ noteId: (0, peers_sdk_1.newid)(), title: `Note ${i}` });
|
|
871
|
+
await peer3Notes.save({ noteId: (0, peers_sdk_1.newid)(), title: `Note ${i}` });
|
|
872
|
+
await peer4Notes.save({ noteId: (0, peers_sdk_1.newid)(), title: `Note ${i}` });
|
|
873
|
+
}
|
|
874
|
+
const syncPromises = [
|
|
875
|
+
// peer1 <-> peer2 <-> peer3 <-> peer4
|
|
876
|
+
peer1.addConnection(peer2, { resyncInterval: 9000 }),
|
|
877
|
+
peer2.addConnection(peer1, { resyncInterval: 9000 }),
|
|
878
|
+
peer2.addConnection(peer3, { resyncInterval: 9000 }),
|
|
879
|
+
peer3.addConnection(peer2, { resyncInterval: 9000 }),
|
|
880
|
+
peer3.addConnection(peer4, { resyncInterval: 9000 }),
|
|
881
|
+
peer4.addConnection(peer3, { resyncInterval: 9000 }),
|
|
882
|
+
];
|
|
883
|
+
// create many inserts, updates, and deletes in all of them
|
|
884
|
+
for (const changeBatch of (0, lodash_1.range)(5)) {
|
|
885
|
+
for (const table of [peer1Notes, peer2Notes, peer3Notes, peer4Notes]) {
|
|
886
|
+
const allNotes = await table.list();
|
|
887
|
+
for (const note of allNotes) {
|
|
888
|
+
const rand = Math.random();
|
|
889
|
+
try {
|
|
890
|
+
if (rand < 0.05) {
|
|
891
|
+
await table.delete(note);
|
|
892
|
+
}
|
|
893
|
+
else if (rand < 0.95) {
|
|
894
|
+
note.title = `Updated ${note.title} ${Math.random().toString().substring(1, 2)} - ${changeBatch}`;
|
|
895
|
+
const exists = await table.get(note.noteId);
|
|
896
|
+
if (!exists) {
|
|
897
|
+
note.noteId = (0, peers_sdk_1.newid)();
|
|
898
|
+
}
|
|
899
|
+
await table.save(note);
|
|
900
|
+
}
|
|
901
|
+
else {
|
|
902
|
+
await table.save({ noteId: (0, peers_sdk_1.newid)(), title: `new note - ${changeBatch}` });
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
catch (error) {
|
|
906
|
+
console.error(error);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
for (let i = 0; i < 10; i++) {
|
|
912
|
+
const stillSyncing = peer1.getConnections().some(c => !c.timestampLastApplied) ||
|
|
913
|
+
peer2.getConnections().some(c => !c.timestampLastApplied) ||
|
|
914
|
+
peer3.getConnections().some(c => !c.timestampLastApplied) ||
|
|
915
|
+
peer4.getConnections().some(c => !c.timestampLastApplied);
|
|
916
|
+
await (0, peers_sdk_1.sleep)(100);
|
|
917
|
+
if (!stillSyncing)
|
|
918
|
+
break;
|
|
919
|
+
console.log("Still syncing...");
|
|
920
|
+
}
|
|
921
|
+
for (let i = 0; i < 20; i++) {
|
|
922
|
+
// verify they sync to the same state
|
|
923
|
+
const notes1 = await peer1Notes.list();
|
|
924
|
+
const notes2 = await peer2Notes.list();
|
|
925
|
+
const notes3 = await peer3Notes.list();
|
|
926
|
+
const notes4 = await peer4Notes.list();
|
|
927
|
+
if ((0, lodash_1.isEqual)(notes1, notes2) && (0, lodash_1.isEqual)(notes1, notes3) && (0, lodash_1.isEqual)(notes1, notes4)) {
|
|
928
|
+
break;
|
|
929
|
+
}
|
|
930
|
+
console.log("Not fully synced yet...");
|
|
931
|
+
await (0, peers_sdk_1.sleep)(1000);
|
|
932
|
+
}
|
|
933
|
+
const notes1 = await peer1Notes.list();
|
|
934
|
+
const notes2 = await peer2Notes.list();
|
|
935
|
+
const notes3 = await peer3Notes.list();
|
|
936
|
+
const notes4 = await peer4Notes.list();
|
|
937
|
+
expect(notes1).toEqual(notes2);
|
|
938
|
+
expect(notes1).toEqual(notes3);
|
|
939
|
+
expect(notes1).toEqual(notes4);
|
|
940
|
+
});
|
|
941
|
+
// unreliable for CI/CD so skipping. Use for local dev only
|
|
942
|
+
it.skip("after syncing with a peers, should skip forward to that peer's timestampAppliedLast for all it's connections", async () => {
|
|
943
|
+
// NOTE: I confirmed via execution time that this is working.
|
|
944
|
+
// Speedup is dependent on amount of data to sync and page size of sync.
|
|
945
|
+
// It is significant for large data sets, but not for small ones.
|
|
946
|
+
// election overhead will be minimized over time for very active networks.
|
|
947
|
+
// for relatively inactive networks, the overhead is not significant.
|
|
948
|
+
/* all peers push and pull syncing
|
|
949
|
+
List changes count: 1980
|
|
950
|
+
Apply changes count: 3510
|
|
951
|
+
Network info count: 0
|
|
952
|
+
Elections count: 0
|
|
953
|
+
Total calls: 5490
|
|
954
|
+
Total time: 24.7s, 24.9s, 24.9s
|
|
955
|
+
*/
|
|
956
|
+
/* add transitive syncing
|
|
957
|
+
List changes count: 612
|
|
958
|
+
Apply changes count: 2142
|
|
959
|
+
Network info count: 180
|
|
960
|
+
Elections count: 0
|
|
961
|
+
Total calls: 2934
|
|
962
|
+
Total time: 10.6s, 10.5s, 10.6s
|
|
963
|
+
*/
|
|
964
|
+
/* add elections on connection changes
|
|
965
|
+
List changes count: 146
|
|
966
|
+
Apply changes count: 1715
|
|
967
|
+
Network info count: 593
|
|
968
|
+
Elections count: 90
|
|
969
|
+
Total calls: 2544
|
|
970
|
+
*/
|
|
971
|
+
/* add push only to preferred connections
|
|
972
|
+
List changes count: 362
|
|
973
|
+
Apply changes count: 480
|
|
974
|
+
Network info count: 674
|
|
975
|
+
Elections count: 90
|
|
976
|
+
Total calls: 1606
|
|
977
|
+
*/
|
|
978
|
+
/* add pull only from preferred connections
|
|
979
|
+
List changes count: 160
|
|
980
|
+
Apply changes count: 378
|
|
981
|
+
Network info count: 573
|
|
982
|
+
Elections count: 90
|
|
983
|
+
Total calls: 1201
|
|
984
|
+
*/
|
|
985
|
+
const peerCnt = 10;
|
|
986
|
+
const errorRate = 0;
|
|
987
|
+
const latencyMs = 10;
|
|
988
|
+
const remotePeers = (0, lodash_1.range)(peerCnt).map(() => {
|
|
989
|
+
return buildRemotePeer({ errorRate, latencyMs });
|
|
990
|
+
});
|
|
991
|
+
const peers = remotePeers.map(p => p.peer);
|
|
992
|
+
for (const peer of peers) {
|
|
993
|
+
getNotesTable(peer);
|
|
994
|
+
}
|
|
995
|
+
// connect all peers to each other
|
|
996
|
+
for (const peer1 of peers) {
|
|
997
|
+
for (const peer2 of peers) {
|
|
998
|
+
if (peer1 === peer2)
|
|
999
|
+
continue;
|
|
1000
|
+
const remotePeer = remotePeers.find(p => p.peer === peer2)?.remotePeer;
|
|
1001
|
+
if (!remotePeer)
|
|
1002
|
+
throw new Error("Remote peer not found");
|
|
1003
|
+
await peer1.addConnection(remotePeer, { resyncInterval: 10_000 });
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
// add a whole bunch of notes and changes
|
|
1007
|
+
for (const peer of peers) {
|
|
1008
|
+
const notesTable = getNotesTable(peer);
|
|
1009
|
+
for (let i = 0; i < 10; i++) {
|
|
1010
|
+
await notesTable.save({ noteId: (0, peers_sdk_1.newid)(), title: `Note ${i}` });
|
|
1011
|
+
const randomNote = (0, lodash_1.shuffle)(await notesTable.list(undefined, { pageSize: 10 }))[0];
|
|
1012
|
+
if (!randomNote)
|
|
1013
|
+
continue;
|
|
1014
|
+
randomNote.title += ` Updated ${i}`;
|
|
1015
|
+
await notesTable.save(randomNote);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
// sync all peers
|
|
1019
|
+
// for (const peer1 of peers) {
|
|
1020
|
+
// for (const peer2 of peers) {
|
|
1021
|
+
// if (peer1 === peer2) continue;
|
|
1022
|
+
// const remotePeer = remotePeers.find(p => p.peer === peer2)?.remotePeer;
|
|
1023
|
+
// if (!remotePeer) throw new Error("Remote peer not found");
|
|
1024
|
+
// await peer1.syncWithRemoteDevice(remotePeer, { pageSize: 10 });
|
|
1025
|
+
// }
|
|
1026
|
+
// }
|
|
1027
|
+
// wait for all peers to sync
|
|
1028
|
+
await (0, peers_sdk_1.sleep)(1000);
|
|
1029
|
+
const myNotes = await getNotesTable(peers[0]).list();
|
|
1030
|
+
expect(myNotes.length).toBeGreaterThan(peerCnt);
|
|
1031
|
+
for (const peer of peers) {
|
|
1032
|
+
const peerNotesTable = getNotesTable(peer);
|
|
1033
|
+
const peerNotes = await peerNotesTable.list();
|
|
1034
|
+
expect(peerNotes).toEqual(myNotes);
|
|
1035
|
+
}
|
|
1036
|
+
console.log("List changes count: ", device_sync_1.DeviceSync.listChangesCount);
|
|
1037
|
+
console.log("Apply changes count: ", device_sync_1.DeviceSync.applyChangesCount);
|
|
1038
|
+
console.log("Network info count: ", device_sync_1.DeviceSync.getNetworkInfoCount);
|
|
1039
|
+
console.log("Elections count: ", device_sync_1.DeviceSync.electPreferredConnectionsCount);
|
|
1040
|
+
console.log("Total calls: ", device_sync_1.DeviceSync.listChangesCount + device_sync_1.DeviceSync.applyChangesCount + device_sync_1.DeviceSync.getNetworkInfoCount + device_sync_1.DeviceSync.electPreferredConnectionsCount);
|
|
1041
|
+
await Promise.all(peers.map(peer => peer.dispose()));
|
|
1042
|
+
});
|
|
1043
|
+
it.skip([
|
|
1044
|
+
`should not slow down as data accumulates many changes`,
|
|
1045
|
+
`(this is a long running that is left skipped unless explicitly checking for performance problems with syncing)`
|
|
1046
|
+
].join('\n '), async () => {
|
|
1047
|
+
// Create peers
|
|
1048
|
+
const peer1 = buildPeer();
|
|
1049
|
+
const peer2 = buildPeer();
|
|
1050
|
+
const peer3 = buildPeer();
|
|
1051
|
+
// Get notes tables
|
|
1052
|
+
const peer1Notes = getNotesTable(peer1);
|
|
1053
|
+
const peer2Notes = getNotesTable(peer2);
|
|
1054
|
+
const peer3Notes = getNotesTable(peer3);
|
|
1055
|
+
// add notes to all of them and sync
|
|
1056
|
+
for (let i = 0; i < 50; i++) {
|
|
1057
|
+
await peer1Notes.save({ noteId: (0, peers_sdk_1.newid)(), title: `Note ${i}` });
|
|
1058
|
+
await peer2Notes.save({ noteId: (0, peers_sdk_1.newid)(), title: `Note ${i}` });
|
|
1059
|
+
await peer3Notes.save({ noteId: (0, peers_sdk_1.newid)(), title: `Note ${i}` });
|
|
1060
|
+
}
|
|
1061
|
+
await Promise.all([
|
|
1062
|
+
peer1.syncWithRemoteDevice(peer2),
|
|
1063
|
+
peer1.syncWithRemoteDevice(peer3),
|
|
1064
|
+
peer2.syncWithRemoteDevice(peer1),
|
|
1065
|
+
peer2.syncWithRemoteDevice(peer3),
|
|
1066
|
+
peer3.syncWithRemoteDevice(peer1),
|
|
1067
|
+
peer3.syncWithRemoteDevice(peer2),
|
|
1068
|
+
]);
|
|
1069
|
+
// create many inserts, updates, and deletes in all of them
|
|
1070
|
+
let allNotes = await peer1Notes.list();
|
|
1071
|
+
for (const changeBatch of (0, lodash_1.range)(100)) {
|
|
1072
|
+
const startTime = Date.now();
|
|
1073
|
+
for (const table of [peer1Notes, peer2Notes, peer3Notes]) {
|
|
1074
|
+
if (!(changeBatch % 10)) {
|
|
1075
|
+
// await table.compact(Date.now());
|
|
1076
|
+
}
|
|
1077
|
+
// allNotes = await table.list();
|
|
1078
|
+
// allNotes = shuffle(allNotes).splice(0, 300);
|
|
1079
|
+
// console.log(`allNotes: ${allNotes.length}`);
|
|
1080
|
+
for (const note of allNotes) {
|
|
1081
|
+
const rand = Math.random();
|
|
1082
|
+
try {
|
|
1083
|
+
if (rand < 0.05) {
|
|
1084
|
+
await table.delete(note);
|
|
1085
|
+
allNotes.splice(allNotes.findIndex(n => n.noteId === note.noteId), 1);
|
|
1086
|
+
}
|
|
1087
|
+
else if (rand < 0.95) {
|
|
1088
|
+
note.title = `Updated ${Math.random().toString().substring(1, 2)} - ${changeBatch}`;
|
|
1089
|
+
await table.save(note);
|
|
1090
|
+
}
|
|
1091
|
+
else {
|
|
1092
|
+
const newNote = await table.save({ noteId: (0, peers_sdk_1.newid)(), title: `new note - ${changeBatch}` });
|
|
1093
|
+
allNotes.push(newNote);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
catch (error) {
|
|
1097
|
+
console.error(error);
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
await Promise.all([
|
|
1102
|
+
peer1.syncWithRemoteDevice(peer2),
|
|
1103
|
+
peer1.syncWithRemoteDevice(peer3),
|
|
1104
|
+
peer2.syncWithRemoteDevice(peer1),
|
|
1105
|
+
peer2.syncWithRemoteDevice(peer3),
|
|
1106
|
+
peer3.syncWithRemoteDevice(peer1),
|
|
1107
|
+
peer3.syncWithRemoteDevice(peer2),
|
|
1108
|
+
]);
|
|
1109
|
+
const changesCount = (0, lodash_1.sum)([
|
|
1110
|
+
await peer1.changeTrackingTable.count(),
|
|
1111
|
+
await peer2.changeTrackingTable.count(),
|
|
1112
|
+
await peer3.changeTrackingTable.count(),
|
|
1113
|
+
]);
|
|
1114
|
+
console.log(`Change batch ${changeBatch}, time: ${Date.now() - startTime}ms, allNotes: ${allNotes.length}, allChanges: ${changesCount}`);
|
|
1115
|
+
}
|
|
1116
|
+
const notes1 = await peer1Notes.list();
|
|
1117
|
+
const notes2 = await peer2Notes.list();
|
|
1118
|
+
const notes3 = await peer3Notes.list();
|
|
1119
|
+
expect(notes1).toEqual(notes2);
|
|
1120
|
+
expect(notes1).toEqual(notes3);
|
|
1121
|
+
});
|
|
1122
|
+
// currently not working come back to this
|
|
1123
|
+
it.skip("should eventually sync even with flaky connections (skipped because it's noisy and slow)", async () => {
|
|
1124
|
+
const remoteMe = buildRemotePeer({ errorRate: 0.3, latencyMs: 200 });
|
|
1125
|
+
const remotePeer = buildRemotePeer({ errorRate: 0.3, latencyMs: 200 });
|
|
1126
|
+
const me = remoteMe.peer;
|
|
1127
|
+
const peer = remotePeer.peer;
|
|
1128
|
+
me.addConnection(remotePeer.remotePeer, { resyncInterval: 1000 });
|
|
1129
|
+
peer.addConnection(remoteMe.remotePeer, { resyncInterval: 1000 });
|
|
1130
|
+
const notesTable = getNotesTable(me);
|
|
1131
|
+
// add notes
|
|
1132
|
+
for (let i = 0; i < 10; i++) {
|
|
1133
|
+
await notesTable.save({ noteId: (0, peers_sdk_1.newid)(), title: `Note ${i}` });
|
|
1134
|
+
await (0, peers_sdk_1.sleep)(1000);
|
|
1135
|
+
}
|
|
1136
|
+
await (0, peers_sdk_1.sleep)(2000);
|
|
1137
|
+
await me.syncWithRemoteDevice(remotePeer.remotePeer);
|
|
1138
|
+
await peer.syncWithRemoteDevice(me);
|
|
1139
|
+
console.log(me.getConnections()[0]);
|
|
1140
|
+
expect(me.getConnections()[0].errorRate).toBeGreaterThan(0);
|
|
1141
|
+
const myNotes = await notesTable.list();
|
|
1142
|
+
const peerNotesTable = getNotesTable(peer);
|
|
1143
|
+
const peerNotes = await peerNotesTable.list();
|
|
1144
|
+
expect(peerNotes).toEqual(myNotes);
|
|
1145
|
+
await Promise.all([
|
|
1146
|
+
me.dispose(),
|
|
1147
|
+
peer.dispose(),
|
|
1148
|
+
]);
|
|
1149
|
+
});
|
|
1150
|
+
it.skip("should should prioritize devices by lowest error * latency", async () => {
|
|
1151
|
+
const peerCnt = 4;
|
|
1152
|
+
const remotePeers = (0, lodash_1.range)(peerCnt).map((i) => {
|
|
1153
|
+
const errorRate = 0; //0.1 * i;
|
|
1154
|
+
const latencyMs = 100 * (i + 1);
|
|
1155
|
+
return buildRemotePeer({ errorRate, latencyMs });
|
|
1156
|
+
});
|
|
1157
|
+
const peers = remotePeers.map(p => p.peer);
|
|
1158
|
+
// ensures the table exists and is being tracked
|
|
1159
|
+
for (const peer of peers) {
|
|
1160
|
+
getNotesTable(peer);
|
|
1161
|
+
}
|
|
1162
|
+
for (const peer1 of peers) {
|
|
1163
|
+
for (const peer2 of peers) {
|
|
1164
|
+
if (peer1 === peer2)
|
|
1165
|
+
continue;
|
|
1166
|
+
const remotePeer = remotePeers.find(p => p.peer === peer2)?.remotePeer;
|
|
1167
|
+
if (!remotePeer)
|
|
1168
|
+
throw new Error("Remote peer not found");
|
|
1169
|
+
await peer1.addConnection(remotePeer, { resyncInterval: 1000 });
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
for (const peer of peers) {
|
|
1173
|
+
expect(peer.getConnections().length).toBe(peerCnt - 1);
|
|
1174
|
+
}
|
|
1175
|
+
// add a whole bunch of notes to one peer
|
|
1176
|
+
const notesTable = getNotesTable(peers[0]);
|
|
1177
|
+
for (let i = 0; i < 10; i++) {
|
|
1178
|
+
await notesTable.save({ noteId: (0, peers_sdk_1.newid)(), title: `Note ${i}` });
|
|
1179
|
+
}
|
|
1180
|
+
// add notes to all peers
|
|
1181
|
+
for (const peer of peers) {
|
|
1182
|
+
const notesTable = getNotesTable(peer);
|
|
1183
|
+
await notesTable.save({ noteId: (0, peers_sdk_1.newid)(), title: `Note from peer ${peer.deviceId}` });
|
|
1184
|
+
await notesTable.save({ noteId: (0, peers_sdk_1.newid)(), title: `Note from peer ${peer.deviceId}` });
|
|
1185
|
+
await notesTable.save({ noteId: (0, peers_sdk_1.newid)(), title: `Note from peer ${peer.deviceId}` });
|
|
1186
|
+
}
|
|
1187
|
+
await (0, peers_sdk_1.sleep)(8000);
|
|
1188
|
+
const myNotes = await notesTable.list();
|
|
1189
|
+
expect(myNotes.length).toBeGreaterThan(peerCnt);
|
|
1190
|
+
for (const peer of peers) {
|
|
1191
|
+
const peerNotesTable = getNotesTable(peer);
|
|
1192
|
+
const peerNotes = await peerNotesTable.list();
|
|
1193
|
+
expect(peerNotes).toEqual(myNotes);
|
|
1194
|
+
}
|
|
1195
|
+
const peer1DeviceId = peers[0].deviceId;
|
|
1196
|
+
const peer2DeviceId = peers[1].deviceId;
|
|
1197
|
+
for (const peer of peers) {
|
|
1198
|
+
await peer.electPreferredConnections();
|
|
1199
|
+
const networkInfo = await peer.getNetworkInfo();
|
|
1200
|
+
if (peer.deviceId === peer1DeviceId) {
|
|
1201
|
+
expect(networkInfo.preferredDeviceIds).toEqual([peer2DeviceId]);
|
|
1202
|
+
}
|
|
1203
|
+
else {
|
|
1204
|
+
expect(networkInfo.preferredDeviceIds).toEqual([peer1DeviceId]);
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
for (const peer of peers) {
|
|
1208
|
+
await peer.electPreferredConnections();
|
|
1209
|
+
const networkInfo = await peer.getNetworkInfo();
|
|
1210
|
+
if (peer.deviceId === peer1DeviceId) {
|
|
1211
|
+
expect(networkInfo.preferredDeviceIds).toEqual([peer2DeviceId]);
|
|
1212
|
+
}
|
|
1213
|
+
else {
|
|
1214
|
+
expect(networkInfo.preferredDeviceIds).toEqual([peer1DeviceId]);
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
const peer3DeviceId = peers[2].deviceId;
|
|
1218
|
+
const peer4DeviceId = peers[3].deviceId;
|
|
1219
|
+
const peer1 = peers[0];
|
|
1220
|
+
expect(peer1.getConnections().map(c => c.deviceId)).toEqual([peer2DeviceId, peer3DeviceId, peer4DeviceId]);
|
|
1221
|
+
// TODO this has been moved to the connection manager
|
|
1222
|
+
// peer1.removeLeastPreferredConnection();
|
|
1223
|
+
// expect(peer1.getConnections().map(c => c.deviceId)).toEqual([peer2DeviceId, peer3DeviceId]);
|
|
1224
|
+
await Promise.all(peers.map(peer => peer.dispose()));
|
|
1225
|
+
});
|
|
1226
|
+
it.skip("should prune connections after 30 and changes should fully sync", async () => {
|
|
1227
|
+
const peerCnt = 50;
|
|
1228
|
+
const remotePeers = (0, lodash_1.range)(peerCnt).map(() => {
|
|
1229
|
+
const errorRate = 0;
|
|
1230
|
+
const latencyMs = 1;
|
|
1231
|
+
return buildRemotePeer({ errorRate, latencyMs });
|
|
1232
|
+
});
|
|
1233
|
+
const peers = remotePeers.map(p => p.peer);
|
|
1234
|
+
for (const peer of peers) {
|
|
1235
|
+
getNotesTable(peer);
|
|
1236
|
+
}
|
|
1237
|
+
// connect all peers to each other
|
|
1238
|
+
let connCnt = 0;
|
|
1239
|
+
for (const peer1 of peers) {
|
|
1240
|
+
for (const peer2 of peers) {
|
|
1241
|
+
if (peer1 === peer2)
|
|
1242
|
+
continue;
|
|
1243
|
+
const remotePeer = remotePeers.find(p => p.peer === peer2)?.remotePeer;
|
|
1244
|
+
if (!remotePeer)
|
|
1245
|
+
throw new Error("Remote peer not found");
|
|
1246
|
+
connCnt++;
|
|
1247
|
+
await peer1.addConnection(remotePeer, {
|
|
1248
|
+
// resyncInterval: 10_000,
|
|
1249
|
+
async onClose() {
|
|
1250
|
+
peer2.removeConnection(peer1.deviceId);
|
|
1251
|
+
peer1.removeConnection(peer2.deviceId);
|
|
1252
|
+
},
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
// await sleep(2000);
|
|
1257
|
+
let stable = false;
|
|
1258
|
+
while (!stable) {
|
|
1259
|
+
console.log("Waiting for stable network info...");
|
|
1260
|
+
const peers1NI = [];
|
|
1261
|
+
for (const peer of peers) {
|
|
1262
|
+
const networkInfo = await peer.getNetworkInfo();
|
|
1263
|
+
peers1NI.push({ deviceId: peer.deviceId, preferredDeviceIds: networkInfo.preferredDeviceIds.length });
|
|
1264
|
+
}
|
|
1265
|
+
await Promise.all(peers.map(peer => peer.electPreferredConnections()));
|
|
1266
|
+
// await sleep(2000);
|
|
1267
|
+
const peers2NI = [];
|
|
1268
|
+
for (const peer of peers) {
|
|
1269
|
+
const networkInfo = await peer.getNetworkInfo();
|
|
1270
|
+
peers2NI.push({ deviceId: peer.deviceId, preferredDeviceIds: networkInfo.preferredDeviceIds.length });
|
|
1271
|
+
}
|
|
1272
|
+
stable = (0, lodash_1.isEqual)(peers1NI, peers2NI);
|
|
1273
|
+
console.log({ peers1NI, peers2NI });
|
|
1274
|
+
}
|
|
1275
|
+
// add a whole bunch of notes and changes
|
|
1276
|
+
for (const peer of peers) {
|
|
1277
|
+
const notesTable = getNotesTable(peer);
|
|
1278
|
+
for (let i = 0; i < 2; i++) {
|
|
1279
|
+
await notesTable.save({ noteId: (0, peers_sdk_1.newid)(), title: `Note ${i}` });
|
|
1280
|
+
const randomNote = (0, lodash_1.shuffle)(await notesTable.list(undefined, { pageSize: 10 }))[0];
|
|
1281
|
+
if (!randomNote)
|
|
1282
|
+
continue;
|
|
1283
|
+
randomNote.title += ` Updated ${i}`;
|
|
1284
|
+
await notesTable.save(randomNote);
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
// give time for all peers to sync
|
|
1288
|
+
await (0, peers_sdk_1.sleep)(5000);
|
|
1289
|
+
const myNotes = await getNotesTable(peers[0]).list();
|
|
1290
|
+
expect(myNotes.length).toBeGreaterThan(peerCnt);
|
|
1291
|
+
for (const peer of peers) {
|
|
1292
|
+
const peerNotesTable = getNotesTable(peer);
|
|
1293
|
+
const peerNotes = await peerNotesTable.list();
|
|
1294
|
+
expect(peerNotes.length).toEqual(myNotes.length);
|
|
1295
|
+
expect(peerNotes).toEqual(myNotes);
|
|
1296
|
+
expect(peer.getConnections().length).toBeLessThanOrEqual(peers_sdk_1.PeerDeviceConsts.MAX_CONNECTIONS);
|
|
1297
|
+
}
|
|
1298
|
+
await Promise.all(peers.map(peer => peer.dispose()));
|
|
1299
|
+
});
|
|
1300
|
+
it.skip("should prune connections after 30 is reached but still sync the full network", async () => {
|
|
1301
|
+
const peerCnt = 50;
|
|
1302
|
+
const remotePeers = (0, lodash_1.range)(peerCnt).map(() => {
|
|
1303
|
+
const errorRate = 0;
|
|
1304
|
+
const latencyMs = 1;
|
|
1305
|
+
return buildRemotePeer({ errorRate, latencyMs });
|
|
1306
|
+
});
|
|
1307
|
+
const peers = remotePeers.map(p => p.peer);
|
|
1308
|
+
for (const peer of peers) {
|
|
1309
|
+
getNotesTable(peer);
|
|
1310
|
+
}
|
|
1311
|
+
// connect all peers to each other
|
|
1312
|
+
let connCnt = 0;
|
|
1313
|
+
for (const peer1 of peers) {
|
|
1314
|
+
for (const peer2 of peers) {
|
|
1315
|
+
if (peer1 === peer2)
|
|
1316
|
+
continue;
|
|
1317
|
+
const remotePeer = remotePeers.find(p => p.peer === peer2)?.remotePeer;
|
|
1318
|
+
if (!remotePeer)
|
|
1319
|
+
throw new Error("Remote peer not found");
|
|
1320
|
+
connCnt++;
|
|
1321
|
+
await peer1.addConnection(remotePeer, {
|
|
1322
|
+
resyncInterval: 4_000, async onClose() {
|
|
1323
|
+
peer2.removeConnection(peer1.deviceId);
|
|
1324
|
+
peer1.removeConnection(peer2.deviceId);
|
|
1325
|
+
},
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
// await sleep(2000);
|
|
1330
|
+
device_sync_1.DeviceSync.listChangesCount = 0;
|
|
1331
|
+
device_sync_1.DeviceSync.applyChangesCount = 0;
|
|
1332
|
+
device_sync_1.DeviceSync.getNetworkInfoCount = 0;
|
|
1333
|
+
device_sync_1.DeviceSync.electPreferredConnectionsCount = 0;
|
|
1334
|
+
// add a whole bunch of notes and changes
|
|
1335
|
+
for (const peer of peers) {
|
|
1336
|
+
const notesTable = getNotesTable(peer);
|
|
1337
|
+
for (let i = 0; i < 2; i++) {
|
|
1338
|
+
await notesTable.save({ noteId: (0, peers_sdk_1.newid)(), title: `Note ${i}` });
|
|
1339
|
+
const randomNote = (0, lodash_1.shuffle)(await notesTable.list(undefined, { pageSize: 10 }))[0];
|
|
1340
|
+
if (!randomNote)
|
|
1341
|
+
continue;
|
|
1342
|
+
randomNote.title += ` Updated ${i}`;
|
|
1343
|
+
await notesTable.save(randomNote);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
// give time for all peers to sync
|
|
1347
|
+
await (0, peers_sdk_1.sleep)(5000);
|
|
1348
|
+
const myNotes = await getNotesTable(peers[0]).list();
|
|
1349
|
+
expect(myNotes.length).toBeGreaterThan(peerCnt);
|
|
1350
|
+
for (const peer of peers) {
|
|
1351
|
+
const peerNotesTable = getNotesTable(peer);
|
|
1352
|
+
const peerNotes = await peerNotesTable.list();
|
|
1353
|
+
expect(peerNotes.length).toEqual(myNotes.length);
|
|
1354
|
+
expect(peerNotes).toEqual(myNotes);
|
|
1355
|
+
expect(peer.getConnections().length).toBeLessThanOrEqual(peers_sdk_1.PeerDeviceConsts.MAX_CONNECTIONS);
|
|
1356
|
+
}
|
|
1357
|
+
const totalNetworkOps = device_sync_1.DeviceSync.listChangesCount + device_sync_1.DeviceSync.applyChangesCount + device_sync_1.DeviceSync.getNetworkInfoCount + device_sync_1.DeviceSync.electPreferredConnectionsCount;
|
|
1358
|
+
console.log("Total count: ", totalNetworkOps);
|
|
1359
|
+
console.log("Total ops per peer: ", Math.round(totalNetworkOps / peerCnt));
|
|
1360
|
+
let stable = false;
|
|
1361
|
+
while (!stable) {
|
|
1362
|
+
await (0, peers_sdk_1.sleep)(2000);
|
|
1363
|
+
const deviceIdPriority1 = {};
|
|
1364
|
+
for (const peer of peers) {
|
|
1365
|
+
let preferredDeviceIds = (await peer.getNetworkInfo()).preferredDeviceIds;
|
|
1366
|
+
const connections = peer.getConnections();
|
|
1367
|
+
preferredDeviceIds = preferredDeviceIds.filter(deviceId => connections.find(c => c.deviceId === deviceId));
|
|
1368
|
+
expect(preferredDeviceIds.length).toBeLessThanOrEqual(peers_sdk_1.PeerDeviceConsts.MAX_CONNECTIONS);
|
|
1369
|
+
preferredDeviceIds.forEach((deviceId) => {
|
|
1370
|
+
deviceIdPriority1[deviceId] = (deviceIdPriority1[deviceId] || 0) + 1;
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1373
|
+
if (1 + 1 === 2) {
|
|
1374
|
+
// setTimeout(() => {
|
|
1375
|
+
// console.log("Device ID priority1: ", deviceIdPriority1);
|
|
1376
|
+
// console.log("Total number of preferred connections: ", Object.values(deviceIdPriority1).reduce((a, b) => a + b, 0));
|
|
1377
|
+
// }, 2000);
|
|
1378
|
+
break;
|
|
1379
|
+
}
|
|
1380
|
+
for (const peer of peers) {
|
|
1381
|
+
await (0, peers_sdk_1.sleep)(10);
|
|
1382
|
+
peer.electPreferredConnections();
|
|
1383
|
+
}
|
|
1384
|
+
await (0, peers_sdk_1.sleep)(2000);
|
|
1385
|
+
const deviceIdPriority2 = {};
|
|
1386
|
+
for (const peer of peers) {
|
|
1387
|
+
let preferredDeviceIds = (await peer.getNetworkInfo()).preferredDeviceIds;
|
|
1388
|
+
const connections = peer.getConnections();
|
|
1389
|
+
preferredDeviceIds = preferredDeviceIds.filter(deviceId => connections.find(c => c.deviceId === deviceId));
|
|
1390
|
+
expect(preferredDeviceIds.length).toBeLessThanOrEqual(peers_sdk_1.PeerDeviceConsts.MAX_CONNECTIONS);
|
|
1391
|
+
preferredDeviceIds.forEach((deviceId) => {
|
|
1392
|
+
deviceIdPriority2[deviceId] = (deviceIdPriority2[deviceId] || 0) + 1;
|
|
1393
|
+
});
|
|
1394
|
+
}
|
|
1395
|
+
Object.keys(deviceIdPriority1).forEach((key) => {
|
|
1396
|
+
if (deviceIdPriority1[key] < 2) {
|
|
1397
|
+
delete deviceIdPriority1[key];
|
|
1398
|
+
}
|
|
1399
|
+
});
|
|
1400
|
+
Object.keys(deviceIdPriority2).forEach((key) => {
|
|
1401
|
+
if (deviceIdPriority2[key] < 2) {
|
|
1402
|
+
delete deviceIdPriority2[key];
|
|
1403
|
+
}
|
|
1404
|
+
});
|
|
1405
|
+
console.log("Device ID priority1: ", deviceIdPriority1);
|
|
1406
|
+
console.log("Device ID priority2: ", deviceIdPriority2);
|
|
1407
|
+
if ((0, lodash_1.isEqual)(deviceIdPriority1, deviceIdPriority2)) {
|
|
1408
|
+
stable = true;
|
|
1409
|
+
const highestPriority = (0, lodash_1.sortBy)(Object.entries(deviceIdPriority1), ([k, v]) => v).map(([k, v]) => k).reverse();
|
|
1410
|
+
const peer = peers.find(p => p.deviceId === highestPriority[0]);
|
|
1411
|
+
if (!peer)
|
|
1412
|
+
throw new Error("Peer not found");
|
|
1413
|
+
const networkInfo = await peer.getNetworkInfo();
|
|
1414
|
+
console.log(networkInfo);
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
await Promise.all(peers.map(peer => peer.dispose()));
|
|
1418
|
+
});
|
|
1419
|
+
it.skip("very large networks - should prune connections after 30 but maintain a fully connected network", async () => {
|
|
1420
|
+
const peerCnt = 110;
|
|
1421
|
+
const remotePeers = (0, lodash_1.range)(peerCnt).map(() => {
|
|
1422
|
+
const errorRate = 0;
|
|
1423
|
+
const latencyMs = 1;
|
|
1424
|
+
return buildRemotePeer({ errorRate, latencyMs });
|
|
1425
|
+
});
|
|
1426
|
+
const peers = remotePeers.map(p => p.peer);
|
|
1427
|
+
for (const peer of peers) {
|
|
1428
|
+
getNotesTable(peer);
|
|
1429
|
+
}
|
|
1430
|
+
// connect all peers to each other
|
|
1431
|
+
let connCnt = 0;
|
|
1432
|
+
for (const peer1 of peers) {
|
|
1433
|
+
for (const peer2 of peers) {
|
|
1434
|
+
if (peer1 === peer2)
|
|
1435
|
+
continue;
|
|
1436
|
+
const remotePeer = remotePeers.find(p => p.peer === peer2)?.remotePeer;
|
|
1437
|
+
if (!remotePeer)
|
|
1438
|
+
throw new Error("Remote peer not found");
|
|
1439
|
+
connCnt++;
|
|
1440
|
+
await peer1.addConnection(remotePeer, {
|
|
1441
|
+
// resyncInterval: 4_000,
|
|
1442
|
+
async onClose() {
|
|
1443
|
+
peer2.removeConnection(peer1.deviceId);
|
|
1444
|
+
peer1.removeConnection(peer2.deviceId);
|
|
1445
|
+
},
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
// await sleep(2000);
|
|
1450
|
+
// PeerDevice.listChangesCount = 0;
|
|
1451
|
+
// PeerDevice.applyChangesCount = 0;
|
|
1452
|
+
// PeerDevice.getNetworkInfoCount = 0;
|
|
1453
|
+
// PeerDevice.electPreferredConnectionsCount = 0;
|
|
1454
|
+
let stable = false;
|
|
1455
|
+
while (!stable) {
|
|
1456
|
+
await (0, peers_sdk_1.sleep)(2000);
|
|
1457
|
+
const deviceIdPriority1 = {};
|
|
1458
|
+
for (const peer of peers) {
|
|
1459
|
+
let preferredDeviceIds = (await peer.getNetworkInfo()).preferredDeviceIds;
|
|
1460
|
+
const connections = peer.getConnections();
|
|
1461
|
+
preferredDeviceIds = preferredDeviceIds.filter(deviceId => connections.find(c => c.deviceId === deviceId));
|
|
1462
|
+
expect(preferredDeviceIds.length).toBeLessThanOrEqual(peers_sdk_1.PeerDeviceConsts.MAX_CONNECTIONS);
|
|
1463
|
+
preferredDeviceIds.forEach((deviceId) => {
|
|
1464
|
+
deviceIdPriority1[deviceId] = (deviceIdPriority1[deviceId] || 0) + 1;
|
|
1465
|
+
});
|
|
1466
|
+
}
|
|
1467
|
+
for (const peer of peers) {
|
|
1468
|
+
await Promise.race([
|
|
1469
|
+
(0, peers_sdk_1.sleep)(20),
|
|
1470
|
+
peer.electPreferredConnections(),
|
|
1471
|
+
]);
|
|
1472
|
+
}
|
|
1473
|
+
await (0, peers_sdk_1.sleep)(2000);
|
|
1474
|
+
const deviceIdPriority2 = {};
|
|
1475
|
+
for (const peer of peers) {
|
|
1476
|
+
let preferredDeviceIds = (await peer.getNetworkInfo()).preferredDeviceIds;
|
|
1477
|
+
const connections = peer.getConnections();
|
|
1478
|
+
preferredDeviceIds = preferredDeviceIds.filter(deviceId => connections.find(c => c.deviceId === deviceId));
|
|
1479
|
+
expect(preferredDeviceIds.length).toBeLessThanOrEqual(peers_sdk_1.PeerDeviceConsts.MAX_CONNECTIONS);
|
|
1480
|
+
preferredDeviceIds.forEach((deviceId) => {
|
|
1481
|
+
deviceIdPriority2[deviceId] = (deviceIdPriority2[deviceId] || 0) + 1;
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1484
|
+
if (!Object.keys(deviceIdPriority2).length) {
|
|
1485
|
+
continue;
|
|
1486
|
+
}
|
|
1487
|
+
Object.keys(deviceIdPriority1).forEach((key) => {
|
|
1488
|
+
if (deviceIdPriority1[key] < 3) {
|
|
1489
|
+
delete deviceIdPriority1[key];
|
|
1490
|
+
}
|
|
1491
|
+
});
|
|
1492
|
+
Object.keys(deviceIdPriority2).forEach((key) => {
|
|
1493
|
+
if (deviceIdPriority2[key] < 3) {
|
|
1494
|
+
delete deviceIdPriority2[key];
|
|
1495
|
+
}
|
|
1496
|
+
});
|
|
1497
|
+
console.log("Device ID priority1: ", deviceIdPriority1);
|
|
1498
|
+
console.log("Device ID priority2: ", deviceIdPriority2);
|
|
1499
|
+
console.log("Total number of priority connections: ", Object.values(deviceIdPriority1).reduce((a, b) => a + b, 0));
|
|
1500
|
+
// walk the network but only through priority connections
|
|
1501
|
+
const connectedDevices = new Set();
|
|
1502
|
+
let connectedPeers = [peers[0]];
|
|
1503
|
+
while (connectedPeers.length) {
|
|
1504
|
+
const peer = connectedPeers.pop();
|
|
1505
|
+
if (!peer)
|
|
1506
|
+
break;
|
|
1507
|
+
connectedDevices.add(peer.deviceId);
|
|
1508
|
+
const priorityDeviceIds = peer.priorityDeviceIds.priorityDeviceIds;
|
|
1509
|
+
for (const deviceId of priorityDeviceIds) {
|
|
1510
|
+
if (!connectedDevices.has(deviceId)) {
|
|
1511
|
+
const peer = peers.find(p => p.deviceId === deviceId);
|
|
1512
|
+
if (!peer)
|
|
1513
|
+
throw new Error("Peer not found");
|
|
1514
|
+
connectedPeers.push(peer);
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
// // check if all devices are a preferred connection by at least one other device
|
|
1519
|
+
// const preferredDeviceIds = new Set<string>();
|
|
1520
|
+
// for (const peer of peers) {
|
|
1521
|
+
// peer.priorityDeviceIds.preferredDeviceIds.forEach((deviceId) => {
|
|
1522
|
+
// preferredDeviceIds.add(deviceId);
|
|
1523
|
+
// });
|
|
1524
|
+
// }
|
|
1525
|
+
if (connectedDevices.size < peerCnt) {
|
|
1526
|
+
console.warn("Could not walk entire network through just priority connections");
|
|
1527
|
+
}
|
|
1528
|
+
// if (preferredDeviceIds.size < peerCnt) {
|
|
1529
|
+
// console.warn("Not all devices are connected through preferred connections");
|
|
1530
|
+
// }
|
|
1531
|
+
if ((0, lodash_1.isEqual)(deviceIdPriority1, deviceIdPriority2)) {
|
|
1532
|
+
stable = true;
|
|
1533
|
+
console.log("highest priority devices: ", (0, lodash_1.sortBy)(Object.entries(deviceIdPriority1), ([k, v]) => v).map(([k, v]) => [k, v]).reverse().slice(0, 20));
|
|
1534
|
+
expect(connectedDevices.size).toEqual(peerCnt);
|
|
1535
|
+
// expect(preferredDeviceIds.size).toEqual(peerCnt);
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
await Promise.all(peers.map(peer => peer.dispose()));
|
|
1539
|
+
});
|
|
1540
|
+
// For some reason this test fails when run with the rest but works when run alone
|
|
1541
|
+
it.skip("should eventually recover when a change has data that doesn't match the schema", async () => {
|
|
1542
|
+
const peer1 = buildPeer();
|
|
1543
|
+
const peer2 = buildPeer();
|
|
1544
|
+
const peer1Notes = getNotesTable(peer1);
|
|
1545
|
+
const peer2Notes = getNotesTable(peer2);
|
|
1546
|
+
const noteId = (0, peers_sdk_1.newid)();
|
|
1547
|
+
const note1 = await peer1Notes.save({ noteId, title: "note inserted" });
|
|
1548
|
+
const note2 = await peer1Notes.save({ noteId: (0, peers_sdk_1.newid)(), title: "note inserted" });
|
|
1549
|
+
const now = Date.now();
|
|
1550
|
+
await peer1.changeTrackingTable.insert({
|
|
1551
|
+
changeId: (0, peers_sdk_1.newid)(),
|
|
1552
|
+
tableName: notesTableName,
|
|
1553
|
+
recordId: noteId,
|
|
1554
|
+
timestamp: now,
|
|
1555
|
+
timestampApplied: now,
|
|
1556
|
+
changeType: 'update',
|
|
1557
|
+
newRecord: { noteId, title: 1 },
|
|
1558
|
+
jsonDiff: [{ op: 'replace', path: '/title', value: 1 }]
|
|
1559
|
+
});
|
|
1560
|
+
await peer2.syncWithRemoteDevice(peer1);
|
|
1561
|
+
let notes1 = await peer1Notes.list();
|
|
1562
|
+
let notes2 = await peer2Notes.list();
|
|
1563
|
+
// Peer1 has both notes
|
|
1564
|
+
expect(notes1.length).toEqual(2);
|
|
1565
|
+
// Peer2 has only the second note, because the first one has the erroneous change which is preventing it from being synced
|
|
1566
|
+
expect(notes2.length).toEqual(1);
|
|
1567
|
+
// peer1 overwrites the bad change
|
|
1568
|
+
await peer1Notes.update({ noteId, title: "note updated" });
|
|
1569
|
+
await peer2.syncWithRemoteDevice(peer1);
|
|
1570
|
+
notes1 = await peer1Notes.list();
|
|
1571
|
+
notes2 = await peer2Notes.list();
|
|
1572
|
+
// this time the first note made it through
|
|
1573
|
+
expect(notes1).toEqual(notes2);
|
|
1574
|
+
});
|
|
1575
|
+
it("should handle changes that can't be inserted", async () => {
|
|
1576
|
+
const peer1 = buildPeer();
|
|
1577
|
+
const peer1Notes = getNotesTable(peer1);
|
|
1578
|
+
const recordId = (0, peers_sdk_1.newid)();
|
|
1579
|
+
await peer1.applyChanges([
|
|
1580
|
+
{
|
|
1581
|
+
changeId: (0, peers_sdk_1.newid)(),
|
|
1582
|
+
tableName: notesTableName,
|
|
1583
|
+
recordId,
|
|
1584
|
+
timestamp: Date.now(),
|
|
1585
|
+
timestampApplied: Date.now(),
|
|
1586
|
+
changeType: 'insert',
|
|
1587
|
+
newRecord: { noteId: recordId, title: "note inserted" },
|
|
1588
|
+
},
|
|
1589
|
+
// @ts-ignore
|
|
1590
|
+
{
|
|
1591
|
+
changeId: (0, peers_sdk_1.newid)(),
|
|
1592
|
+
tableName: notesTableName,
|
|
1593
|
+
recordId,
|
|
1594
|
+
timestamp: Date.now(),
|
|
1595
|
+
timestampApplied: Date.now(),
|
|
1596
|
+
changeType: 'update',
|
|
1597
|
+
newRecord: { noteId: recordId, title: "note inserted" },
|
|
1598
|
+
},
|
|
1599
|
+
{
|
|
1600
|
+
changeId: (0, peers_sdk_1.newid)(),
|
|
1601
|
+
tableName: notesTableName,
|
|
1602
|
+
recordId,
|
|
1603
|
+
timestamp: Date.now(),
|
|
1604
|
+
timestampApplied: Date.now(),
|
|
1605
|
+
changeType: 'update',
|
|
1606
|
+
newRecord: { noteId: recordId, title: "note updated" },
|
|
1607
|
+
jsonDiff: [{ op: 'replace', path: '/title', value: "note updated" }]
|
|
1608
|
+
}
|
|
1609
|
+
]);
|
|
1610
|
+
const notes = await peer1Notes.list();
|
|
1611
|
+
expect(notes).toEqual([{ noteId: recordId, title: "note updated" }]);
|
|
1612
|
+
});
|
|
1613
|
+
it.todo("should establish connections with remote peers to try to become 'well' connected");
|
|
1614
|
+
it.todo("should correctly apply a batch of changes that includes multiple tables with interleaved timestamps");
|
|
1615
|
+
it.todo("should ensure connected devices agree on the current time");
|
|
1616
|
+
it.todo("should not allow changes with a timestamp or timestampApplied in the future");
|
|
1617
|
+
});
|
|
1618
|
+
//# sourceMappingURL=device-sync.test.js.map
|