cojson 0.16.5 → 0.16.7
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 +16 -0
- package/dist/GarbageCollector.d.ts +12 -0
- package/dist/GarbageCollector.d.ts.map +1 -0
- package/dist/GarbageCollector.js +37 -0
- package/dist/GarbageCollector.js.map +1 -0
- package/dist/coValue.d.ts +1 -1
- package/dist/coValueContentMessage.d.ts +1 -0
- package/dist/coValueContentMessage.d.ts.map +1 -1
- package/dist/coValueContentMessage.js +11 -3
- package/dist/coValueContentMessage.js.map +1 -1
- package/dist/coValueCore/coValueCore.d.ts +2 -1
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +15 -0
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/coValueCore/utils.d.ts.map +1 -1
- package/dist/coValueCore/utils.js.map +1 -1
- package/dist/coValueCore/verifiedState.d.ts +1 -0
- package/dist/coValueCore/verifiedState.d.ts.map +1 -1
- package/dist/coValueCore/verifiedState.js.map +1 -1
- package/dist/coValues/coMap.d.ts +3 -3
- package/dist/coValues/coPlainText.d.ts +1 -0
- package/dist/coValues/coPlainText.d.ts.map +1 -1
- package/dist/coValues/coPlainText.js +27 -8
- package/dist/coValues/coPlainText.js.map +1 -1
- package/dist/coValues/coStream.d.ts +2 -2
- package/dist/coValues/group.d.ts +1 -1
- package/dist/config.d.ts +10 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +16 -1
- package/dist/config.js.map +1 -1
- package/dist/exports.d.ts +11 -4
- package/dist/exports.d.ts.map +1 -1
- package/dist/exports.js +8 -3
- package/dist/exports.js.map +1 -1
- package/dist/localNode.d.ts +3 -0
- package/dist/localNode.d.ts.map +1 -1
- package/dist/localNode.js +11 -0
- package/dist/localNode.js.map +1 -1
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +3 -1
- package/dist/permissions.js.map +1 -1
- package/dist/queue/LocalTransactionsSyncQueue.js +1 -1
- package/dist/queue/LocalTransactionsSyncQueue.js.map +1 -1
- package/dist/queue/StoreQueue.d.ts +7 -1
- package/dist/queue/StoreQueue.d.ts.map +1 -1
- package/dist/queue/StoreQueue.js +35 -13
- package/dist/queue/StoreQueue.js.map +1 -1
- package/dist/storage/sqlite/client.d.ts +4 -4
- package/dist/storage/sqlite/client.d.ts.map +1 -1
- package/dist/storage/sqlite/client.js +13 -4
- package/dist/storage/sqlite/client.js.map +1 -1
- package/dist/storage/sqliteAsync/client.d.ts +3 -3
- package/dist/storage/sqliteAsync/client.d.ts.map +1 -1
- package/dist/storage/sqliteAsync/client.js +12 -3
- package/dist/storage/sqliteAsync/client.js.map +1 -1
- package/dist/storage/storageAsync.d.ts.map +1 -1
- package/dist/storage/storageAsync.js +2 -7
- package/dist/storage/storageAsync.js.map +1 -1
- package/dist/storage/storageSync.d.ts.map +1 -1
- package/dist/storage/storageSync.js +2 -7
- package/dist/storage/storageSync.js.map +1 -1
- package/dist/storage/types.d.ts +2 -2
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +17 -3
- package/dist/sync.js.map +1 -1
- package/dist/tests/GarbageCollector.test.d.ts +2 -0
- package/dist/tests/GarbageCollector.test.d.ts.map +1 -0
- package/dist/tests/GarbageCollector.test.js +85 -0
- package/dist/tests/GarbageCollector.test.js.map +1 -0
- package/dist/tests/coPlainText.test.js +142 -4
- package/dist/tests/coPlainText.test.js.map +1 -1
- package/dist/tests/coStream.test.js +3 -3
- package/dist/tests/coStream.test.js.map +1 -1
- package/dist/tests/sync.garbageCollection.test.d.ts +2 -0
- package/dist/tests/sync.garbageCollection.test.d.ts.map +1 -0
- package/dist/tests/sync.garbageCollection.test.js +133 -0
- package/dist/tests/sync.garbageCollection.test.js.map +1 -0
- package/dist/tests/sync.mesh.test.js +48 -34
- package/dist/tests/sync.mesh.test.js.map +1 -1
- package/dist/tests/sync.storage.test.js +31 -21
- package/dist/tests/sync.storage.test.js.map +1 -1
- package/dist/tests/sync.storageAsync.test.js +76 -29
- package/dist/tests/sync.storageAsync.test.js.map +1 -1
- package/dist/tests/testStorage.d.ts +1 -0
- package/dist/tests/testStorage.d.ts.map +1 -1
- package/dist/tests/testStorage.js +1 -1
- package/dist/tests/testStorage.js.map +1 -1
- package/dist/tests/testUtils.d.ts +1 -0
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +1 -0
- package/dist/tests/testUtils.js.map +1 -1
- package/package.json +1 -1
- package/src/GarbageCollector.ts +48 -0
- package/src/coValueContentMessage.ts +16 -3
- package/src/coValueCore/coValueCore.ts +27 -10
- package/src/coValueCore/utils.ts +1 -0
- package/src/coValueCore/verifiedState.ts +1 -0
- package/src/coValues/coPlainText.ts +40 -8
- package/src/config.ts +20 -1
- package/src/exports.ts +13 -5
- package/src/localNode.ts +15 -1
- package/src/permissions.ts +3 -1
- package/src/queue/LocalTransactionsSyncQueue.ts +1 -1
- package/src/queue/StoreQueue.ts +45 -12
- package/src/storage/sqlite/client.ts +24 -10
- package/src/storage/sqliteAsync/client.ts +26 -5
- package/src/storage/storageAsync.ts +5 -9
- package/src/storage/storageSync.ts +2 -9
- package/src/storage/types.ts +7 -4
- package/src/sync.ts +19 -3
- package/src/tests/GarbageCollector.test.ts +127 -0
- package/src/tests/coPlainText.test.ts +176 -4
- package/src/tests/coStream.test.ts +7 -3
- package/src/tests/sync.garbageCollection.test.ts +178 -0
- package/src/tests/sync.mesh.test.ts +49 -34
- package/src/tests/sync.storage.test.ts +31 -21
- package/src/tests/sync.storageAsync.test.ts +81 -29
- package/src/tests/testStorage.ts +11 -3
- package/src/tests/testUtils.ts +4 -1
package/src/storage/types.ts
CHANGED
|
@@ -65,6 +65,11 @@ export interface DBClientInterfaceAsync {
|
|
|
65
65
|
coValueId: string,
|
|
66
66
|
): Promise<StoredCoValueRow | undefined> | undefined;
|
|
67
67
|
|
|
68
|
+
upsertCoValue(
|
|
69
|
+
id: string,
|
|
70
|
+
header?: CoValueHeader,
|
|
71
|
+
): Promise<number | undefined>;
|
|
72
|
+
|
|
68
73
|
getCoValueSessions(coValueRowId: number): Promise<StoredSessionRow[]>;
|
|
69
74
|
|
|
70
75
|
getSingleCoValueSession(
|
|
@@ -83,8 +88,6 @@ export interface DBClientInterfaceAsync {
|
|
|
83
88
|
firstNewTxIdx: number,
|
|
84
89
|
): Promise<SignatureAfterRow[]>;
|
|
85
90
|
|
|
86
|
-
addCoValue(msg: NewContentMessage): Promise<number>;
|
|
87
|
-
|
|
88
91
|
addSessionUpdate({
|
|
89
92
|
sessionUpdate,
|
|
90
93
|
sessionRow,
|
|
@@ -115,6 +118,8 @@ export interface DBClientInterfaceAsync {
|
|
|
115
118
|
export interface DBClientInterfaceSync {
|
|
116
119
|
getCoValue(coValueId: string): StoredCoValueRow | undefined;
|
|
117
120
|
|
|
121
|
+
upsertCoValue(id: string, header?: CoValueHeader): number | undefined;
|
|
122
|
+
|
|
118
123
|
getCoValueSessions(coValueRowId: number): StoredSessionRow[];
|
|
119
124
|
|
|
120
125
|
getSingleCoValueSession(
|
|
@@ -133,8 +138,6 @@ export interface DBClientInterfaceSync {
|
|
|
133
138
|
firstNewTxIdx: number,
|
|
134
139
|
): Pick<SignatureAfterRow, "idx" | "signature">[];
|
|
135
140
|
|
|
136
|
-
addCoValue(msg: NewContentMessage): number;
|
|
137
|
-
|
|
138
141
|
addSessionUpdate({
|
|
139
142
|
sessionUpdate,
|
|
140
143
|
sessionRow,
|
package/src/sync.ts
CHANGED
|
@@ -461,6 +461,22 @@ export class SyncManager {
|
|
|
461
461
|
|
|
462
462
|
if (!coValue.hasVerifiedContent()) {
|
|
463
463
|
if (!msg.header) {
|
|
464
|
+
const storageKnownState = this.local.storage?.getKnownState(msg.id);
|
|
465
|
+
|
|
466
|
+
if (storageKnownState?.header) {
|
|
467
|
+
// If the CoValue has been garbage collected, we load it from the storage before handling the new content
|
|
468
|
+
coValue.loadFromStorage((found) => {
|
|
469
|
+
if (found) {
|
|
470
|
+
this.handleNewContent(msg, from);
|
|
471
|
+
} else {
|
|
472
|
+
logger.error("Known CoValue not found in storage", {
|
|
473
|
+
id: msg.id,
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
|
|
464
480
|
if (peer) {
|
|
465
481
|
this.trySendToPeer(peer, {
|
|
466
482
|
action: "known",
|
|
@@ -782,12 +798,12 @@ export class SyncManager {
|
|
|
782
798
|
|
|
783
799
|
if (!storage) return;
|
|
784
800
|
|
|
801
|
+
const value = this.local.getCoValue(content.id);
|
|
802
|
+
|
|
785
803
|
// Try to store the content as-is for performance
|
|
786
804
|
// In case that some transactions are missing, a correction will be requested, but it's an edge case
|
|
787
805
|
storage.store(content, (correction) => {
|
|
788
|
-
return
|
|
789
|
-
.getCoValue(content.id)
|
|
790
|
-
.verified?.newContentSince(correction);
|
|
806
|
+
return value.verified?.newContentSince(correction);
|
|
791
807
|
});
|
|
792
808
|
}
|
|
793
809
|
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { assert, beforeEach, describe, expect, test, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { setGarbageCollectorMaxAge } from "../config";
|
|
4
|
+
import { TEST_NODE_CONFIG, setupTestAccount, setupTestNode } from "./testUtils";
|
|
5
|
+
|
|
6
|
+
// We want to simulate a real world communication that happens asynchronously
|
|
7
|
+
TEST_NODE_CONFIG.withAsyncPeers = true;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
// We want to test what happens when the garbage collector kicks in and removes a coValue
|
|
11
|
+
// We set the max age to -1 to make it remove everything
|
|
12
|
+
setGarbageCollectorMaxAge(-1);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe("garbage collector", () => {
|
|
16
|
+
test("coValues are garbage collected when maxAge is reached", async () => {
|
|
17
|
+
const client = setupTestNode();
|
|
18
|
+
|
|
19
|
+
client.addStorage({
|
|
20
|
+
ourName: "client",
|
|
21
|
+
});
|
|
22
|
+
client.node.enableGarbageCollector();
|
|
23
|
+
|
|
24
|
+
const group = client.node.createGroup();
|
|
25
|
+
const map = group.createMap();
|
|
26
|
+
map.set("hello", "world", "trusting");
|
|
27
|
+
|
|
28
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
29
|
+
|
|
30
|
+
client.node.garbageCollector?.collect();
|
|
31
|
+
|
|
32
|
+
const coValue = client.node.getCoValue(map.id);
|
|
33
|
+
|
|
34
|
+
expect(coValue.isAvailable()).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("coValues are not garbage collected if they have listeners", async () => {
|
|
38
|
+
const client = setupTestNode();
|
|
39
|
+
|
|
40
|
+
client.addStorage({
|
|
41
|
+
ourName: "client",
|
|
42
|
+
});
|
|
43
|
+
client.node.enableGarbageCollector();
|
|
44
|
+
|
|
45
|
+
const group = client.node.createGroup();
|
|
46
|
+
const map = group.createMap();
|
|
47
|
+
map.set("hello", "world", "trusting");
|
|
48
|
+
|
|
49
|
+
// Add a listener to the map
|
|
50
|
+
const unsubscribe = map.subscribe(() => {
|
|
51
|
+
// This listener keeps the coValue alive
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
55
|
+
|
|
56
|
+
client.node.garbageCollector?.collect();
|
|
57
|
+
|
|
58
|
+
expect(client.node.getCoValue(map.id).isAvailable()).toBe(true);
|
|
59
|
+
|
|
60
|
+
// Clean up the listener
|
|
61
|
+
unsubscribe();
|
|
62
|
+
|
|
63
|
+
// The coValue should be collected after the listener is removed
|
|
64
|
+
client.node.garbageCollector?.collect();
|
|
65
|
+
|
|
66
|
+
expect(client.node.getCoValue(map.id).isAvailable()).toBe(false);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("coValues are not garbage collected if they are a group or account", async () => {
|
|
70
|
+
const client = await setupTestAccount();
|
|
71
|
+
|
|
72
|
+
client.addStorage({
|
|
73
|
+
ourName: "client",
|
|
74
|
+
});
|
|
75
|
+
client.node.enableGarbageCollector();
|
|
76
|
+
|
|
77
|
+
const group = client.node.createGroup();
|
|
78
|
+
|
|
79
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
80
|
+
|
|
81
|
+
client.node.garbageCollector?.collect();
|
|
82
|
+
|
|
83
|
+
expect(client.node.getCoValue(group.id).isAvailable()).toBe(true);
|
|
84
|
+
expect(client.node.getCoValue(client.accountID).isAvailable()).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("coValues are not garbage collected if the maxAge is not reached", async () => {
|
|
88
|
+
setGarbageCollectorMaxAge(1000);
|
|
89
|
+
|
|
90
|
+
const client = setupTestNode();
|
|
91
|
+
|
|
92
|
+
client.addStorage({
|
|
93
|
+
ourName: "client",
|
|
94
|
+
});
|
|
95
|
+
client.node.enableGarbageCollector();
|
|
96
|
+
|
|
97
|
+
const garbageCollector = client.node.garbageCollector;
|
|
98
|
+
|
|
99
|
+
assert(garbageCollector);
|
|
100
|
+
|
|
101
|
+
const getCurrentTime = vi.spyOn(garbageCollector, "getCurrentTime");
|
|
102
|
+
|
|
103
|
+
getCurrentTime.mockReturnValue(1);
|
|
104
|
+
|
|
105
|
+
const group = client.node.createGroup();
|
|
106
|
+
const map1 = group.createMap();
|
|
107
|
+
const map2 = group.createMap();
|
|
108
|
+
|
|
109
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
110
|
+
|
|
111
|
+
map1.set("hello", "world", "trusting");
|
|
112
|
+
|
|
113
|
+
getCurrentTime.mockReturnValue(2000);
|
|
114
|
+
|
|
115
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
116
|
+
|
|
117
|
+
garbageCollector.collect();
|
|
118
|
+
|
|
119
|
+
const coValue = client.node.getCoValue(map1.id);
|
|
120
|
+
|
|
121
|
+
expect(coValue.isAvailable()).toBe(true);
|
|
122
|
+
|
|
123
|
+
const coValue2 = client.node.getCoValue(map2.id);
|
|
124
|
+
|
|
125
|
+
expect(coValue2.isAvailable()).toBe(false);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
@@ -1,10 +1,21 @@
|
|
|
1
|
-
import { afterEach, expect, test, vi } from "vitest";
|
|
1
|
+
import { afterEach, beforeEach, expect, test, vi } from "vitest";
|
|
2
2
|
import { expectPlainText } from "../coValue.js";
|
|
3
|
+
import { setMaxRecommendedTxSize } from "../config.js";
|
|
3
4
|
import { WasmCrypto } from "../crypto/WasmCrypto.js";
|
|
4
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
SyncMessagesLog,
|
|
7
|
+
loadCoValueOrFail,
|
|
8
|
+
nodeWithRandomAgentAndSessionID,
|
|
9
|
+
setupTestNode,
|
|
10
|
+
} from "./testUtils.js";
|
|
5
11
|
|
|
6
12
|
const Crypto = await WasmCrypto.create();
|
|
7
13
|
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
setMaxRecommendedTxSize(100 * 1024);
|
|
16
|
+
SyncMessagesLog.clear();
|
|
17
|
+
});
|
|
18
|
+
|
|
8
19
|
afterEach(() => void vi.unstubAllGlobals());
|
|
9
20
|
|
|
10
21
|
test("Empty CoPlainText works", () => {
|
|
@@ -185,8 +196,8 @@ test("insertBefore and insertAfter work as expected", () => {
|
|
|
185
196
|
expect(content.toString()).toEqual("hey");
|
|
186
197
|
|
|
187
198
|
// Insert '!' at start
|
|
188
|
-
content.insertBefore(0, "
|
|
189
|
-
expect(content.toString()).toEqual("
|
|
199
|
+
content.insertBefore(0, "!?", "trusting"); // "!?hey"
|
|
200
|
+
expect(content.toString()).toEqual("!?hey");
|
|
190
201
|
});
|
|
191
202
|
|
|
192
203
|
test("Can delete a single grapheme", () => {
|
|
@@ -286,3 +297,164 @@ test("Splits into and from grapheme string arrays", () => {
|
|
|
286
297
|
const text = content.fromGraphemes(graphemes);
|
|
287
298
|
expect(text).toEqual("👋 안녕!");
|
|
288
299
|
});
|
|
300
|
+
|
|
301
|
+
test("chunks transactions when when the chars are longer than MAX_RECOMMENDED_TX_SIZE", async () => {
|
|
302
|
+
setMaxRecommendedTxSize(5);
|
|
303
|
+
|
|
304
|
+
const client = setupTestNode();
|
|
305
|
+
const { storage } = client.addStorage();
|
|
306
|
+
|
|
307
|
+
const coValue = client.node.createCoValue({
|
|
308
|
+
type: "coplaintext",
|
|
309
|
+
ruleset: { type: "unsafeAllowAll" },
|
|
310
|
+
meta: null,
|
|
311
|
+
...Crypto.createdNowUnique(),
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
const content = expectPlainText(coValue.getCurrentContent());
|
|
315
|
+
|
|
316
|
+
content.insertAfter(
|
|
317
|
+
content.entries().length,
|
|
318
|
+
"I'm writing you to test that coplaintext",
|
|
319
|
+
"trusting",
|
|
320
|
+
);
|
|
321
|
+
content.insertAfter(
|
|
322
|
+
content.entries().length,
|
|
323
|
+
" chunks transactions when when the chars are longer than MAX_RECOMMENDED_TX_SIZE.",
|
|
324
|
+
"trusting",
|
|
325
|
+
);
|
|
326
|
+
content.insertAfter(
|
|
327
|
+
content.entries().length,
|
|
328
|
+
"This is required because when a user paste 1Mb of text, we can split it in multiple websocket messages.",
|
|
329
|
+
"trusting",
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
content.insertBefore(0, "Dear reader,\n", "trusting");
|
|
333
|
+
|
|
334
|
+
expect(content.toString()).toMatchInlineSnapshot(
|
|
335
|
+
`"Dear reader,\nI'm writing you to test that coplaintext chunks transactions when when the chars are longer than MAX_RECOMMENDED_TX_SIZE.This is required because when a user paste 1Mb of text, we can split it in multiple websocket messages."`,
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
await coValue.waitForSync();
|
|
339
|
+
|
|
340
|
+
client.restart();
|
|
341
|
+
client.addStorage({
|
|
342
|
+
storage,
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
const loaded = await loadCoValueOrFail(client.node, content.id);
|
|
346
|
+
await loaded.core.waitForSync();
|
|
347
|
+
|
|
348
|
+
expect(loaded.toString()).toEqual(content.toString());
|
|
349
|
+
|
|
350
|
+
expect(
|
|
351
|
+
SyncMessagesLog.getMessages({
|
|
352
|
+
CoPlainText: coValue,
|
|
353
|
+
}),
|
|
354
|
+
).toMatchInlineSnapshot(`
|
|
355
|
+
[
|
|
356
|
+
"client -> storage | CONTENT CoPlainText header: true new: ",
|
|
357
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 0 New: 1",
|
|
358
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 1 New: 1",
|
|
359
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 2 New: 1",
|
|
360
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 3 New: 1",
|
|
361
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 4 New: 1",
|
|
362
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 5 New: 1",
|
|
363
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 6 New: 1",
|
|
364
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 7 New: 1",
|
|
365
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 8 New: 1",
|
|
366
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 9 New: 1",
|
|
367
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 10 New: 1",
|
|
368
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 11 New: 1",
|
|
369
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 12 New: 1",
|
|
370
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 13 New: 1",
|
|
371
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 14 New: 1",
|
|
372
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 15 New: 1",
|
|
373
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 16 New: 1",
|
|
374
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 17 New: 1",
|
|
375
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 18 New: 1",
|
|
376
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 19 New: 1",
|
|
377
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 20 New: 1",
|
|
378
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 21 New: 1",
|
|
379
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 22 New: 1",
|
|
380
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 23 New: 1",
|
|
381
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 24 New: 1",
|
|
382
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 25 New: 1",
|
|
383
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 26 New: 1",
|
|
384
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 27 New: 1",
|
|
385
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 28 New: 1",
|
|
386
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 29 New: 1",
|
|
387
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 30 New: 1",
|
|
388
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 31 New: 1",
|
|
389
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 32 New: 1",
|
|
390
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 33 New: 1",
|
|
391
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 34 New: 1",
|
|
392
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 35 New: 1",
|
|
393
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 36 New: 1",
|
|
394
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 37 New: 1",
|
|
395
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 38 New: 1",
|
|
396
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 39 New: 1",
|
|
397
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 40 New: 1",
|
|
398
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 41 New: 1",
|
|
399
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 42 New: 1",
|
|
400
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 43 New: 1",
|
|
401
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 44 New: 1",
|
|
402
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 45 New: 1",
|
|
403
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 46 New: 1",
|
|
404
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 47 New: 1",
|
|
405
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 48 New: 1",
|
|
406
|
+
"client -> storage | CONTENT CoPlainText header: false new: After: 49 New: 1",
|
|
407
|
+
"client -> storage | LOAD CoPlainText sessions: empty",
|
|
408
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 0 New: 1 expectContentUntil: header/50",
|
|
409
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 1 New: 1",
|
|
410
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 2 New: 1",
|
|
411
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 3 New: 1",
|
|
412
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 4 New: 1",
|
|
413
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 5 New: 1",
|
|
414
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 6 New: 1",
|
|
415
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 7 New: 1",
|
|
416
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 8 New: 1",
|
|
417
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 9 New: 1",
|
|
418
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 10 New: 1",
|
|
419
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 11 New: 1",
|
|
420
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 12 New: 1",
|
|
421
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 13 New: 1",
|
|
422
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 14 New: 1",
|
|
423
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 15 New: 1",
|
|
424
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 16 New: 1",
|
|
425
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 17 New: 1",
|
|
426
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 18 New: 1",
|
|
427
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 19 New: 1",
|
|
428
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 20 New: 1",
|
|
429
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 21 New: 1",
|
|
430
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 22 New: 1",
|
|
431
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 23 New: 1",
|
|
432
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 24 New: 1",
|
|
433
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 25 New: 1",
|
|
434
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 26 New: 1",
|
|
435
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 27 New: 1",
|
|
436
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 28 New: 1",
|
|
437
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 29 New: 1",
|
|
438
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 30 New: 1",
|
|
439
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 31 New: 1",
|
|
440
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 32 New: 1",
|
|
441
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 33 New: 1",
|
|
442
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 34 New: 1",
|
|
443
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 35 New: 1",
|
|
444
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 36 New: 1",
|
|
445
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 37 New: 1",
|
|
446
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 38 New: 1",
|
|
447
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 39 New: 1",
|
|
448
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 40 New: 1",
|
|
449
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 41 New: 1",
|
|
450
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 42 New: 1",
|
|
451
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 43 New: 1",
|
|
452
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 44 New: 1",
|
|
453
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 45 New: 1",
|
|
454
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 46 New: 1",
|
|
455
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 47 New: 1",
|
|
456
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 48 New: 1",
|
|
457
|
+
"storage -> client | CONTENT CoPlainText header: true new: After: 49 New: 1",
|
|
458
|
+
]
|
|
459
|
+
`);
|
|
460
|
+
});
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
RawBinaryCoStream,
|
|
7
7
|
RawCoStreamView,
|
|
8
8
|
} from "../coValues/coStream.js";
|
|
9
|
-
import {
|
|
9
|
+
import { TRANSACTION_CONFIG } from "../config.js";
|
|
10
10
|
import { WasmCrypto } from "../crypto/WasmCrypto.js";
|
|
11
11
|
import { SessionID } from "../ids.js";
|
|
12
12
|
import {
|
|
@@ -153,7 +153,9 @@ test("When adding large transactions (small fraction of MAX_RECOMMENDED_TX_SIZE)
|
|
|
153
153
|
);
|
|
154
154
|
|
|
155
155
|
for (let i = 0; i < 10; i++) {
|
|
156
|
-
const chunk = new Uint8Array(
|
|
156
|
+
const chunk = new Uint8Array(
|
|
157
|
+
TRANSACTION_CONFIG.MAX_RECOMMENDED_TX_SIZE / 3 + 100,
|
|
158
|
+
);
|
|
157
159
|
|
|
158
160
|
content.pushBinaryStreamChunk(chunk, "trusting");
|
|
159
161
|
}
|
|
@@ -226,7 +228,9 @@ test("When adding large transactions (bigger than MAX_RECOMMENDED_TX_SIZE), we s
|
|
|
226
228
|
"trusting",
|
|
227
229
|
);
|
|
228
230
|
|
|
229
|
-
const chunk = new Uint8Array(
|
|
231
|
+
const chunk = new Uint8Array(
|
|
232
|
+
TRANSACTION_CONFIG.MAX_RECOMMENDED_TX_SIZE + 100,
|
|
233
|
+
);
|
|
230
234
|
|
|
231
235
|
for (let i = 0; i < 3; i++) {
|
|
232
236
|
content.pushBinaryStreamChunk(chunk, "trusting");
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { assert, beforeEach, describe, expect, test, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { setGarbageCollectorMaxAge } from "../config";
|
|
4
|
+
import { emptyKnownState } from "../exports";
|
|
5
|
+
import {
|
|
6
|
+
SyncMessagesLog,
|
|
7
|
+
TEST_NODE_CONFIG,
|
|
8
|
+
loadCoValueOrFail,
|
|
9
|
+
setupTestNode,
|
|
10
|
+
waitFor,
|
|
11
|
+
} from "./testUtils";
|
|
12
|
+
|
|
13
|
+
// We want to simulate a real world communication that happens asynchronously
|
|
14
|
+
TEST_NODE_CONFIG.withAsyncPeers = true;
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
// We want to test what happens when the garbage collector kicks in and removes a coValue
|
|
18
|
+
// We set the max age to -1 to make it remove everything
|
|
19
|
+
setGarbageCollectorMaxAge(-1);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe("sync after the garbage collector has run", () => {
|
|
23
|
+
let jazzCloud: ReturnType<typeof setupTestNode>;
|
|
24
|
+
|
|
25
|
+
beforeEach(async () => {
|
|
26
|
+
SyncMessagesLog.clear();
|
|
27
|
+
jazzCloud = setupTestNode({
|
|
28
|
+
isSyncServer: true,
|
|
29
|
+
});
|
|
30
|
+
jazzCloud.addStorage({
|
|
31
|
+
ourName: "server",
|
|
32
|
+
});
|
|
33
|
+
jazzCloud.node.enableGarbageCollector();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("loading a coValue from the sync server that was removed by the garbage collector", async () => {
|
|
37
|
+
const client = setupTestNode();
|
|
38
|
+
|
|
39
|
+
client.connectToSyncServer();
|
|
40
|
+
|
|
41
|
+
const group = jazzCloud.node.createGroup();
|
|
42
|
+
const map = group.createMap();
|
|
43
|
+
map.set("hello", "world", "trusting");
|
|
44
|
+
|
|
45
|
+
await map.core.waitForSync();
|
|
46
|
+
|
|
47
|
+
// force the garbage collector to run
|
|
48
|
+
jazzCloud.node.garbageCollector?.collect();
|
|
49
|
+
|
|
50
|
+
SyncMessagesLog.clear();
|
|
51
|
+
|
|
52
|
+
const mapOnClient = await loadCoValueOrFail(client.node, map.id);
|
|
53
|
+
expect(mapOnClient.get("hello")).toEqual("world");
|
|
54
|
+
|
|
55
|
+
expect(
|
|
56
|
+
SyncMessagesLog.getMessages({
|
|
57
|
+
Group: group.core,
|
|
58
|
+
Map: map.core,
|
|
59
|
+
}),
|
|
60
|
+
).toMatchInlineSnapshot(`
|
|
61
|
+
[
|
|
62
|
+
"client -> server | LOAD Map sessions: empty",
|
|
63
|
+
"server -> storage | LOAD Map sessions: empty",
|
|
64
|
+
"storage -> server | CONTENT Group header: true new: After: 0 New: 3",
|
|
65
|
+
"storage -> server | CONTENT Map header: true new: After: 0 New: 1",
|
|
66
|
+
"server -> client | CONTENT Group header: true new: After: 0 New: 3",
|
|
67
|
+
"server -> client | CONTENT Map header: true new: After: 0 New: 1",
|
|
68
|
+
"client -> server | KNOWN Group sessions: header/3",
|
|
69
|
+
"client -> server | KNOWN Map sessions: header/1",
|
|
70
|
+
]
|
|
71
|
+
`);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("updating a coValue that was removed by the garbage collector", async () => {
|
|
75
|
+
const client = setupTestNode();
|
|
76
|
+
|
|
77
|
+
client.connectToSyncServer();
|
|
78
|
+
|
|
79
|
+
const group = jazzCloud.node.createGroup();
|
|
80
|
+
group.addMember("everyone", "writer");
|
|
81
|
+
const map = group.createMap();
|
|
82
|
+
map.set("hello", "world", "trusting");
|
|
83
|
+
|
|
84
|
+
const mapOnClient = await loadCoValueOrFail(client.node, map.id);
|
|
85
|
+
expect(mapOnClient.get("hello")).toEqual("world");
|
|
86
|
+
|
|
87
|
+
// force the garbage collector to run
|
|
88
|
+
jazzCloud.node.garbageCollector?.collect();
|
|
89
|
+
SyncMessagesLog.clear();
|
|
90
|
+
|
|
91
|
+
mapOnClient.set("hello", "updated", "trusting");
|
|
92
|
+
|
|
93
|
+
await mapOnClient.core.waitForSync();
|
|
94
|
+
|
|
95
|
+
const mapOnServer = await loadCoValueOrFail(jazzCloud.node, map.id);
|
|
96
|
+
|
|
97
|
+
expect(mapOnServer.get("hello")).toEqual("updated");
|
|
98
|
+
|
|
99
|
+
expect(
|
|
100
|
+
SyncMessagesLog.getMessages({
|
|
101
|
+
Group: group.core,
|
|
102
|
+
Map: map.core,
|
|
103
|
+
}),
|
|
104
|
+
).toMatchInlineSnapshot(`
|
|
105
|
+
[
|
|
106
|
+
"client -> server | CONTENT Map header: false new: After: 0 New: 1",
|
|
107
|
+
"server -> storage | LOAD Map sessions: empty",
|
|
108
|
+
"storage -> server | CONTENT Group header: true new: After: 0 New: 5",
|
|
109
|
+
"storage -> server | CONTENT Map header: true new: After: 0 New: 1",
|
|
110
|
+
"server -> client | KNOWN Map sessions: header/2",
|
|
111
|
+
"server -> storage | CONTENT Map header: false new: After: 0 New: 1",
|
|
112
|
+
]
|
|
113
|
+
`);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("syncing a coValue that was removed by the garbage collector", async () => {
|
|
117
|
+
const edge = setupTestNode();
|
|
118
|
+
edge.addStorage({
|
|
119
|
+
ourName: "edge",
|
|
120
|
+
});
|
|
121
|
+
edge.connectToSyncServer({
|
|
122
|
+
syncServer: jazzCloud.node,
|
|
123
|
+
syncServerName: "server",
|
|
124
|
+
ourName: "edge",
|
|
125
|
+
});
|
|
126
|
+
edge.node.enableGarbageCollector();
|
|
127
|
+
const client = setupTestNode();
|
|
128
|
+
|
|
129
|
+
client.connectToSyncServer({
|
|
130
|
+
syncServer: edge.node,
|
|
131
|
+
syncServerName: "edge",
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const group = edge.node.createGroup();
|
|
135
|
+
group.addMember("everyone", "writer");
|
|
136
|
+
|
|
137
|
+
await group.core.waitForSync();
|
|
138
|
+
|
|
139
|
+
const map = group.createMap();
|
|
140
|
+
|
|
141
|
+
map.set("hello", "updated", "trusting");
|
|
142
|
+
|
|
143
|
+
// force the garbage collector to run before the transaction is synced
|
|
144
|
+
edge.node.garbageCollector?.collect();
|
|
145
|
+
expect(edge.node.getCoValue(map.id).isAvailable()).toBe(false);
|
|
146
|
+
|
|
147
|
+
SyncMessagesLog.clear();
|
|
148
|
+
|
|
149
|
+
// The storage should work even after the coValue is unmounted, so the load should be successful
|
|
150
|
+
const mapOnClient = await loadCoValueOrFail(client.node, map.id);
|
|
151
|
+
expect(mapOnClient.get("hello")).toEqual("updated");
|
|
152
|
+
|
|
153
|
+
expect(
|
|
154
|
+
SyncMessagesLog.getMessages({
|
|
155
|
+
Group: group.core,
|
|
156
|
+
Map: map.core,
|
|
157
|
+
}),
|
|
158
|
+
).toMatchInlineSnapshot(`
|
|
159
|
+
[
|
|
160
|
+
"client -> edge | LOAD Map sessions: empty",
|
|
161
|
+
"edge -> storage | CONTENT Map header: true new: After: 0 New: 1",
|
|
162
|
+
"edge -> server | CONTENT Map header: true new: After: 0 New: 1",
|
|
163
|
+
"edge -> storage | LOAD Map sessions: empty",
|
|
164
|
+
"storage -> edge | CONTENT Group header: true new: After: 0 New: 5",
|
|
165
|
+
"storage -> edge | CONTENT Map header: true new: After: 0 New: 1",
|
|
166
|
+
"edge -> server | CONTENT Map header: true new: ",
|
|
167
|
+
"edge -> client | CONTENT Group header: true new: After: 0 New: 5",
|
|
168
|
+
"edge -> client | CONTENT Map header: true new: After: 0 New: 1",
|
|
169
|
+
"server -> edge | KNOWN Map sessions: header/1",
|
|
170
|
+
"server -> storage | CONTENT Map header: true new: After: 0 New: 1",
|
|
171
|
+
"server -> edge | KNOWN Map sessions: header/1",
|
|
172
|
+
"server -> storage | CONTENT Map header: true new: ",
|
|
173
|
+
"client -> edge | KNOWN Group sessions: header/5",
|
|
174
|
+
"client -> edge | KNOWN Map sessions: header/1",
|
|
175
|
+
]
|
|
176
|
+
`);
|
|
177
|
+
});
|
|
178
|
+
});
|