cojson 0.18.33 → 0.18.34
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 +9 -0
- package/dist/SyncStateManager.d.ts.map +1 -1
- package/dist/SyncStateManager.js +2 -2
- package/dist/SyncStateManager.js.map +1 -1
- package/dist/coValueCore/SessionMap.d.ts.map +1 -1
- package/dist/coValueCore/SessionMap.js +9 -15
- package/dist/coValueCore/SessionMap.js.map +1 -1
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +3 -4
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/coValueCore/verifiedState.d.ts +3 -1
- package/dist/coValueCore/verifiedState.d.ts.map +1 -1
- package/dist/coValueCore/verifiedState.js +10 -4
- package/dist/coValueCore/verifiedState.js.map +1 -1
- package/dist/knownState.d.ts +9 -1
- package/dist/knownState.d.ts.map +1 -1
- package/dist/knownState.js +29 -3
- package/dist/knownState.js.map +1 -1
- package/dist/localNode.d.ts.map +1 -1
- package/dist/localNode.js +2 -1
- package/dist/localNode.js.map +1 -1
- package/dist/queue/LocalTransactionsSyncQueue.d.ts +10 -9
- package/dist/queue/LocalTransactionsSyncQueue.d.ts.map +1 -1
- package/dist/queue/LocalTransactionsSyncQueue.js +53 -47
- package/dist/queue/LocalTransactionsSyncQueue.js.map +1 -1
- package/dist/storage/knownState.js +2 -2
- package/dist/storage/knownState.js.map +1 -1
- package/dist/sync.d.ts +1 -2
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +0 -1
- package/dist/sync.js.map +1 -1
- package/dist/tests/coPlainText.test.js +13 -14
- package/dist/tests/coPlainText.test.js.map +1 -1
- package/dist/tests/coValueCore.isCompletelyDownloaded.test.js +3 -2
- package/dist/tests/coValueCore.isCompletelyDownloaded.test.js.map +1 -1
- package/dist/tests/coValueCore.isStreaming.test.js +54 -3
- package/dist/tests/coValueCore.isStreaming.test.js.map +1 -1
- package/dist/tests/group.childKeyRotation.test.js +9 -9
- package/dist/tests/group.childKeyRotation.test.js.map +1 -1
- package/dist/tests/knownState.test.js +82 -10
- package/dist/tests/knownState.test.js.map +1 -1
- package/dist/tests/sync.load.test.js +29 -29
- package/dist/tests/sync.mesh.test.js +38 -31
- package/dist/tests/sync.mesh.test.js.map +1 -1
- package/dist/tests/sync.storage.test.js +24 -23
- package/dist/tests/sync.storage.test.js.map +1 -1
- package/dist/tests/sync.storageAsync.test.js +24 -23
- package/dist/tests/sync.storageAsync.test.js.map +1 -1
- package/dist/tests/sync.upload.test.js +58 -58
- package/dist/tests/testUtils.d.ts +11 -9
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +26 -16
- package/dist/tests/testUtils.js.map +1 -1
- package/package.json +3 -3
- package/src/SyncStateManager.ts +8 -2
- package/src/coValueCore/SessionMap.ts +18 -15
- package/src/coValueCore/coValueCore.ts +4 -11
- package/src/coValueCore/verifiedState.ts +20 -5
- package/src/knownState.ts +48 -4
- package/src/localNode.ts +6 -3
- package/src/queue/LocalTransactionsSyncQueue.ts +77 -93
- package/src/storage/knownState.ts +2 -2
- package/src/sync.ts +0 -1
- package/src/tests/coPlainText.test.ts +13 -14
- package/src/tests/coValueCore.isCompletelyDownloaded.test.ts +3 -2
- package/src/tests/coValueCore.isStreaming.test.ts +84 -2
- package/src/tests/group.childKeyRotation.test.ts +9 -9
- package/src/tests/knownState.test.ts +106 -9
- package/src/tests/sync.load.test.ts +29 -29
- package/src/tests/sync.mesh.test.ts +38 -31
- package/src/tests/sync.storage.test.ts +24 -23
- package/src/tests/sync.storageAsync.test.ts +24 -23
- package/src/tests/sync.upload.test.ts +58 -58
- package/src/tests/testUtils.ts +30 -18
package/src/knownState.ts
CHANGED
|
@@ -106,18 +106,62 @@ export function cloneKnownState(knownState: CoValueKnownState) {
|
|
|
106
106
|
};
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Checks if all the local sessions have the same counters as in remote.
|
|
111
|
+
*/
|
|
112
|
+
export function areCurrentSessionsInSyncWith(
|
|
113
|
+
current: Record<string, number>,
|
|
114
|
+
target: Record<string, number>,
|
|
115
|
+
) {
|
|
116
|
+
for (const [sessionId, currentCount] of Object.entries(current) as [
|
|
117
|
+
SessionID,
|
|
118
|
+
number,
|
|
119
|
+
][]) {
|
|
120
|
+
const targetCount = target[sessionId] ?? 0;
|
|
121
|
+
if (currentCount !== targetCount) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
|
|
109
129
|
/**
|
|
110
130
|
* Checks if all the local sessions have the same counters as in remote.
|
|
111
131
|
*/
|
|
112
132
|
export function isKnownStateSubsetOf(
|
|
113
|
-
|
|
114
|
-
|
|
133
|
+
current: Record<string, number>,
|
|
134
|
+
target: Record<string, number>,
|
|
115
135
|
) {
|
|
116
|
-
for (const sessionId of Object.
|
|
117
|
-
|
|
136
|
+
for (const [sessionId, currentCount] of Object.entries(current) as [
|
|
137
|
+
SessionID,
|
|
138
|
+
number,
|
|
139
|
+
][]) {
|
|
140
|
+
const targetCount = target[sessionId] ?? 0;
|
|
141
|
+
if (currentCount > targetCount) {
|
|
118
142
|
return false;
|
|
119
143
|
}
|
|
120
144
|
}
|
|
121
145
|
|
|
122
146
|
return true;
|
|
123
147
|
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Returns the record with the sessions that need to be sent to the target
|
|
151
|
+
*/
|
|
152
|
+
export function getKnownStateToSend(
|
|
153
|
+
current: Record<string, number>,
|
|
154
|
+
target: Record<string, number>,
|
|
155
|
+
) {
|
|
156
|
+
const toSend: Record<string, number> = {};
|
|
157
|
+
for (const [sessionId, currentCount] of Object.entries(current) as [
|
|
158
|
+
SessionID,
|
|
159
|
+
number,
|
|
160
|
+
][]) {
|
|
161
|
+
const targetCount = target[sessionId] ?? 0;
|
|
162
|
+
if (currentCount > targetCount) {
|
|
163
|
+
toSend[sessionId] = currentCount;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return toSend;
|
|
167
|
+
}
|
package/src/localNode.ts
CHANGED
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
import {
|
|
11
11
|
type CoValueHeader,
|
|
12
12
|
type CoValueUniqueness,
|
|
13
|
-
VerifiedState,
|
|
14
13
|
} from "./coValueCore/verifiedState.js";
|
|
15
14
|
import {
|
|
16
15
|
AccountMeta,
|
|
@@ -31,7 +30,7 @@ import {
|
|
|
31
30
|
type RawGroup,
|
|
32
31
|
secretSeedFromInviteSecret,
|
|
33
32
|
} from "./coValues/group.js";
|
|
34
|
-
import { CO_VALUE_LOADING_CONFIG
|
|
33
|
+
import { CO_VALUE_LOADING_CONFIG } from "./config.js";
|
|
35
34
|
import { AgentSecret, CryptoProvider } from "./crypto/crypto.js";
|
|
36
35
|
import { AgentID, RawCoID, SessionID, isAgentID } from "./ids.js";
|
|
37
36
|
import { logger } from "./logger.js";
|
|
@@ -41,6 +40,7 @@ import { accountOrAgentIDfromSessionID } from "./typeUtils/accountOrAgentIDfromS
|
|
|
41
40
|
import { expectGroup } from "./typeUtils/expectGroup.js";
|
|
42
41
|
import { canBeBranched } from "./coValueCore/branching.js";
|
|
43
42
|
import { connectedPeers } from "./streamUtils.js";
|
|
43
|
+
import { emptyKnownState } from "./knownState.js";
|
|
44
44
|
|
|
45
45
|
/** A `LocalNode` represents a local view of a set of loaded `CoValue`s, from the perspective of a particular account (or primitive cryptographic agent).
|
|
46
46
|
|
|
@@ -380,7 +380,10 @@ export class LocalNode {
|
|
|
380
380
|
}
|
|
381
381
|
|
|
382
382
|
this.garbageCollector?.trackCoValueAccess(coValue);
|
|
383
|
-
this.syncManager.
|
|
383
|
+
this.syncManager.syncLocalTransaction(
|
|
384
|
+
coValue.verified,
|
|
385
|
+
emptyKnownState(id),
|
|
386
|
+
);
|
|
384
387
|
|
|
385
388
|
return coValue;
|
|
386
389
|
}
|
|
@@ -1,17 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from "../coValueContentMessage.js";
|
|
6
|
-
import { Transaction, VerifiedState } from "../coValueCore/verifiedState.js";
|
|
7
|
-
import { Signature } from "../crypto/crypto.js";
|
|
8
|
-
import { RawCoID, SessionID } from "../ids.js";
|
|
9
|
-
import {
|
|
10
|
-
combineKnownStateSessions,
|
|
11
|
-
KnownStateSessions,
|
|
12
|
-
} from "../knownState.js";
|
|
1
|
+
import { knownStateFromContent } from "../coValueContentMessage.js";
|
|
2
|
+
import { VerifiedState } from "../coValueCore/verifiedState.js";
|
|
3
|
+
import { RawCoID } from "../ids.js";
|
|
4
|
+
import { combineKnownStateSessions, CoValueKnownState } from "../knownState.js";
|
|
13
5
|
import { NewContentMessage } from "../sync.js";
|
|
14
|
-
import { LinkedList } from "./LinkedList.js";
|
|
15
6
|
|
|
16
7
|
/**
|
|
17
8
|
* This queue is used to batch the sync of local transactions while preserving the order of updates between CoValues.
|
|
@@ -23,65 +14,97 @@ import { LinkedList } from "./LinkedList.js";
|
|
|
23
14
|
* 2. If we do multiple updates on the same CoMap, the updates will be batched because it's safe to do so.
|
|
24
15
|
*/
|
|
25
16
|
export class LocalTransactionsSyncQueue {
|
|
26
|
-
private
|
|
17
|
+
private batch: NewContentMessage[] = [];
|
|
18
|
+
private firstChunks = new Map<RawCoID, NewContentMessage>();
|
|
19
|
+
private lastUpdatedValue: VerifiedState | undefined;
|
|
20
|
+
private lastUpdatedValueKnownState: CoValueKnownState | undefined;
|
|
27
21
|
|
|
28
22
|
constructor(private readonly sync: (content: NewContentMessage) => void) {}
|
|
29
23
|
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
syncTransaction = (
|
|
25
|
+
coValue: VerifiedState,
|
|
26
|
+
knownStateBefore: CoValueKnownState,
|
|
27
|
+
) => {
|
|
28
|
+
const lastUpdatedValue = this.lastUpdatedValue;
|
|
29
|
+
const lastUpdatedValueKnownState = this.lastUpdatedValueKnownState;
|
|
32
30
|
|
|
33
|
-
if (
|
|
34
|
-
|
|
31
|
+
if (lastUpdatedValue && lastUpdatedValueKnownState) {
|
|
32
|
+
if (lastUpdatedValue.id === coValue.id) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
this.addContentToBatch(lastUpdatedValue, lastUpdatedValueKnownState);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
this.lastUpdatedValue = coValue;
|
|
40
|
+
this.lastUpdatedValueKnownState = knownStateBefore;
|
|
41
|
+
|
|
42
|
+
for (const trackingSet of this.dirtyCoValuesTrackingSets) {
|
|
43
|
+
trackingSet.add(coValue.id);
|
|
35
44
|
}
|
|
36
45
|
|
|
37
|
-
this.
|
|
46
|
+
this.scheduleNextBatch();
|
|
38
47
|
};
|
|
39
48
|
|
|
40
|
-
|
|
49
|
+
private addContentToBatch(
|
|
41
50
|
coValue: VerifiedState,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const lastPendingSync = this.queue.tail?.value;
|
|
48
|
-
const lastSignatureIdx = coValue.getLastSignatureCheckpoint(sessionID);
|
|
49
|
-
const isSignatureCheckpoint =
|
|
50
|
-
lastSignatureIdx > -1 && lastSignatureIdx === txIdx;
|
|
51
|
-
|
|
52
|
-
if (lastPendingSync?.id === coValue.id && !isSignatureCheckpoint) {
|
|
53
|
-
addTransactionToContentMessage(
|
|
54
|
-
lastPendingSync,
|
|
55
|
-
transaction,
|
|
56
|
-
sessionID,
|
|
57
|
-
signature,
|
|
58
|
-
txIdx,
|
|
59
|
-
);
|
|
51
|
+
knownStateBefore: CoValueKnownState,
|
|
52
|
+
) {
|
|
53
|
+
const content = coValue.newContentSince(knownStateBefore, {
|
|
54
|
+
skipExpectContentUntil: true, // we need to calculate the streaming header considering the current batch
|
|
55
|
+
});
|
|
60
56
|
|
|
57
|
+
if (!content) {
|
|
61
58
|
return;
|
|
62
59
|
}
|
|
63
60
|
|
|
64
|
-
|
|
61
|
+
let firstChunk = this.firstChunks.get(coValue.id);
|
|
65
62
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
transaction,
|
|
69
|
-
sessionID,
|
|
70
|
-
signature,
|
|
71
|
-
txIdx,
|
|
72
|
-
);
|
|
63
|
+
for (const piece of content) {
|
|
64
|
+
this.batch.push(piece);
|
|
73
65
|
|
|
74
|
-
|
|
75
|
-
|
|
66
|
+
// Check if the local content updates are in streaming, if so we need to add the info to the first chunk
|
|
67
|
+
if (firstChunk) {
|
|
68
|
+
if (!firstChunk.expectContentUntil) {
|
|
69
|
+
firstChunk.expectContentUntil =
|
|
70
|
+
knownStateFromContent(firstChunk).sessions;
|
|
71
|
+
}
|
|
72
|
+
combineKnownStateSessions(
|
|
73
|
+
firstChunk.expectContentUntil,
|
|
74
|
+
knownStateFromContent(piece).sessions,
|
|
75
|
+
);
|
|
76
|
+
} else {
|
|
77
|
+
firstChunk = piece;
|
|
78
|
+
this.firstChunks.set(coValue.id, firstChunk);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
76
82
|
|
|
77
|
-
|
|
78
|
-
|
|
83
|
+
private nextBatchScheduled = false;
|
|
84
|
+
scheduleNextBatch() {
|
|
85
|
+
if (this.nextBatchScheduled) return;
|
|
79
86
|
|
|
80
|
-
this.
|
|
87
|
+
this.nextBatchScheduled = true;
|
|
81
88
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
89
|
+
queueMicrotask(() => {
|
|
90
|
+
if (this.lastUpdatedValue && this.lastUpdatedValueKnownState) {
|
|
91
|
+
this.addContentToBatch(
|
|
92
|
+
this.lastUpdatedValue,
|
|
93
|
+
this.lastUpdatedValueKnownState,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
const batch = this.batch;
|
|
97
|
+
|
|
98
|
+
this.lastUpdatedValue = undefined;
|
|
99
|
+
this.lastUpdatedValueKnownState = undefined;
|
|
100
|
+
this.firstChunks = new Map();
|
|
101
|
+
this.batch = [];
|
|
102
|
+
this.nextBatchScheduled = false;
|
|
103
|
+
|
|
104
|
+
for (const content of batch) {
|
|
105
|
+
this.sync(content);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
85
108
|
}
|
|
86
109
|
|
|
87
110
|
private dirtyCoValuesTrackingSets: Set<Set<RawCoID>> = new Set();
|
|
@@ -110,43 +133,4 @@ export class LocalTransactionsSyncQueue {
|
|
|
110
133
|
},
|
|
111
134
|
};
|
|
112
135
|
};
|
|
113
|
-
|
|
114
|
-
private processingSyncs = false;
|
|
115
|
-
processPendingSyncs() {
|
|
116
|
-
if (this.processingSyncs) return;
|
|
117
|
-
|
|
118
|
-
this.processingSyncs = true;
|
|
119
|
-
|
|
120
|
-
queueMicrotask(() => {
|
|
121
|
-
const firstContentPieceMap = new Map<RawCoID, NewContentMessage>();
|
|
122
|
-
|
|
123
|
-
while (this.queue.head) {
|
|
124
|
-
const content = this.queue.head.value;
|
|
125
|
-
|
|
126
|
-
const firstContentPiece = firstContentPieceMap.get(content.id);
|
|
127
|
-
|
|
128
|
-
if (!firstContentPiece) {
|
|
129
|
-
firstContentPieceMap.set(content.id, content);
|
|
130
|
-
} else {
|
|
131
|
-
// There is already a content piece for this coValue, so this means that we need to flag
|
|
132
|
-
// that this content is going to be streamed
|
|
133
|
-
if (!firstContentPiece.expectContentUntil) {
|
|
134
|
-
firstContentPiece.expectContentUntil =
|
|
135
|
-
knownStateFromContent(firstContentPiece).sessions;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
combineKnownStateSessions(
|
|
139
|
-
firstContentPiece.expectContentUntil,
|
|
140
|
-
knownStateFromContent(content).sessions,
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
this.sync(content);
|
|
145
|
-
|
|
146
|
-
this.queue.shift();
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
this.processingSyncs = false;
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
136
|
}
|
|
@@ -3,7 +3,7 @@ import { RawCoID } from "../ids.js";
|
|
|
3
3
|
import {
|
|
4
4
|
CoValueKnownState,
|
|
5
5
|
emptyKnownState,
|
|
6
|
-
|
|
6
|
+
areCurrentSessionsInSyncWith,
|
|
7
7
|
} from "../knownState.js";
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -87,7 +87,7 @@ function isInSync(
|
|
|
87
87
|
return false;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
return
|
|
90
|
+
return areCurrentSessionsInSyncWith(
|
|
91
91
|
knownState.sessions,
|
|
92
92
|
knownStateFromStorage.sessions,
|
|
93
93
|
);
|
package/src/sync.ts
CHANGED
|
@@ -771,7 +771,6 @@ export class SyncManager {
|
|
|
771
771
|
private syncQueue = new LocalTransactionsSyncQueue((content) =>
|
|
772
772
|
this.syncContent(content),
|
|
773
773
|
);
|
|
774
|
-
syncHeader = this.syncQueue.syncHeader;
|
|
775
774
|
syncLocalTransaction = this.syncQueue.syncTransaction;
|
|
776
775
|
trackDirtyCoValues = this.syncQueue.trackDirtyCoValues;
|
|
777
776
|
|
|
@@ -371,13 +371,12 @@ test("chunks transactions when when the chars are longer than MAX_RECOMMENDED_TX
|
|
|
371
371
|
}),
|
|
372
372
|
).toMatchInlineSnapshot(`
|
|
373
373
|
[
|
|
374
|
-
"client -> storage | CONTENT CoPlainText header: true new: After: 0 New:
|
|
375
|
-
"client -> storage | CONTENT CoPlainText header: false new: After: 1 New: 1",
|
|
374
|
+
"client -> storage | CONTENT CoPlainText header: true new: After: 0 New: 2 expectContentUntil: header/42",
|
|
376
375
|
"client -> storage | CONTENT CoPlainText header: false new: After: 2 New: 1",
|
|
377
376
|
"client -> storage | CONTENT CoPlainText header: false new: After: 3 New: 1",
|
|
378
377
|
"client -> storage | CONTENT CoPlainText header: false new: After: 4 New: 1",
|
|
379
|
-
"client -> storage | CONTENT CoPlainText header: false new: After: 5 New:
|
|
380
|
-
"client -> storage | CONTENT CoPlainText header: false new: After:
|
|
378
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 5 New: 1",
|
|
379
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 6 New: 2",
|
|
381
380
|
"client -> storage | CONTENT CoPlainText header: false new: After: 8 New: 1",
|
|
382
381
|
"client -> storage | CONTENT CoPlainText header: false new: After: 9 New: 1",
|
|
383
382
|
"client -> storage | CONTENT CoPlainText header: false new: After: 10 New: 1",
|
|
@@ -389,8 +388,8 @@ test("chunks transactions when when the chars are longer than MAX_RECOMMENDED_TX
|
|
|
389
388
|
"client -> storage | CONTENT CoPlainText header: false new: After: 16 New: 1",
|
|
390
389
|
"client -> storage | CONTENT CoPlainText header: false new: After: 17 New: 1",
|
|
391
390
|
"client -> storage | CONTENT CoPlainText header: false new: After: 18 New: 1",
|
|
392
|
-
"client -> storage | CONTENT CoPlainText header: false new: After: 19 New:
|
|
393
|
-
"client -> storage | CONTENT CoPlainText header: false new: After:
|
|
391
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 19 New: 1",
|
|
392
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 20 New: 2",
|
|
394
393
|
"client -> storage | CONTENT CoPlainText header: false new: After: 22 New: 1",
|
|
395
394
|
"client -> storage | CONTENT CoPlainText header: false new: After: 23 New: 1",
|
|
396
395
|
"client -> storage | CONTENT CoPlainText header: false new: After: 24 New: 1",
|
|
@@ -406,16 +405,16 @@ test("chunks transactions when when the chars are longer than MAX_RECOMMENDED_TX
|
|
|
406
405
|
"client -> storage | CONTENT CoPlainText header: false new: After: 34 New: 1",
|
|
407
406
|
"client -> storage | CONTENT CoPlainText header: false new: After: 35 New: 1",
|
|
408
407
|
"client -> storage | CONTENT CoPlainText header: false new: After: 36 New: 1",
|
|
409
|
-
"client -> storage | CONTENT CoPlainText header: false new: After: 37 New:
|
|
410
|
-
"client -> storage | CONTENT CoPlainText header: false new: After:
|
|
408
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 37 New: 1",
|
|
409
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 38 New: 3",
|
|
411
410
|
"client -> storage | CONTENT CoPlainText header: false new: After: 41 New: 1",
|
|
412
411
|
"client -> storage | LOAD CoPlainText sessions: empty",
|
|
413
412
|
"storage -> client | CONTENT CoPlainText header: true new: After: 0 New: 2 expectContentUntil: header/42",
|
|
414
413
|
"storage -> client | CONTENT CoPlainText header: true new: After: 2 New: 1",
|
|
415
414
|
"storage -> client | CONTENT CoPlainText header: true new: After: 3 New: 1",
|
|
416
415
|
"storage -> client | CONTENT CoPlainText header: true new: After: 4 New: 1",
|
|
417
|
-
"storage -> client | CONTENT CoPlainText header: true new: After: 5 New:
|
|
418
|
-
"storage -> client | CONTENT CoPlainText header: true new: After:
|
|
416
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 5 New: 1",
|
|
417
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 6 New: 2",
|
|
419
418
|
"storage -> client | CONTENT CoPlainText header: true new: After: 8 New: 1",
|
|
420
419
|
"storage -> client | CONTENT CoPlainText header: true new: After: 9 New: 1",
|
|
421
420
|
"storage -> client | CONTENT CoPlainText header: true new: After: 10 New: 1",
|
|
@@ -427,8 +426,8 @@ test("chunks transactions when when the chars are longer than MAX_RECOMMENDED_TX
|
|
|
427
426
|
"storage -> client | CONTENT CoPlainText header: true new: After: 16 New: 1",
|
|
428
427
|
"storage -> client | CONTENT CoPlainText header: true new: After: 17 New: 1",
|
|
429
428
|
"storage -> client | CONTENT CoPlainText header: true new: After: 18 New: 1",
|
|
430
|
-
"storage -> client | CONTENT CoPlainText header: true new: After: 19 New:
|
|
431
|
-
"storage -> client | CONTENT CoPlainText header: true new: After:
|
|
429
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 19 New: 1",
|
|
430
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 20 New: 2",
|
|
432
431
|
"storage -> client | CONTENT CoPlainText header: true new: After: 22 New: 1",
|
|
433
432
|
"storage -> client | CONTENT CoPlainText header: true new: After: 23 New: 1",
|
|
434
433
|
"storage -> client | CONTENT CoPlainText header: true new: After: 24 New: 1",
|
|
@@ -444,8 +443,8 @@ test("chunks transactions when when the chars are longer than MAX_RECOMMENDED_TX
|
|
|
444
443
|
"storage -> client | CONTENT CoPlainText header: true new: After: 34 New: 1",
|
|
445
444
|
"storage -> client | CONTENT CoPlainText header: true new: After: 35 New: 1",
|
|
446
445
|
"storage -> client | CONTENT CoPlainText header: true new: After: 36 New: 1",
|
|
447
|
-
"storage -> client | CONTENT CoPlainText header: true new: After: 37 New:
|
|
448
|
-
"storage -> client | CONTENT CoPlainText header: true new: After:
|
|
446
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 37 New: 1",
|
|
447
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 38 New: 3",
|
|
449
448
|
"storage -> client | CONTENT CoPlainText header: true new: After: 41 New: 1",
|
|
450
449
|
]
|
|
451
450
|
`);
|
|
@@ -537,8 +537,9 @@ describe("CoValueCore.isCompletelyDownloaded", () => {
|
|
|
537
537
|
bobSession.syncManager.handleNewContent(lastChunk, "import");
|
|
538
538
|
|
|
539
539
|
// Wait for the notification to be scheduled and executed
|
|
540
|
-
await waitFor(() =>
|
|
541
|
-
|
|
540
|
+
await waitFor(() => {
|
|
541
|
+
expect(mapOnBob.core.isCompletelyDownloaded()).toBe(true);
|
|
542
|
+
});
|
|
542
543
|
});
|
|
543
544
|
|
|
544
545
|
test.skip("should return false when the owner of the value is streaming", async () => {
|
|
@@ -3,9 +3,12 @@ import {
|
|
|
3
3
|
SyncMessagesLog,
|
|
4
4
|
TEST_NODE_CONFIG,
|
|
5
5
|
loadCoValueOrFail,
|
|
6
|
+
setupTestAccount,
|
|
6
7
|
setupTestNode,
|
|
7
8
|
waitFor,
|
|
8
9
|
} from "./testUtils";
|
|
10
|
+
import type { RawCoMap } from "../exports";
|
|
11
|
+
import type { CoID } from "../coValue";
|
|
9
12
|
|
|
10
13
|
let jazzCloud: ReturnType<typeof setupTestNode>;
|
|
11
14
|
|
|
@@ -82,7 +85,7 @@ describe("isStreaming", () => {
|
|
|
82
85
|
expect(mapInNewSession.core.isStreaming()).toBe(false);
|
|
83
86
|
});
|
|
84
87
|
|
|
85
|
-
test("loading a large content update
|
|
88
|
+
test("loading a large content update should be streaming until all chunks are sent", async () => {
|
|
86
89
|
const client = setupTestNode({
|
|
87
90
|
connected: true,
|
|
88
91
|
});
|
|
@@ -222,7 +225,8 @@ describe("isStreaming", () => {
|
|
|
222
225
|
await map.core.waitForSync();
|
|
223
226
|
const newSession = client.spawnNewSession();
|
|
224
227
|
|
|
225
|
-
await loadCoValueOrFail(newSession.node, map.id);
|
|
228
|
+
const mapInNewSession1 = await loadCoValueOrFail(newSession.node, map.id);
|
|
229
|
+
await mapInNewSession1.core.waitForFullStreaming();
|
|
226
230
|
|
|
227
231
|
const content = map.core.verified.newContentSince(undefined);
|
|
228
232
|
assert(content);
|
|
@@ -268,4 +272,82 @@ describe("isStreaming", () => {
|
|
|
268
272
|
|
|
269
273
|
expect(mapInNewSession.core.isStreaming()).toBe(false);
|
|
270
274
|
});
|
|
275
|
+
|
|
276
|
+
test("mixed updates should not leave isStreaming to true (3 sessions)", async () => {
|
|
277
|
+
const aliceLaptop = await setupTestAccount({
|
|
278
|
+
connected: true,
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
const group = aliceLaptop.node.createGroup();
|
|
282
|
+
const map = group.createMap();
|
|
283
|
+
|
|
284
|
+
map.set("count", 0, "trusting");
|
|
285
|
+
map.set("count", 1, "trusting");
|
|
286
|
+
|
|
287
|
+
await map.core.waitForSync();
|
|
288
|
+
|
|
289
|
+
const alicePhone = await aliceLaptop.spawnNewSession();
|
|
290
|
+
|
|
291
|
+
const mapOnPhone = await loadCoValueOrFail(alicePhone.node, map.id);
|
|
292
|
+
|
|
293
|
+
mapOnPhone.set("count", 2, "trusting");
|
|
294
|
+
mapOnPhone.set("count", 3, "trusting");
|
|
295
|
+
mapOnPhone.set("count", 4, "trusting");
|
|
296
|
+
|
|
297
|
+
await mapOnPhone.core.waitForSync();
|
|
298
|
+
|
|
299
|
+
const aliceTablet = await alicePhone.spawnNewSession();
|
|
300
|
+
const mapOnTablet = await loadCoValueOrFail(aliceTablet.node, map.id);
|
|
301
|
+
|
|
302
|
+
mapOnTablet.set("count", 5, "trusting");
|
|
303
|
+
mapOnTablet.set("count", 6, "trusting");
|
|
304
|
+
mapOnTablet.set("count", 7, "trusting");
|
|
305
|
+
|
|
306
|
+
await mapOnTablet.core.waitForSync();
|
|
307
|
+
|
|
308
|
+
map.set("count", 8, "trusting");
|
|
309
|
+
map.set("count", 9, "trusting");
|
|
310
|
+
map.set("count", 10, "trusting");
|
|
311
|
+
|
|
312
|
+
mapOnPhone.set("count", 11, "trusting");
|
|
313
|
+
mapOnTablet.set("count", 12, "trusting");
|
|
314
|
+
|
|
315
|
+
await map.core.waitForSync();
|
|
316
|
+
await mapOnPhone.core.waitForSync();
|
|
317
|
+
await mapOnTablet.core.waitForSync();
|
|
318
|
+
|
|
319
|
+
expect(map.core.isStreaming()).toBe(false);
|
|
320
|
+
expect(mapOnPhone.core.isStreaming()).toBe(false);
|
|
321
|
+
expect(mapOnTablet.core.isStreaming()).toBe(false);
|
|
322
|
+
|
|
323
|
+
const mapBranch = map.core.createBranch("test-branch");
|
|
324
|
+
|
|
325
|
+
const aliceTv = await aliceTablet.spawnNewSession();
|
|
326
|
+
|
|
327
|
+
const mapBranchOnTv = await loadCoValueOrFail(
|
|
328
|
+
aliceTv.node,
|
|
329
|
+
mapBranch.id as unknown as CoID<RawCoMap>,
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
mapBranchOnTv.set("count", 13, "trusting");
|
|
333
|
+
mapBranchOnTv.set("count", 14, "trusting");
|
|
334
|
+
mapBranchOnTv.set("count", 15, "trusting");
|
|
335
|
+
|
|
336
|
+
await mapBranchOnTv.core.waitForSync();
|
|
337
|
+
|
|
338
|
+
group.addMember("everyone", "reader");
|
|
339
|
+
|
|
340
|
+
const bob = await setupTestAccount({
|
|
341
|
+
connected: true,
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
const mapBranchOnBob = await loadCoValueOrFail(bob.node, mapBranchOnTv.id);
|
|
345
|
+
|
|
346
|
+
expect(mapBranchOnBob.core.isStreaming()).toBe(false);
|
|
347
|
+
expect(map.core.isStreaming()).toBe(false);
|
|
348
|
+
expect(mapOnPhone.core.isStreaming()).toBe(false);
|
|
349
|
+
expect(mapOnTablet.core.isStreaming()).toBe(false);
|
|
350
|
+
|
|
351
|
+
expect(mapBranchOnBob.get("count")).toBe(15);
|
|
352
|
+
});
|
|
271
353
|
});
|
|
@@ -89,7 +89,7 @@ describe("Group.childKeyRotation", () => {
|
|
|
89
89
|
const newBobSession = await bob.spawnNewSession();
|
|
90
90
|
|
|
91
91
|
const childGroupOnNewBobNode = await loadCoValueOrFail(
|
|
92
|
-
newBobSession,
|
|
92
|
+
newBobSession.node,
|
|
93
93
|
childGroup.id,
|
|
94
94
|
);
|
|
95
95
|
|
|
@@ -138,15 +138,15 @@ describe("Group.childKeyRotation", () => {
|
|
|
138
138
|
const newBobSession = await bob.spawnNewSession();
|
|
139
139
|
|
|
140
140
|
for (const chunk of content) {
|
|
141
|
-
newBobSession.syncManager.handleNewContent(chunk, "import");
|
|
141
|
+
newBobSession.node.syncManager.handleNewContent(chunk, "import");
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
const childGroupOnNewBobNode = await loadCoValueOrFail(
|
|
145
|
-
newBobSession,
|
|
145
|
+
newBobSession.node,
|
|
146
146
|
childGroup.id,
|
|
147
147
|
);
|
|
148
148
|
|
|
149
|
-
newBobSession.syncManager.handleNewContent(lastChunk, "import");
|
|
149
|
+
newBobSession.node.syncManager.handleNewContent(lastChunk, "import");
|
|
150
150
|
|
|
151
151
|
// The migration waits for the group to be completely downloaded
|
|
152
152
|
await childGroupOnNewBobNode.core.waitForAsync((core) =>
|
|
@@ -205,15 +205,15 @@ describe("Group.childKeyRotation", () => {
|
|
|
205
205
|
const newBobSession = await bob.spawnNewSession();
|
|
206
206
|
|
|
207
207
|
for (const chunk of content) {
|
|
208
|
-
newBobSession.syncManager.handleNewContent(chunk, "import");
|
|
208
|
+
newBobSession.node.syncManager.handleNewContent(chunk, "import");
|
|
209
209
|
}
|
|
210
210
|
|
|
211
211
|
const childGroupOnNewBobNode = await loadCoValueOrFail(
|
|
212
|
-
newBobSession,
|
|
212
|
+
newBobSession.node,
|
|
213
213
|
childGroup.id,
|
|
214
214
|
);
|
|
215
215
|
|
|
216
|
-
newBobSession.syncManager.handleNewContent(lastChunk, "import");
|
|
216
|
+
newBobSession.node.syncManager.handleNewContent(lastChunk, "import");
|
|
217
217
|
|
|
218
218
|
// The migration waits for the group to be completely downloaded, this includes full streaming of the parent group
|
|
219
219
|
await childGroupOnNewBobNode.core.waitForAsync((core) =>
|
|
@@ -268,7 +268,7 @@ describe("Group.childKeyRotation", () => {
|
|
|
268
268
|
|
|
269
269
|
// Instead Bob is an admin, so when loading the child group he can rotate the readKey
|
|
270
270
|
const newBobSession = await bob.spawnNewSession();
|
|
271
|
-
const mapOnNewBobNode = await loadCoValueOrFail(newBobSession, map.id);
|
|
271
|
+
const mapOnNewBobNode = await loadCoValueOrFail(newBobSession.node, map.id);
|
|
272
272
|
|
|
273
273
|
mapOnNewBobNode.set("test", "Not readable by charlie");
|
|
274
274
|
|
|
@@ -416,7 +416,7 @@ describe("Group.childKeyRotation", () => {
|
|
|
416
416
|
|
|
417
417
|
const newBobSession = await bob.spawnNewSession();
|
|
418
418
|
const childGroupOnNewBobNode = await loadCoValueOrFail(
|
|
419
|
-
newBobSession,
|
|
419
|
+
newBobSession.node,
|
|
420
420
|
childGroup.id,
|
|
421
421
|
);
|
|
422
422
|
|