cojson 0.13.10 β 0.13.12
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +15 -0
- package/dist/CoValuesStore.d.ts +3 -1
- package/dist/CoValuesStore.d.ts.map +1 -1
- package/dist/CoValuesStore.js +7 -6
- package/dist/CoValuesStore.js.map +1 -1
- package/dist/PeerKnownStates.d.ts +5 -25
- package/dist/PeerKnownStates.d.ts.map +1 -1
- package/dist/PeerKnownStates.js +7 -20
- package/dist/PeerKnownStates.js.map +1 -1
- package/dist/PeerState.d.ts +14 -9
- package/dist/PeerState.d.ts.map +1 -1
- package/dist/PeerState.js +51 -8
- package/dist/PeerState.js.map +1 -1
- package/dist/SyncStateManager.js +2 -2
- package/dist/SyncStateManager.js.map +1 -1
- package/dist/coValueCore.js +2 -2
- package/dist/coValueCore.js.map +1 -1
- package/dist/coValueState.d.ts +21 -46
- package/dist/coValueState.d.ts.map +1 -1
- package/dist/coValueState.js +174 -246
- package/dist/coValueState.js.map +1 -1
- package/dist/coValues/coList.d.ts +13 -2
- package/dist/coValues/coList.d.ts.map +1 -1
- package/dist/coValues/coList.js +60 -34
- package/dist/coValues/coList.js.map +1 -1
- package/dist/coValues/coPlainText.d.ts +45 -0
- package/dist/coValues/coPlainText.d.ts.map +1 -1
- package/dist/coValues/coPlainText.js +61 -11
- package/dist/coValues/coPlainText.js.map +1 -1
- package/dist/coValues/group.js +2 -2
- package/dist/coValues/group.js.map +1 -1
- package/dist/exports.d.ts +2 -4
- package/dist/exports.d.ts.map +1 -1
- package/dist/exports.js +1 -2
- package/dist/exports.js.map +1 -1
- package/dist/localNode.d.ts.map +1 -1
- package/dist/localNode.js +20 -16
- package/dist/localNode.js.map +1 -1
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +40 -92
- package/dist/sync.js.map +1 -1
- package/dist/tests/PeerKnownStates.test.js +9 -14
- package/dist/tests/PeerKnownStates.test.js.map +1 -1
- package/dist/tests/PeerState.test.js +22 -34
- package/dist/tests/PeerState.test.js.map +1 -1
- package/dist/tests/coList.test.js +63 -0
- package/dist/tests/coList.test.js.map +1 -1
- package/dist/tests/coPlainText.test.js +66 -11
- package/dist/tests/coPlainText.test.js.map +1 -1
- package/dist/tests/coValueState.test.js +57 -104
- package/dist/tests/coValueState.test.js.map +1 -1
- package/dist/tests/group.test.js +1 -2
- package/dist/tests/group.test.js.map +1 -1
- package/dist/tests/messagesTestUtils.d.ts +4 -1
- package/dist/tests/messagesTestUtils.d.ts.map +1 -1
- package/dist/tests/messagesTestUtils.js +10 -0
- package/dist/tests/messagesTestUtils.js.map +1 -1
- package/dist/tests/sync.mesh.test.js +65 -3
- package/dist/tests/sync.mesh.test.js.map +1 -1
- package/dist/tests/sync.peerReconciliation.test.js +8 -8
- package/dist/tests/sync.peerReconciliation.test.js.map +1 -1
- package/dist/tests/sync.test.js +6 -4
- package/dist/tests/sync.test.js.map +1 -1
- package/package.json +1 -1
- package/src/CoValuesStore.ts +9 -6
- package/src/PeerKnownStates.ts +19 -56
- package/src/PeerState.ts +69 -13
- package/src/SyncStateManager.ts +2 -2
- package/src/coValueCore.ts +2 -2
- package/src/coValueState.ts +197 -317
- package/src/coValues/coList.ts +84 -44
- package/src/coValues/coPlainText.ts +75 -11
- package/src/coValues/group.ts +2 -2
- package/src/exports.ts +0 -6
- package/src/localNode.ts +30 -21
- package/src/sync.ts +46 -95
- package/src/tests/PeerKnownStates.test.ts +9 -14
- package/src/tests/PeerState.test.ts +27 -40
- package/src/tests/coList.test.ts +83 -0
- package/src/tests/coPlainText.test.ts +81 -11
- package/src/tests/coValueState.test.ts +55 -106
- package/src/tests/group.test.ts +2 -2
- package/src/tests/messagesTestUtils.ts +12 -1
- package/src/tests/sync.mesh.test.ts +81 -3
- package/src/tests/sync.peerReconciliation.test.ts +8 -8
- package/src/tests/sync.test.ts +8 -23
- package/dist/storage/FileSystem.d.ts +0 -37
- package/dist/storage/FileSystem.d.ts.map +0 -1
- package/dist/storage/FileSystem.js +0 -48
- package/dist/storage/FileSystem.js.map +0 -1
- package/dist/storage/chunksAndKnownStates.d.ts +0 -7
- package/dist/storage/chunksAndKnownStates.d.ts.map +0 -1
- package/dist/storage/chunksAndKnownStates.js +0 -98
- package/dist/storage/chunksAndKnownStates.js.map +0 -1
- package/dist/storage/index.d.ts +0 -52
- package/dist/storage/index.d.ts.map +0 -1
- package/dist/storage/index.js +0 -335
- package/dist/storage/index.js.map +0 -1
- package/src/storage/FileSystem.ts +0 -113
- package/src/storage/chunksAndKnownStates.ts +0 -137
- package/src/storage/index.ts +0 -531
package/src/sync.ts
CHANGED
|
@@ -163,7 +163,7 @@ export class SyncManager {
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
async handleSyncMessage(msg: SyncMessage, peer: PeerState) {
|
|
166
|
-
if (
|
|
166
|
+
if (this.local.coValuesStore.get(msg.id).isErroredInPeer(peer.id)) {
|
|
167
167
|
logger.warn(
|
|
168
168
|
`Skipping message ${msg.action} on errored coValue ${msg.id} from peer ${peer.id}`,
|
|
169
169
|
);
|
|
@@ -223,11 +223,7 @@ export class SyncManager {
|
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
peer.toldKnownState.add(id);
|
|
226
|
-
peer.
|
|
227
|
-
type: "COMBINE_WITH",
|
|
228
|
-
id: id,
|
|
229
|
-
value: coValue.knownState(),
|
|
230
|
-
});
|
|
226
|
+
peer.combineOptimisticWith(id, coValue.knownState());
|
|
231
227
|
} else if (!peer.toldKnownState.has(id)) {
|
|
232
228
|
this.trySendToPeer(peer, {
|
|
233
229
|
action: "known",
|
|
@@ -255,8 +251,8 @@ export class SyncManager {
|
|
|
255
251
|
for (const id of coValue.getDependedOnCoValues()) {
|
|
256
252
|
const entry = this.local.coValuesStore.get(id);
|
|
257
253
|
|
|
258
|
-
if (entry.
|
|
259
|
-
buildOrderedCoValueList(entry.
|
|
254
|
+
if (entry.isAvailable()) {
|
|
255
|
+
buildOrderedCoValueList(entry.core);
|
|
260
256
|
}
|
|
261
257
|
}
|
|
262
258
|
|
|
@@ -264,31 +260,25 @@ export class SyncManager {
|
|
|
264
260
|
};
|
|
265
261
|
|
|
266
262
|
for (const entry of this.local.coValuesStore.getValues()) {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
// so we can send the load message in the correct order
|
|
282
|
-
buildOrderedCoValueList(coValue);
|
|
283
|
-
break;
|
|
263
|
+
if (!entry.isAvailable()) {
|
|
264
|
+
// If the coValue is unavailable and we never tried this peer
|
|
265
|
+
// we try to load it from the peer
|
|
266
|
+
if (!peer.toldKnownState.has(entry.id)) {
|
|
267
|
+
await entry.loadFromPeers([peer]).catch((e: unknown) => {
|
|
268
|
+
logger.error("Error sending load", { err: e });
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
} else {
|
|
272
|
+
const coValue = entry.core;
|
|
273
|
+
|
|
274
|
+
// Build the list of coValues ordered by dependency
|
|
275
|
+
// so we can send the load message in the correct order
|
|
276
|
+
buildOrderedCoValueList(coValue);
|
|
284
277
|
}
|
|
285
278
|
|
|
286
279
|
// Fill the missing known states with empty known states
|
|
287
280
|
if (!peer.optimisticKnownStates.has(entry.id)) {
|
|
288
|
-
peer.
|
|
289
|
-
type: "SET_AS_EMPTY",
|
|
290
|
-
id: entry.id,
|
|
291
|
-
});
|
|
281
|
+
peer.setOptimisticKnownState(entry.id, "empty");
|
|
292
282
|
}
|
|
293
283
|
}
|
|
294
284
|
|
|
@@ -403,14 +393,13 @@ export class SyncManager {
|
|
|
403
393
|
* This way we can track part of the data loss that may occur when the other peer is restarted
|
|
404
394
|
*
|
|
405
395
|
*/
|
|
406
|
-
peer.
|
|
407
|
-
type: "SET",
|
|
408
|
-
id: msg.id,
|
|
409
|
-
value: knownStateIn(msg),
|
|
410
|
-
});
|
|
396
|
+
peer.setKnownState(msg.id, knownStateIn(msg));
|
|
411
397
|
const entry = this.local.coValuesStore.get(msg.id);
|
|
412
398
|
|
|
413
|
-
if (
|
|
399
|
+
if (
|
|
400
|
+
entry.highLevelState === "unknown" ||
|
|
401
|
+
entry.highLevelState === "unavailable"
|
|
402
|
+
) {
|
|
414
403
|
const eligiblePeers = this.getServerAndStoragePeers(peer.id);
|
|
415
404
|
|
|
416
405
|
if (eligiblePeers.length === 0) {
|
|
@@ -437,7 +426,7 @@ export class SyncManager {
|
|
|
437
426
|
}
|
|
438
427
|
}
|
|
439
428
|
|
|
440
|
-
if (entry.
|
|
429
|
+
if (entry.highLevelState === "loading") {
|
|
441
430
|
// We need to return from handleLoad immediately and wait for the CoValue to be loaded
|
|
442
431
|
// in a new task, otherwise we might block further incoming content messages that would
|
|
443
432
|
// resolve the CoValue as available. This can happen when we receive fresh
|
|
@@ -467,7 +456,7 @@ export class SyncManager {
|
|
|
467
456
|
err: e,
|
|
468
457
|
});
|
|
469
458
|
});
|
|
470
|
-
} else if (entry.
|
|
459
|
+
} else if (entry.isAvailable()) {
|
|
471
460
|
await this.sendNewContentIncludingDependencies(msg.id, peer);
|
|
472
461
|
} else {
|
|
473
462
|
this.trySendToPeer(peer, {
|
|
@@ -482,28 +471,17 @@ export class SyncManager {
|
|
|
482
471
|
async handleKnownState(msg: KnownStateMessage, peer: PeerState) {
|
|
483
472
|
const entry = this.local.coValuesStore.get(msg.id);
|
|
484
473
|
|
|
485
|
-
peer.
|
|
486
|
-
type: "COMBINE_WITH",
|
|
487
|
-
id: msg.id,
|
|
488
|
-
value: knownStateIn(msg),
|
|
489
|
-
});
|
|
474
|
+
peer.combineWith(msg.id, knownStateIn(msg));
|
|
490
475
|
|
|
491
476
|
// The header is a boolean value that tells us if the other peer do have information about the header.
|
|
492
477
|
// If it's false in this point it means that the coValue is unavailable on the other peer.
|
|
493
|
-
|
|
494
|
-
const availableOnPeer = peer.optimisticKnownStates.get(msg.id)?.header;
|
|
495
|
-
|
|
496
|
-
if (!availableOnPeer) {
|
|
497
|
-
entry.dispatch({
|
|
498
|
-
type: "not-found-in-peer",
|
|
499
|
-
peerId: peer.id,
|
|
500
|
-
});
|
|
501
|
-
}
|
|
478
|
+
const availableOnPeer = peer.optimisticKnownStates.get(msg.id)?.header;
|
|
502
479
|
|
|
503
|
-
|
|
480
|
+
if (!availableOnPeer) {
|
|
481
|
+
entry.markNotFoundInPeer(peer.id);
|
|
504
482
|
}
|
|
505
483
|
|
|
506
|
-
if (entry.
|
|
484
|
+
if (entry.isAvailable()) {
|
|
507
485
|
await this.sendNewContentIncludingDependencies(msg.id, peer);
|
|
508
486
|
}
|
|
509
487
|
}
|
|
@@ -526,23 +504,7 @@ export class SyncManager {
|
|
|
526
504
|
|
|
527
505
|
let coValue: CoValueCore;
|
|
528
506
|
|
|
529
|
-
|
|
530
|
-
* The new content might come while the coValue is loading or is not loaded yet.
|
|
531
|
-
*
|
|
532
|
-
* This might happen when we restart the server because:
|
|
533
|
-
* - The client known state assumes that the coValue is available on the server
|
|
534
|
-
* - The server might not have loaded the coValue yet because it was not requested
|
|
535
|
-
*
|
|
536
|
-
* In this case we need to load the coValue from the storage or other peers.
|
|
537
|
-
*
|
|
538
|
-
* If this load fails we send a correction request, because the client has the wrong assumption that
|
|
539
|
-
* we have the coValue while we don't.
|
|
540
|
-
*/
|
|
541
|
-
if (entry.state.type !== "available" && !msg.header) {
|
|
542
|
-
await this.local.loadCoValueCore(msg.id, peer.id);
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
if (entry.state.type !== "available") {
|
|
507
|
+
if (!entry.isAvailable()) {
|
|
546
508
|
if (!msg.header) {
|
|
547
509
|
this.trySendToPeer(peer, {
|
|
548
510
|
action: "known",
|
|
@@ -560,20 +522,13 @@ export class SyncManager {
|
|
|
560
522
|
return;
|
|
561
523
|
}
|
|
562
524
|
|
|
563
|
-
peer.
|
|
564
|
-
type: "UPDATE_HEADER",
|
|
565
|
-
id: msg.id,
|
|
566
|
-
header: true,
|
|
567
|
-
});
|
|
525
|
+
peer.updateHeader(msg.id, true);
|
|
568
526
|
|
|
569
527
|
coValue = new CoValueCore(msg.header, this.local);
|
|
570
528
|
|
|
571
|
-
entry.
|
|
572
|
-
type: "available",
|
|
573
|
-
coValue,
|
|
574
|
-
});
|
|
529
|
+
entry.markAvailable(coValue, peer.id);
|
|
575
530
|
} else {
|
|
576
|
-
coValue = entry.
|
|
531
|
+
coValue = entry.core;
|
|
577
532
|
}
|
|
578
533
|
|
|
579
534
|
let invalidStateAssumed = false;
|
|
@@ -616,20 +571,18 @@ export class SyncManager {
|
|
|
616
571
|
id: msg.id,
|
|
617
572
|
err: result.error,
|
|
618
573
|
});
|
|
619
|
-
|
|
574
|
+
entry.markErrored(peer.id, result.error);
|
|
620
575
|
continue;
|
|
621
576
|
}
|
|
622
577
|
|
|
623
578
|
this.recordTransactionsSize(newTransactions, peer.role);
|
|
624
579
|
|
|
625
|
-
peer.
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
value:
|
|
630
|
-
newContentForSession.after +
|
|
580
|
+
peer.updateSessionCounter(
|
|
581
|
+
msg.id,
|
|
582
|
+
sessionID,
|
|
583
|
+
newContentForSession.after +
|
|
631
584
|
newContentForSession.newTransactions.length,
|
|
632
|
-
|
|
585
|
+
);
|
|
633
586
|
}
|
|
634
587
|
|
|
635
588
|
if (invalidStateAssumed) {
|
|
@@ -675,11 +628,7 @@ export class SyncManager {
|
|
|
675
628
|
}
|
|
676
629
|
|
|
677
630
|
async handleCorrection(msg: KnownStateMessage, peer: PeerState) {
|
|
678
|
-
peer.
|
|
679
|
-
type: "SET",
|
|
680
|
-
id: msg.id,
|
|
681
|
-
value: knownStateIn(msg),
|
|
682
|
-
});
|
|
631
|
+
peer.setKnownState(msg.id, knownStateIn(msg));
|
|
683
632
|
|
|
684
633
|
return this.sendNewContentIncludingDependencies(msg.id, peer);
|
|
685
634
|
}
|
|
@@ -712,7 +661,8 @@ export class SyncManager {
|
|
|
712
661
|
async actuallySyncCoValue(coValue: CoValueCore) {
|
|
713
662
|
for (const peer of this.peersInPriorityOrder()) {
|
|
714
663
|
if (peer.closed) continue;
|
|
715
|
-
if (
|
|
664
|
+
if (this.local.coValuesStore.get(coValue.id).isErroredInPeer(peer.id))
|
|
665
|
+
continue;
|
|
716
666
|
|
|
717
667
|
if (peer.optimisticKnownStates.has(coValue.id)) {
|
|
718
668
|
await this.sendNewContentIncludingDependencies(coValue.id, peer);
|
|
@@ -767,7 +717,8 @@ export class SyncManager {
|
|
|
767
717
|
const coValues = this.local.coValuesStore.getValues();
|
|
768
718
|
const validCoValues = Array.from(coValues).filter(
|
|
769
719
|
(coValue) =>
|
|
770
|
-
coValue.
|
|
720
|
+
coValue.highLevelState === "available" ||
|
|
721
|
+
coValue.highLevelState === "loading",
|
|
771
722
|
);
|
|
772
723
|
|
|
773
724
|
return Promise.all(
|
|
@@ -9,7 +9,7 @@ describe("PeerKnownStates", () => {
|
|
|
9
9
|
const id = "test-id" as RawCoID;
|
|
10
10
|
const knownState: CoValueKnownState = emptyKnownState(id);
|
|
11
11
|
|
|
12
|
-
peerKnownStates.
|
|
12
|
+
peerKnownStates.set(id, knownState);
|
|
13
13
|
|
|
14
14
|
expect(peerKnownStates.get(id)).toEqual(knownState);
|
|
15
15
|
expect(peerKnownStates.has(id)).toBe(true);
|
|
@@ -19,7 +19,7 @@ describe("PeerKnownStates", () => {
|
|
|
19
19
|
const peerKnownStates = new PeerKnownStates();
|
|
20
20
|
const id = "test-id" as RawCoID;
|
|
21
21
|
|
|
22
|
-
peerKnownStates.
|
|
22
|
+
peerKnownStates.updateHeader(id, true);
|
|
23
23
|
|
|
24
24
|
const result = peerKnownStates.get(id);
|
|
25
25
|
expect(result?.header).toBe(true);
|
|
@@ -30,12 +30,7 @@ describe("PeerKnownStates", () => {
|
|
|
30
30
|
const id = "test-id" as RawCoID;
|
|
31
31
|
const sessionId = "session-1" as SessionID;
|
|
32
32
|
|
|
33
|
-
peerKnownStates.
|
|
34
|
-
type: "UPDATE_SESSION_COUNTER",
|
|
35
|
-
id,
|
|
36
|
-
sessionId,
|
|
37
|
-
value: 5,
|
|
38
|
-
});
|
|
33
|
+
peerKnownStates.updateSessionCounter(id, sessionId, 5);
|
|
39
34
|
|
|
40
35
|
const result = peerKnownStates.get(id);
|
|
41
36
|
expect(result?.sessions[sessionId]).toBe(5);
|
|
@@ -55,8 +50,8 @@ describe("PeerKnownStates", () => {
|
|
|
55
50
|
sessions: { [session2]: 10 },
|
|
56
51
|
};
|
|
57
52
|
|
|
58
|
-
peerKnownStates.
|
|
59
|
-
peerKnownStates.
|
|
53
|
+
peerKnownStates.set(id, initialState);
|
|
54
|
+
peerKnownStates.combineWith(id, combineState);
|
|
60
55
|
|
|
61
56
|
const result = peerKnownStates.get(id);
|
|
62
57
|
expect(result?.sessions).toEqual({ [session1]: 5, [session2]: 10 });
|
|
@@ -71,8 +66,8 @@ describe("PeerKnownStates", () => {
|
|
|
71
66
|
sessions: { [sessionId]: 5 },
|
|
72
67
|
};
|
|
73
68
|
|
|
74
|
-
peerKnownStates.
|
|
75
|
-
peerKnownStates.
|
|
69
|
+
peerKnownStates.set(id, initialState);
|
|
70
|
+
peerKnownStates.set(id, "empty");
|
|
76
71
|
|
|
77
72
|
const result = peerKnownStates.get(id);
|
|
78
73
|
expect(result).toEqual(emptyKnownState(id));
|
|
@@ -84,7 +79,7 @@ describe("PeerKnownStates", () => {
|
|
|
84
79
|
const listener = vi.fn();
|
|
85
80
|
|
|
86
81
|
peerKnownStates.subscribe(listener);
|
|
87
|
-
peerKnownStates.
|
|
82
|
+
peerKnownStates.set(id, "empty");
|
|
88
83
|
|
|
89
84
|
expect(listener).toHaveBeenCalledWith(id, emptyKnownState(id));
|
|
90
85
|
});
|
|
@@ -97,7 +92,7 @@ describe("PeerKnownStates", () => {
|
|
|
97
92
|
const unsubscribe = peerKnownStates.subscribe(listener);
|
|
98
93
|
unsubscribe();
|
|
99
94
|
|
|
100
|
-
peerKnownStates.
|
|
95
|
+
peerKnownStates.set(id, "empty");
|
|
101
96
|
|
|
102
97
|
expect(listener).not.toHaveBeenCalled();
|
|
103
98
|
});
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { describe, expect, test, vi } from "vitest";
|
|
2
|
-
import { PeerKnownStateActions } from "../PeerKnownStates.js";
|
|
3
2
|
import { PeerState } from "../PeerState.js";
|
|
4
3
|
import { CO_VALUE_PRIORITY } from "../priority.js";
|
|
5
|
-
import { Peer, SyncMessage } from "../sync.js";
|
|
4
|
+
import { CoValueKnownState, Peer, SyncMessage } from "../sync.js";
|
|
6
5
|
|
|
7
6
|
function setup() {
|
|
8
7
|
const mockPeer: Peer = {
|
|
@@ -146,16 +145,11 @@ describe("PeerState", () => {
|
|
|
146
145
|
|
|
147
146
|
test("should clone the knownStates into optimisticKnownStates and knownStates when passed as argument", () => {
|
|
148
147
|
const { peerState, mockPeer } = setup();
|
|
149
|
-
|
|
150
|
-
type: "SET",
|
|
148
|
+
peerState.setKnownState("co_z1", {
|
|
151
149
|
id: "co_z1",
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
sessions: {},
|
|
156
|
-
},
|
|
157
|
-
};
|
|
158
|
-
peerState.dispatchToKnownStates(action);
|
|
150
|
+
header: false,
|
|
151
|
+
sessions: {},
|
|
152
|
+
});
|
|
159
153
|
|
|
160
154
|
const newPeerState = new PeerState(mockPeer, peerState.knownStates);
|
|
161
155
|
|
|
@@ -165,25 +159,26 @@ describe("PeerState", () => {
|
|
|
165
159
|
|
|
166
160
|
test("should dispatch to both states", () => {
|
|
167
161
|
const { peerState } = setup();
|
|
168
|
-
const knownStatesSpy = vi.spyOn(peerState.
|
|
162
|
+
const knownStatesSpy = vi.spyOn(peerState._knownStates, "set");
|
|
163
|
+
if (peerState._optimisticKnownStates === "assumeInfallible") {
|
|
164
|
+
throw new Error("Expected normal optimisticKnownStates");
|
|
165
|
+
}
|
|
166
|
+
|
|
169
167
|
const optimisticKnownStatesSpy = vi.spyOn(
|
|
170
|
-
peerState.
|
|
171
|
-
"
|
|
168
|
+
peerState._optimisticKnownStates,
|
|
169
|
+
"set",
|
|
172
170
|
);
|
|
173
171
|
|
|
174
|
-
const
|
|
175
|
-
type: "SET",
|
|
172
|
+
const state: CoValueKnownState = {
|
|
176
173
|
id: "co_z1",
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
header: false,
|
|
180
|
-
sessions: {},
|
|
181
|
-
},
|
|
174
|
+
header: false,
|
|
175
|
+
sessions: {},
|
|
182
176
|
};
|
|
183
|
-
peerState.dispatchToKnownStates(action);
|
|
184
177
|
|
|
185
|
-
|
|
186
|
-
|
|
178
|
+
peerState.setKnownState("co_z1", state);
|
|
179
|
+
|
|
180
|
+
expect(knownStatesSpy).toHaveBeenCalledWith("co_z1", state);
|
|
181
|
+
expect(optimisticKnownStatesSpy).toHaveBeenCalledWith("co_z1", state);
|
|
187
182
|
});
|
|
188
183
|
|
|
189
184
|
test("should use same reference for knownStates and optimisticKnownStates for storage peers", () => {
|
|
@@ -204,28 +199,20 @@ describe("PeerState", () => {
|
|
|
204
199
|
expect(peerState.knownStates).toBe(peerState.optimisticKnownStates);
|
|
205
200
|
|
|
206
201
|
// Verify that dispatching only updates one state
|
|
207
|
-
const knownStatesSpy = vi.spyOn(peerState.
|
|
208
|
-
|
|
209
|
-
peerState.optimisticKnownStates,
|
|
210
|
-
"dispatch",
|
|
211
|
-
);
|
|
202
|
+
const knownStatesSpy = vi.spyOn(peerState._knownStates, "set");
|
|
203
|
+
expect(peerState._optimisticKnownStates).toBe("assumeInfallible");
|
|
212
204
|
|
|
213
|
-
const
|
|
214
|
-
type: "SET",
|
|
205
|
+
const state: CoValueKnownState = {
|
|
215
206
|
id: "co_z1",
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
header: false,
|
|
219
|
-
sessions: {},
|
|
220
|
-
},
|
|
207
|
+
header: false,
|
|
208
|
+
sessions: {},
|
|
221
209
|
};
|
|
222
|
-
|
|
210
|
+
|
|
211
|
+
peerState.setKnownState("co_z1", state);
|
|
223
212
|
|
|
224
213
|
// Only one dispatch should happen since they're the same reference
|
|
225
214
|
expect(knownStatesSpy).toHaveBeenCalledTimes(1);
|
|
226
|
-
expect(knownStatesSpy).toHaveBeenCalledWith(
|
|
227
|
-
expect(optimisticKnownStatesSpy).toHaveBeenCalledTimes(1);
|
|
228
|
-
expect(optimisticKnownStatesSpy).toHaveBeenCalledWith(action);
|
|
215
|
+
expect(knownStatesSpy).toHaveBeenCalledWith("co_z1", state);
|
|
229
216
|
});
|
|
230
217
|
|
|
231
218
|
test("should use separate references for knownStates and optimisticKnownStates for non-storage peers", () => {
|
package/src/tests/coList.test.ts
CHANGED
|
@@ -95,6 +95,76 @@ test("appendItems add an array of items at the end of the list", () => {
|
|
|
95
95
|
expect(content.toJSON()).toEqual(["hello", "world", "hooray", "universe"]);
|
|
96
96
|
});
|
|
97
97
|
|
|
98
|
+
test("appendItems at index", () => {
|
|
99
|
+
const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
|
|
100
|
+
|
|
101
|
+
const coValue = node.createCoValue({
|
|
102
|
+
type: "colist",
|
|
103
|
+
ruleset: { type: "unsafeAllowAll" },
|
|
104
|
+
meta: null,
|
|
105
|
+
...Crypto.createdNowUnique(),
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const content = expectList(coValue.getCurrentContent());
|
|
109
|
+
|
|
110
|
+
content.append("first", 0, "trusting");
|
|
111
|
+
content.append("second", 0, "trusting");
|
|
112
|
+
expect(content.toJSON()).toEqual(["first", "second"]);
|
|
113
|
+
|
|
114
|
+
content.appendItems(["third", "fourth"], 1, "trusting");
|
|
115
|
+
expect(content.toJSON()).toEqual(["first", "second", "third", "fourth"]);
|
|
116
|
+
|
|
117
|
+
content.appendItems(["hello", "world"], 0, "trusting");
|
|
118
|
+
expect(content.toJSON()).toEqual([
|
|
119
|
+
"first",
|
|
120
|
+
"hello",
|
|
121
|
+
"world",
|
|
122
|
+
"second",
|
|
123
|
+
"third",
|
|
124
|
+
"fourth",
|
|
125
|
+
]);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("appendItems at index", () => {
|
|
129
|
+
const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
|
|
130
|
+
|
|
131
|
+
const coValue = node.createCoValue({
|
|
132
|
+
type: "colist",
|
|
133
|
+
ruleset: { type: "unsafeAllowAll" },
|
|
134
|
+
meta: null,
|
|
135
|
+
...Crypto.createdNowUnique(),
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const content = expectList(coValue.getCurrentContent());
|
|
139
|
+
|
|
140
|
+
content.append("first", 0, "trusting");
|
|
141
|
+
expect(content.toJSON()).toEqual(["first"]);
|
|
142
|
+
|
|
143
|
+
content.appendItems(["second"], 0, "trusting");
|
|
144
|
+
expect(content.toJSON()).toEqual(["first", "second"]);
|
|
145
|
+
|
|
146
|
+
content.appendItems(["third"], 1, "trusting");
|
|
147
|
+
expect(content.toJSON()).toEqual(["first", "second", "third"]);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("appendItems with negative index", () => {
|
|
151
|
+
const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
|
|
152
|
+
|
|
153
|
+
const coValue = node.createCoValue({
|
|
154
|
+
type: "colist",
|
|
155
|
+
ruleset: { type: "unsafeAllowAll" },
|
|
156
|
+
meta: null,
|
|
157
|
+
...Crypto.createdNowUnique(),
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const content = expectList(coValue.getCurrentContent());
|
|
161
|
+
|
|
162
|
+
content.append("hello", 0, "trusting");
|
|
163
|
+
expect(content.toJSON()).toEqual(["hello"]);
|
|
164
|
+
content.appendItems(["world", "hooray", "universe"], -1, "trusting");
|
|
165
|
+
expect(content.toJSON()).toEqual(["hello", "world", "hooray", "universe"]);
|
|
166
|
+
});
|
|
167
|
+
|
|
98
168
|
test("Can push into empty list", () => {
|
|
99
169
|
const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
|
|
100
170
|
|
|
@@ -151,3 +221,16 @@ test("Items prepended to start appear with latest first", () => {
|
|
|
151
221
|
|
|
152
222
|
expect(content.toJSON()).toEqual(["third", "second", "first"]);
|
|
153
223
|
});
|
|
224
|
+
|
|
225
|
+
test("should handle large lists", () => {
|
|
226
|
+
const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
|
|
227
|
+
|
|
228
|
+
const group = node.createGroup();
|
|
229
|
+
const coValue = group.createList();
|
|
230
|
+
|
|
231
|
+
for (let i = 0; i < 8_000; i++) {
|
|
232
|
+
coValue.append(`item ${i}`, undefined, "trusting");
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
expect(coValue.toJSON().length).toEqual(8_000);
|
|
236
|
+
});
|
|
@@ -71,25 +71,23 @@ test("Can insert and delete in CoPlainText", () => {
|
|
|
71
71
|
content.insertAfter(0, "hello", "trusting");
|
|
72
72
|
expect(content.toString()).toEqual("hello");
|
|
73
73
|
|
|
74
|
-
content.insertAfter(
|
|
74
|
+
content.insertAfter(4, " world", "trusting");
|
|
75
75
|
expect(content.toString()).toEqual("hello world");
|
|
76
76
|
|
|
77
|
-
content.
|
|
77
|
+
content.insertBefore(0, "Hello, ", "trusting");
|
|
78
78
|
expect(content.toString()).toEqual("Hello, hello world");
|
|
79
79
|
|
|
80
|
-
console.log("first delete");
|
|
81
80
|
content.deleteRange({ from: 6, to: 12 }, "trusting");
|
|
82
81
|
expect(content.toString()).toEqual("Hello, world");
|
|
83
82
|
|
|
84
|
-
content.
|
|
83
|
+
content.insertBefore(2, "π", "trusting");
|
|
85
84
|
expect(content.toString()).toEqual("Heπllo, world");
|
|
86
85
|
|
|
87
|
-
console.log("second delete");
|
|
88
86
|
content.deleteRange({ from: 2, to: 4 }, "trusting");
|
|
89
87
|
expect(content.toString()).toEqual("Hello, world");
|
|
90
88
|
});
|
|
91
89
|
|
|
92
|
-
test("Multiple items
|
|
90
|
+
test("Multiple items inserted appear in correct order", () => {
|
|
93
91
|
const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
|
|
94
92
|
|
|
95
93
|
const coValue = node.createCoValue({
|
|
@@ -101,10 +99,10 @@ test("Multiple items appended after start appear in correct order", () => {
|
|
|
101
99
|
|
|
102
100
|
const content = expectPlainText(coValue.getCurrentContent());
|
|
103
101
|
|
|
104
|
-
// Add multiple items in
|
|
102
|
+
// Add multiple items in sequence
|
|
105
103
|
content.insertAfter(0, "h", "trusting");
|
|
106
|
-
content.insertAfter(
|
|
107
|
-
content.insertAfter(
|
|
104
|
+
content.insertAfter(0, "e", "trusting");
|
|
105
|
+
content.insertAfter(1, "y", "trusting");
|
|
108
106
|
|
|
109
107
|
// They should appear in insertion order (hey), not reversed (yeh)
|
|
110
108
|
expect(content.toString()).toEqual("hey");
|
|
@@ -124,10 +122,82 @@ test("Items inserted at start appear with latest first", () => {
|
|
|
124
122
|
|
|
125
123
|
// Insert multiple items at the start
|
|
126
124
|
content.insertAfter(0, "first", "trusting");
|
|
127
|
-
content.
|
|
128
|
-
content.
|
|
125
|
+
content.insertBefore(0, "second", "trusting");
|
|
126
|
+
content.insertBefore(0, "third", "trusting");
|
|
129
127
|
|
|
130
128
|
// They should appear in reverse chronological order
|
|
131
129
|
// because newer items should appear before older items
|
|
132
130
|
expect(content.toString()).toEqual("thirdsecondfirst");
|
|
133
131
|
});
|
|
132
|
+
|
|
133
|
+
test("Handles different locales correctly", () => {
|
|
134
|
+
const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
|
|
135
|
+
|
|
136
|
+
// Test with explicit locale in meta
|
|
137
|
+
const coValueJa = node.createCoValue({
|
|
138
|
+
type: "coplaintext",
|
|
139
|
+
ruleset: { type: "unsafeAllowAll" },
|
|
140
|
+
meta: { locale: "ja-JP" },
|
|
141
|
+
...Crypto.createdNowUnique(),
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const contentJa = expectPlainText(coValueJa.getCurrentContent());
|
|
145
|
+
contentJa.insertAfter(0, "γγγ«γ‘γ―", "trusting");
|
|
146
|
+
expect(contentJa.toString()).toEqual("γγγ«γ‘γ―");
|
|
147
|
+
|
|
148
|
+
// Test browser locale fallback
|
|
149
|
+
vi.stubGlobal("navigator", { language: "fr-FR" });
|
|
150
|
+
|
|
151
|
+
const coValueBrowser = node.createCoValue({
|
|
152
|
+
type: "coplaintext",
|
|
153
|
+
ruleset: { type: "unsafeAllowAll" },
|
|
154
|
+
meta: null,
|
|
155
|
+
...Crypto.createdNowUnique(),
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const contentBrowser = expectPlainText(coValueBrowser.getCurrentContent());
|
|
159
|
+
contentBrowser.insertAfter(0, "bonjour", "trusting");
|
|
160
|
+
expect(contentBrowser.toString()).toEqual("bonjour");
|
|
161
|
+
|
|
162
|
+
// Test fallback to 'en' when no navigator
|
|
163
|
+
vi.stubGlobal("navigator", undefined);
|
|
164
|
+
|
|
165
|
+
const coValueFallback = node.createCoValue({
|
|
166
|
+
type: "coplaintext",
|
|
167
|
+
ruleset: { type: "unsafeAllowAll" },
|
|
168
|
+
meta: null,
|
|
169
|
+
...Crypto.createdNowUnique(),
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const contentFallback = expectPlainText(coValueFallback.getCurrentContent());
|
|
173
|
+
contentFallback.insertAfter(0, "hello", "trusting");
|
|
174
|
+
expect(contentFallback.toString()).toEqual("hello");
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("insertBefore and insertAfter work as expected", () => {
|
|
178
|
+
const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
|
|
179
|
+
const coValue = node.createCoValue({
|
|
180
|
+
type: "coplaintext",
|
|
181
|
+
ruleset: { type: "unsafeAllowAll" },
|
|
182
|
+
meta: null,
|
|
183
|
+
...Crypto.createdNowUnique(),
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const content = expectPlainText(coValue.getCurrentContent());
|
|
187
|
+
|
|
188
|
+
// Insert 'h' at start
|
|
189
|
+
content.insertBefore(0, "h", "trusting"); // "h"
|
|
190
|
+
expect(content.toString()).toEqual("h");
|
|
191
|
+
|
|
192
|
+
// Insert 'e' after 'h'
|
|
193
|
+
content.insertAfter(0, "e", "trusting"); // "he"
|
|
194
|
+
expect(content.toString()).toEqual("he");
|
|
195
|
+
|
|
196
|
+
// Insert 'y' after 'e'
|
|
197
|
+
content.insertAfter(1, "y", "trusting"); // "hey"
|
|
198
|
+
expect(content.toString()).toEqual("hey");
|
|
199
|
+
|
|
200
|
+
// Insert '!' at start
|
|
201
|
+
content.insertBefore(0, "!", "trusting"); // "!hey"
|
|
202
|
+
expect(content.toString()).toEqual("!hey");
|
|
203
|
+
});
|