cojson 0.8.19 → 0.8.23
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/CHANGELOG.md +13 -0
- package/dist/native/CoValuesStore.js +31 -0
- package/dist/native/CoValuesStore.js.map +1 -0
- package/dist/native/PeerState.js +7 -0
- package/dist/native/PeerState.js.map +1 -1
- package/dist/native/SyncStateSubscriptionManager.js +2 -2
- package/dist/native/SyncStateSubscriptionManager.js.map +1 -1
- package/dist/native/coValueState.js +175 -27
- package/dist/native/coValueState.js.map +1 -1
- package/dist/native/localNode.js +20 -41
- package/dist/native/localNode.js.map +1 -1
- package/dist/native/sync.js +49 -93
- package/dist/native/sync.js.map +1 -1
- package/dist/web/CoValuesStore.js +31 -0
- package/dist/web/CoValuesStore.js.map +1 -0
- package/dist/web/PeerState.js +7 -0
- package/dist/web/PeerState.js.map +1 -1
- package/dist/web/SyncStateSubscriptionManager.js +2 -2
- package/dist/web/SyncStateSubscriptionManager.js.map +1 -1
- package/dist/web/coValueState.js +175 -27
- package/dist/web/coValueState.js.map +1 -1
- package/dist/web/localNode.js +20 -41
- package/dist/web/localNode.js.map +1 -1
- package/dist/web/sync.js +49 -93
- package/dist/web/sync.js.map +1 -1
- package/package.json +1 -1
- package/src/CoValuesStore.ts +38 -0
- package/src/PeerKnownStates.ts +1 -1
- package/src/PeerState.ts +10 -1
- package/src/SyncStateSubscriptionManager.ts +2 -2
- package/src/coValueState.ts +253 -42
- package/src/localNode.ts +28 -56
- package/src/sync.ts +64 -116
- package/src/tests/PeerState.test.ts +44 -1
- package/src/tests/coValueState.test.ts +362 -0
- package/src/tests/group.test.ts +1 -1
- package/src/tests/sync.test.ts +140 -19
package/src/sync.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { PeerState } from "./PeerState.js";
|
|
|
2
2
|
import { SyncStateSubscriptionManager } from "./SyncStateSubscriptionManager.js";
|
|
3
3
|
import { CoValueHeader, Transaction } from "./coValueCore.js";
|
|
4
4
|
import { CoValueCore } from "./coValueCore.js";
|
|
5
|
-
import { CoValueState } from "./coValueState.js";
|
|
6
5
|
import { Signature } from "./crypto/crypto.js";
|
|
7
6
|
import { RawCoID, SessionID } from "./ids.js";
|
|
8
7
|
import { LocalNode } from "./localNode.js";
|
|
@@ -79,6 +78,7 @@ export interface Peer {
|
|
|
79
78
|
role: "peer" | "server" | "client" | "storage";
|
|
80
79
|
priority?: number;
|
|
81
80
|
crashOnClose: boolean;
|
|
81
|
+
deletePeerStateOnClose?: boolean;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
export function combinedKnownStates(
|
|
@@ -135,31 +135,10 @@ export class SyncManager {
|
|
|
135
135
|
return Object.values(this.peers);
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
(peer) => peer.
|
|
138
|
+
getServerAndStoragePeers(excludePeerId?: PeerID): PeerState[] {
|
|
139
|
+
return this.peersInPriorityOrder().filter(
|
|
140
|
+
(peer) => peer.isServerOrStoragePeer() && peer.id !== excludePeerId,
|
|
141
141
|
);
|
|
142
|
-
|
|
143
|
-
const coValueEntry = this.local.coValues[id];
|
|
144
|
-
|
|
145
|
-
for (const peer of eligiblePeers) {
|
|
146
|
-
if (peer.erroredCoValues.has(id)) {
|
|
147
|
-
console.error(
|
|
148
|
-
`Skipping load on errored coValue ${id} from peer ${peer.id}`,
|
|
149
|
-
);
|
|
150
|
-
continue;
|
|
151
|
-
}
|
|
152
|
-
await peer.pushOutgoingMessage({
|
|
153
|
-
action: "load",
|
|
154
|
-
id: id,
|
|
155
|
-
header: false,
|
|
156
|
-
sessions: {},
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
if (coValueEntry?.state.type === "unknown") {
|
|
160
|
-
await coValueEntry.state.waitForPeer(peer.id);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
142
|
}
|
|
164
143
|
|
|
165
144
|
async handleSyncMessage(msg: SyncMessage, peer: PeerState) {
|
|
@@ -192,19 +171,10 @@ export class SyncManager {
|
|
|
192
171
|
}
|
|
193
172
|
|
|
194
173
|
async subscribeToIncludingDependencies(id: RawCoID, peer: PeerState) {
|
|
195
|
-
const entry = this.local.
|
|
174
|
+
const entry = this.local.coValuesStore.get(id);
|
|
196
175
|
|
|
197
|
-
if (
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (entry.state.type === "unknown") {
|
|
202
|
-
this.trySendToPeer(peer, {
|
|
203
|
-
action: "load",
|
|
204
|
-
id,
|
|
205
|
-
header: false,
|
|
206
|
-
sessions: {},
|
|
207
|
-
}).catch((e: unknown) => {
|
|
176
|
+
if (entry.state.type !== "available") {
|
|
177
|
+
entry.loadFromPeers([peer]).catch((e: unknown) => {
|
|
208
178
|
console.error("Error sending load", e);
|
|
209
179
|
});
|
|
210
180
|
return;
|
|
@@ -334,7 +304,7 @@ export class SyncManager {
|
|
|
334
304
|
|
|
335
305
|
if (peerState.isServerOrStoragePeer()) {
|
|
336
306
|
const initialSync = async () => {
|
|
337
|
-
for (const id of
|
|
307
|
+
for (const id of this.local.coValuesStore.getKeys()) {
|
|
338
308
|
// console.log("subscribing to after peer added", id, peer.id)
|
|
339
309
|
await this.subscribeToIncludingDependencies(id, peerState);
|
|
340
310
|
|
|
@@ -392,6 +362,10 @@ export class SyncManager {
|
|
|
392
362
|
const state = this.peers[peer.id];
|
|
393
363
|
state?.gracefulShutdown();
|
|
394
364
|
unsubscribeFromKnownStatesUpdates();
|
|
365
|
+
|
|
366
|
+
if (peer.deletePeerStateOnClose) {
|
|
367
|
+
delete this.peers[peer.id];
|
|
368
|
+
}
|
|
395
369
|
});
|
|
396
370
|
}
|
|
397
371
|
|
|
@@ -400,61 +374,39 @@ export class SyncManager {
|
|
|
400
374
|
}
|
|
401
375
|
|
|
402
376
|
async handleLoad(msg: LoadMessage, peer: PeerState) {
|
|
403
|
-
peer.
|
|
377
|
+
peer.dispatchToKnownStates({
|
|
404
378
|
type: "SET",
|
|
405
379
|
id: msg.id,
|
|
406
380
|
value: knownStateIn(msg),
|
|
407
381
|
});
|
|
408
|
-
|
|
382
|
+
const entry = this.local.coValuesStore.get(msg.id);
|
|
409
383
|
|
|
410
|
-
if (
|
|
411
|
-
|
|
384
|
+
if (entry.state.type === "unknown" || entry.state.type === "unavailable") {
|
|
385
|
+
const eligiblePeers = this.getServerAndStoragePeers(peer.id);
|
|
412
386
|
|
|
413
|
-
// special case: we should be able to solve this much more neatly
|
|
414
|
-
// with an explicit state machine in the future
|
|
415
|
-
const eligiblePeers = this.peersInPriorityOrder().filter(
|
|
416
|
-
(other) => other.id !== peer.id && other.isServerOrStoragePeer(),
|
|
417
|
-
);
|
|
418
387
|
if (eligiblePeers.length === 0) {
|
|
388
|
+
// If the load request contains a header or any session data
|
|
389
|
+
// and we don't have any eligible peers to load the coValue from
|
|
390
|
+
// we try to load it from the sender because it is the only place
|
|
391
|
+
// where we can get informations about the coValue
|
|
419
392
|
if (msg.header || Object.keys(msg.sessions).length > 0) {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
);
|
|
423
|
-
this.trySendToPeer(peer, {
|
|
424
|
-
action: "known",
|
|
425
|
-
id: msg.id,
|
|
426
|
-
header: false,
|
|
427
|
-
sessions: {},
|
|
428
|
-
}).catch((e) => {
|
|
429
|
-
console.error("Error sending known state", e);
|
|
393
|
+
entry.loadFromPeers([peer]).catch((e) => {
|
|
394
|
+
console.error("Error loading coValue in handleLoad", e);
|
|
430
395
|
});
|
|
431
396
|
}
|
|
432
397
|
return;
|
|
433
398
|
} else {
|
|
434
|
-
this.local
|
|
435
|
-
.
|
|
436
|
-
|
|
437
|
-
dontWaitFor: peer.id,
|
|
438
|
-
})
|
|
439
|
-
.catch((e) => {
|
|
440
|
-
console.error("Error loading coValue in handleLoad", e);
|
|
441
|
-
});
|
|
399
|
+
this.local.loadCoValueCore(msg.id, peer.id).catch((e) => {
|
|
400
|
+
console.error("Error loading coValue in handleLoad", e);
|
|
401
|
+
});
|
|
442
402
|
}
|
|
443
|
-
|
|
444
|
-
entry = this.local.coValues[msg.id]!;
|
|
445
403
|
}
|
|
446
404
|
|
|
447
|
-
if (entry.state.type === "
|
|
448
|
-
|
|
449
|
-
// "Waiting for loaded",
|
|
450
|
-
// msg.id,
|
|
451
|
-
// "after message from",
|
|
452
|
-
// peer.id,
|
|
453
|
-
// );
|
|
454
|
-
const loaded = await entry.state.ready;
|
|
405
|
+
if (entry.state.type === "loading") {
|
|
406
|
+
const value = await entry.getCoValue();
|
|
455
407
|
|
|
456
|
-
if (
|
|
457
|
-
peer.
|
|
408
|
+
if (value === "unavailable") {
|
|
409
|
+
peer.dispatchToKnownStates({
|
|
458
410
|
type: "SET",
|
|
459
411
|
id: msg.id,
|
|
460
412
|
value: knownStateIn(msg),
|
|
@@ -474,37 +426,37 @@ export class SyncManager {
|
|
|
474
426
|
}
|
|
475
427
|
}
|
|
476
428
|
|
|
477
|
-
|
|
478
|
-
|
|
429
|
+
if (entry.state.type === "available") {
|
|
430
|
+
await this.tellUntoldKnownStateIncludingDependencies(msg.id, peer);
|
|
431
|
+
await this.sendNewContentIncludingDependencies(msg.id, peer);
|
|
432
|
+
}
|
|
479
433
|
}
|
|
480
434
|
|
|
481
435
|
async handleKnownState(msg: KnownStateMessage, peer: PeerState) {
|
|
482
|
-
|
|
436
|
+
const entry = this.local.coValuesStore.get(msg.id);
|
|
483
437
|
|
|
484
|
-
peer.
|
|
438
|
+
peer.dispatchToKnownStates({
|
|
485
439
|
type: "COMBINE_WITH",
|
|
486
440
|
id: msg.id,
|
|
487
441
|
value: knownStateIn(msg),
|
|
488
442
|
});
|
|
489
443
|
|
|
490
|
-
|
|
491
|
-
type: "COMBINE_WITH",
|
|
492
|
-
id: msg.id,
|
|
493
|
-
value: knownStateIn(msg),
|
|
494
|
-
});
|
|
495
|
-
|
|
496
|
-
if (!entry) {
|
|
444
|
+
if (entry.state.type === "unknown" || entry.state.type === "unavailable") {
|
|
497
445
|
if (msg.asDependencyOf) {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
446
|
+
const dependencyEntry = this.local.coValuesStore.get(
|
|
447
|
+
msg.asDependencyOf,
|
|
448
|
+
);
|
|
449
|
+
|
|
450
|
+
if (
|
|
451
|
+
dependencyEntry.state.type === "available" ||
|
|
452
|
+
dependencyEntry.state.type === "loading"
|
|
453
|
+
) {
|
|
454
|
+
this.local.loadCoValueCore(msg.id, peer.id).catch((e) => {
|
|
455
|
+
console.error(
|
|
456
|
+
`Error loading coValue ${msg.id} to create loading state, as dependency of ${msg.asDependencyOf}`,
|
|
457
|
+
e,
|
|
458
|
+
);
|
|
459
|
+
});
|
|
508
460
|
} else {
|
|
509
461
|
throw new Error(
|
|
510
462
|
"Expected coValue dependency entry to be created, missing subscribe?",
|
|
@@ -517,12 +469,14 @@ export class SyncManager {
|
|
|
517
469
|
}
|
|
518
470
|
}
|
|
519
471
|
|
|
520
|
-
if
|
|
472
|
+
// The header is a boolean value that tells us if the other peer do have information about the header.
|
|
473
|
+
// If it's false in this point it means that the coValue is unavailable on the other peer.
|
|
474
|
+
if (entry.state.type !== "available") {
|
|
521
475
|
const availableOnPeer = peer.optimisticKnownStates.get(msg.id)?.header;
|
|
522
476
|
|
|
523
477
|
if (!availableOnPeer) {
|
|
524
478
|
entry.dispatch({
|
|
525
|
-
type: "not-found",
|
|
479
|
+
type: "not-found-in-peer",
|
|
526
480
|
peerId: peer.id,
|
|
527
481
|
});
|
|
528
482
|
}
|
|
@@ -530,29 +484,24 @@ export class SyncManager {
|
|
|
530
484
|
return;
|
|
531
485
|
}
|
|
532
486
|
|
|
533
|
-
|
|
534
|
-
|
|
487
|
+
if (entry.state.type === "available") {
|
|
488
|
+
await this.tellUntoldKnownStateIncludingDependencies(msg.id, peer);
|
|
489
|
+
await this.sendNewContentIncludingDependencies(msg.id, peer);
|
|
490
|
+
}
|
|
535
491
|
}
|
|
536
492
|
|
|
537
493
|
async handleNewContent(msg: NewContentMessage, peer: PeerState) {
|
|
538
|
-
const entry = this.local.
|
|
539
|
-
|
|
540
|
-
if (!entry) {
|
|
541
|
-
console.error(
|
|
542
|
-
`Expected coValue entry for ${msg.id} to be created on new content, missing subscribe?`,
|
|
543
|
-
);
|
|
544
|
-
return;
|
|
545
|
-
}
|
|
494
|
+
const entry = this.local.coValuesStore.get(msg.id);
|
|
546
495
|
|
|
547
496
|
let coValue: CoValueCore;
|
|
548
497
|
|
|
549
|
-
if (entry.state.type
|
|
498
|
+
if (entry.state.type !== "available") {
|
|
550
499
|
if (!msg.header) {
|
|
551
500
|
console.error("Expected header to be sent in first message");
|
|
552
501
|
return;
|
|
553
502
|
}
|
|
554
503
|
|
|
555
|
-
peer.
|
|
504
|
+
peer.dispatchToKnownStates({
|
|
556
505
|
type: "UPDATE_HEADER",
|
|
557
506
|
id: msg.id,
|
|
558
507
|
header: true,
|
|
@@ -561,9 +510,8 @@ export class SyncManager {
|
|
|
561
510
|
coValue = new CoValueCore(msg.header, this.local);
|
|
562
511
|
|
|
563
512
|
entry.dispatch({
|
|
564
|
-
type: "
|
|
513
|
+
type: "available",
|
|
565
514
|
coValue,
|
|
566
|
-
peerId: peer.id,
|
|
567
515
|
});
|
|
568
516
|
} else {
|
|
569
517
|
coValue = entry.state.coValue;
|
|
@@ -645,7 +593,7 @@ export class SyncManager {
|
|
|
645
593
|
continue;
|
|
646
594
|
}
|
|
647
595
|
|
|
648
|
-
peer.
|
|
596
|
+
peer.dispatchToKnownStates({
|
|
649
597
|
type: "UPDATE_SESSION_COUNTER",
|
|
650
598
|
id: msg.id,
|
|
651
599
|
sessionId: sessionID,
|
|
@@ -688,7 +636,7 @@ export class SyncManager {
|
|
|
688
636
|
}
|
|
689
637
|
|
|
690
638
|
async handleCorrection(msg: KnownStateMessage, peer: PeerState) {
|
|
691
|
-
peer.
|
|
639
|
+
peer.dispatchToKnownStates({
|
|
692
640
|
type: "SET",
|
|
693
641
|
id: msg.id,
|
|
694
642
|
value: knownStateIn(msg),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test, vi } from "vitest";
|
|
2
|
+
import { PeerKnownStateActions } from "../PeerKnownStates.js";
|
|
2
3
|
import { PeerState } from "../PeerState.js";
|
|
3
4
|
import { CO_VALUE_PRIORITY } from "../priority.js";
|
|
4
5
|
import { Peer, SyncMessage } from "../sync.js";
|
|
@@ -15,7 +16,7 @@ function setup() {
|
|
|
15
16
|
close: vi.fn(),
|
|
16
17
|
},
|
|
17
18
|
};
|
|
18
|
-
const peerState = new PeerState(mockPeer);
|
|
19
|
+
const peerState = new PeerState(mockPeer, undefined);
|
|
19
20
|
return { mockPeer, peerState };
|
|
20
21
|
}
|
|
21
22
|
|
|
@@ -115,4 +116,46 @@ describe("PeerState", () => {
|
|
|
115
116
|
contentMessageMid,
|
|
116
117
|
);
|
|
117
118
|
});
|
|
119
|
+
|
|
120
|
+
test("should clone the knownStates into optimisticKnownStates and knownStates when passed as argument", () => {
|
|
121
|
+
const { peerState, mockPeer } = setup();
|
|
122
|
+
const action: PeerKnownStateActions = {
|
|
123
|
+
type: "SET",
|
|
124
|
+
id: "co_z1",
|
|
125
|
+
value: {
|
|
126
|
+
id: "co_z1",
|
|
127
|
+
header: false,
|
|
128
|
+
sessions: {},
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
peerState.dispatchToKnownStates(action);
|
|
132
|
+
|
|
133
|
+
const newPeerState = new PeerState(mockPeer, peerState.knownStates);
|
|
134
|
+
|
|
135
|
+
expect(newPeerState.knownStates).toEqual(peerState.knownStates);
|
|
136
|
+
expect(newPeerState.optimisticKnownStates).toEqual(peerState.knownStates);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("should dispatch to both states", () => {
|
|
140
|
+
const { peerState } = setup();
|
|
141
|
+
const knownStatesSpy = vi.spyOn(peerState.knownStates, "dispatch");
|
|
142
|
+
const optimisticKnownStatesSpy = vi.spyOn(
|
|
143
|
+
peerState.optimisticKnownStates,
|
|
144
|
+
"dispatch",
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
const action: PeerKnownStateActions = {
|
|
148
|
+
type: "SET",
|
|
149
|
+
id: "co_z1",
|
|
150
|
+
value: {
|
|
151
|
+
id: "co_z1",
|
|
152
|
+
header: false,
|
|
153
|
+
sessions: {},
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
peerState.dispatchToKnownStates(action);
|
|
157
|
+
|
|
158
|
+
expect(knownStatesSpy).toHaveBeenCalledWith(action);
|
|
159
|
+
expect(optimisticKnownStatesSpy).toHaveBeenCalledWith(action);
|
|
160
|
+
});
|
|
118
161
|
});
|