cojson-storage-sqlite 0.13.2 → 0.13.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 +20 -0
- package/LICENSE.txt +1 -1
- package/dist/tests/messagesTestUtils.d.ts +10 -0
- package/dist/tests/messagesTestUtils.d.ts.map +1 -0
- package/dist/tests/messagesTestUtils.js +42 -0
- package/dist/tests/messagesTestUtils.js.map +1 -0
- package/dist/tests/sqlite.test.d.ts +2 -0
- package/dist/tests/sqlite.test.d.ts.map +1 -0
- package/dist/tests/sqlite.test.js +191 -0
- package/dist/tests/sqlite.test.js.map +1 -0
- package/dist/tests/testUtils.d.ts +9 -0
- package/dist/tests/testUtils.d.ts.map +1 -0
- package/dist/tests/testUtils.js +33 -0
- package/dist/tests/testUtils.js.map +1 -0
- package/package.json +4 -4
- package/src/tests/messagesTestUtils.ts +72 -0
- package/src/tests/sqlite.test.ts +321 -0
- package/src/tests/testUtils.ts +43 -0
package/.turbo/turbo-build.log
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# cojson-storage-sqlite
|
|
2
2
|
|
|
3
|
+
## 0.13.7
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [bc3d7bb]
|
|
8
|
+
- Updated dependencies [4e9aae1]
|
|
9
|
+
- Updated dependencies [21c935c]
|
|
10
|
+
- Updated dependencies [aa1c80e]
|
|
11
|
+
- Updated dependencies [13074be]
|
|
12
|
+
- cojson@0.13.7
|
|
13
|
+
- cojson-storage@0.13.7
|
|
14
|
+
|
|
15
|
+
## 0.13.5
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- Updated dependencies [e090b39]
|
|
20
|
+
- cojson@0.13.5
|
|
21
|
+
- cojson-storage@0.13.5
|
|
22
|
+
|
|
3
23
|
## 0.13.2
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
package/LICENSE.txt
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CoValueCore, SyncMessage } from "cojson";
|
|
2
|
+
export declare function toSimplifiedMessages(coValues: Record<string, CoValueCore>, messages: {
|
|
3
|
+
from: "client" | "server" | "storage";
|
|
4
|
+
msg: SyncMessage;
|
|
5
|
+
}[]): string[];
|
|
6
|
+
export declare function debugMessages(coValues: Record<string, CoValueCore>, messages: {
|
|
7
|
+
from: "client" | "server" | "storage";
|
|
8
|
+
msg: SyncMessage;
|
|
9
|
+
}[]): void;
|
|
10
|
+
//# sourceMappingURL=messagesTestUtils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messagesTestUtils.d.ts","sourceRoot":"","sources":["../../src/tests/messagesTestUtils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAuB,WAAW,EAAE,MAAM,QAAQ,CAAC;AA2B5E,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACrC,QAAQ,EAAE;IACR,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;IACtC,GAAG,EAAE,WAAW,CAAC;CAClB,EAAE,YA6BJ;AAED,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACrC,QAAQ,EAAE;IACR,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;IACtC,GAAG,EAAE,WAAW,CAAC;CAClB,EAAE,QAGJ"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
function simplifySessions(msg) {
|
|
2
|
+
const count = Object.values(msg.sessions).reduce((acc, session) => acc + session, 0);
|
|
3
|
+
if (msg.header) {
|
|
4
|
+
return `header/${count}`;
|
|
5
|
+
}
|
|
6
|
+
return "empty";
|
|
7
|
+
}
|
|
8
|
+
function simplifyNewContent(content) {
|
|
9
|
+
if (!content) {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
return Object.values(content)
|
|
13
|
+
.map((c) => `After: ${c.after} New: ${c.newTransactions.length}`)
|
|
14
|
+
.join(" | ");
|
|
15
|
+
}
|
|
16
|
+
export function toSimplifiedMessages(coValues, messages) {
|
|
17
|
+
function getCoValue(id) {
|
|
18
|
+
for (const [name, coValue] of Object.entries(coValues)) {
|
|
19
|
+
if (coValue.id === id) {
|
|
20
|
+
return name;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return `unknown/${id}`;
|
|
24
|
+
}
|
|
25
|
+
function toDebugString(from, msg) {
|
|
26
|
+
switch (msg.action) {
|
|
27
|
+
case "known":
|
|
28
|
+
return `${from} -> KNOWN ${msg.isCorrection ? "CORRECTION " : ""}${getCoValue(msg.id)} sessions: ${simplifySessions(msg)}`;
|
|
29
|
+
case "load":
|
|
30
|
+
return `${from} -> LOAD ${getCoValue(msg.id)} sessions: ${simplifySessions(msg)}`;
|
|
31
|
+
case "done":
|
|
32
|
+
return `${from} -> DONE ${getCoValue(msg.id)}`;
|
|
33
|
+
case "content":
|
|
34
|
+
return `${from} -> CONTENT ${getCoValue(msg.id)} header: ${Boolean(msg.header)} new: ${simplifyNewContent(msg.new)}`;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return messages.map((m) => toDebugString(m.from, m.msg));
|
|
38
|
+
}
|
|
39
|
+
export function debugMessages(coValues, messages) {
|
|
40
|
+
console.log(toSimplifiedMessages(coValues, messages));
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=messagesTestUtils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messagesTestUtils.js","sourceRoot":"","sources":["../../src/tests/messagesTestUtils.ts"],"names":[],"mappings":"AAEA,SAAS,gBAAgB,CAAC,GAA0C;IAClE,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,MAAM,CAC9C,CAAC,GAAW,EAAE,OAAe,EAAE,EAAE,CAAC,GAAG,GAAG,OAAO,EAC/C,CAAC,CACF,CAAC;IAEF,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;QACf,OAAO,UAAU,KAAK,EAAE,CAAC;IAC3B,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,kBAAkB,CACzB,OAAqD;IAErD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;SAC1B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,eAAe,CAAC,MAAM,EAAE,CAAC;SAChE,IAAI,CAAC,KAAK,CAAC,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,oBAAoB,CAClC,QAAqC,EACrC,QAGG;IAEH,SAAS,UAAU,CAAC,EAAU;QAC5B,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;YACvD,IAAI,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC;gBACtB,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;QAED,OAAO,WAAW,EAAE,EAAE,CAAC;IACzB,CAAC;IAED,SAAS,aAAa,CACpB,IAAqC,EACrC,GAAgB;QAEhB,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC;YACnB,KAAK,OAAO;gBACV,OAAO,GAAG,IAAI,aAAa,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,GAAG,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,cAAc,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC;YAC7H,KAAK,MAAM;gBACT,OAAO,GAAG,IAAI,YAAY,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,cAAc,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC;YACpF,KAAK,MAAM;gBACT,OAAO,GAAG,IAAI,YAAY,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;YACjD,KAAK,SAAS;gBACZ,OAAO,GAAG,IAAI,eAAe,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,YAAY,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,kBAAkB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;QACzH,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,aAAa,CAC3B,QAAqC,EACrC,QAGG;IAEH,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;AACxD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite.test.d.ts","sourceRoot":"","sources":["../../src/tests/sqlite.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { unlinkSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { ControlledAgent, LocalNode } from "cojson";
|
|
6
|
+
import { SyncManager } from "cojson-storage";
|
|
7
|
+
import { WasmCrypto } from "cojson/crypto/WasmCrypto";
|
|
8
|
+
import { expect, onTestFinished, test, vi } from "vitest";
|
|
9
|
+
import { SQLiteNode } from "../index.js";
|
|
10
|
+
import { toSimplifiedMessages } from "./messagesTestUtils.js";
|
|
11
|
+
import { trackMessages } from "./testUtils.js";
|
|
12
|
+
const Crypto = await WasmCrypto.create();
|
|
13
|
+
async function createSQLiteStorage(defaultDbPath) {
|
|
14
|
+
const dbPath = defaultDbPath ?? join(tmpdir(), `test-${randomUUID()}.db`);
|
|
15
|
+
if (!defaultDbPath) {
|
|
16
|
+
onTestFinished(() => {
|
|
17
|
+
unlinkSync(dbPath);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
peer: await SQLiteNode.asPeer({
|
|
22
|
+
filename: dbPath,
|
|
23
|
+
}),
|
|
24
|
+
dbPath,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
test("Should be able to initialize and load from empty DB", async () => {
|
|
28
|
+
const agentSecret = Crypto.newRandomAgentSecret();
|
|
29
|
+
const node = new LocalNode(new ControlledAgent(agentSecret, Crypto), Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)), Crypto);
|
|
30
|
+
node.syncManager.addPeer((await createSQLiteStorage()).peer);
|
|
31
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
32
|
+
expect(node.syncManager.peers.storage).toBeDefined();
|
|
33
|
+
});
|
|
34
|
+
test("should sync and load data from storage", async () => {
|
|
35
|
+
const agentSecret = Crypto.newRandomAgentSecret();
|
|
36
|
+
const node1 = new LocalNode(new ControlledAgent(agentSecret, Crypto), Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)), Crypto);
|
|
37
|
+
const node1Sync = trackMessages(node1);
|
|
38
|
+
const { peer, dbPath } = await createSQLiteStorage();
|
|
39
|
+
node1.syncManager.addPeer(peer);
|
|
40
|
+
const group = node1.createGroup();
|
|
41
|
+
const map = group.createMap();
|
|
42
|
+
map.set("hello", "world");
|
|
43
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
44
|
+
expect(toSimplifiedMessages({
|
|
45
|
+
Map: map.core,
|
|
46
|
+
Group: group.core,
|
|
47
|
+
}, node1Sync.messages)).toMatchInlineSnapshot(`
|
|
48
|
+
[
|
|
49
|
+
"client -> CONTENT Group header: true new: After: 0 New: 3",
|
|
50
|
+
"client -> CONTENT Map header: true new: After: 0 New: 1",
|
|
51
|
+
]
|
|
52
|
+
`);
|
|
53
|
+
node1Sync.restore();
|
|
54
|
+
const node2 = new LocalNode(new ControlledAgent(agentSecret, Crypto), Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)), Crypto);
|
|
55
|
+
const node2Sync = trackMessages(node2);
|
|
56
|
+
const { peer: peer2 } = await createSQLiteStorage(dbPath);
|
|
57
|
+
node2.syncManager.addPeer(peer2);
|
|
58
|
+
const map2 = await node2.load(map.id);
|
|
59
|
+
if (map2 === "unavailable") {
|
|
60
|
+
throw new Error("Map is unavailable");
|
|
61
|
+
}
|
|
62
|
+
expect(map2.get("hello")).toBe("world");
|
|
63
|
+
expect(toSimplifiedMessages({
|
|
64
|
+
Map: map.core,
|
|
65
|
+
Group: group.core,
|
|
66
|
+
}, node2Sync.messages)).toMatchInlineSnapshot(`
|
|
67
|
+
[
|
|
68
|
+
"client -> LOAD Map sessions: empty",
|
|
69
|
+
"storage -> KNOWN Group sessions: header/3",
|
|
70
|
+
"storage -> CONTENT Group header: true new: After: 0 New: 3",
|
|
71
|
+
"client -> KNOWN Group sessions: header/3",
|
|
72
|
+
"storage -> KNOWN Map sessions: header/1",
|
|
73
|
+
"storage -> CONTENT Map header: true new: After: 0 New: 1",
|
|
74
|
+
"client -> KNOWN Map sessions: header/1",
|
|
75
|
+
]
|
|
76
|
+
`);
|
|
77
|
+
node2Sync.restore();
|
|
78
|
+
});
|
|
79
|
+
test("should load dependencies correctly (group inheritance)", async () => {
|
|
80
|
+
const agentSecret = Crypto.newRandomAgentSecret();
|
|
81
|
+
const node1 = new LocalNode(new ControlledAgent(agentSecret, Crypto), Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)), Crypto);
|
|
82
|
+
const node1Sync = trackMessages(node1);
|
|
83
|
+
const { peer, dbPath } = await createSQLiteStorage();
|
|
84
|
+
node1.syncManager.addPeer(peer);
|
|
85
|
+
const group = node1.createGroup();
|
|
86
|
+
const parentGroup = node1.createGroup();
|
|
87
|
+
group.extend(parentGroup);
|
|
88
|
+
const map = group.createMap();
|
|
89
|
+
map.set("hello", "world");
|
|
90
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
91
|
+
expect(toSimplifiedMessages({
|
|
92
|
+
Map: map.core,
|
|
93
|
+
Group: group.core,
|
|
94
|
+
ParentGroup: parentGroup.core,
|
|
95
|
+
}, node1Sync.messages)).toMatchInlineSnapshot(`
|
|
96
|
+
[
|
|
97
|
+
"client -> CONTENT ParentGroup header: true new: After: 0 New: 4",
|
|
98
|
+
"client -> CONTENT Group header: true new: After: 0 New: 5",
|
|
99
|
+
"client -> CONTENT Map header: true new: After: 0 New: 1",
|
|
100
|
+
]
|
|
101
|
+
`);
|
|
102
|
+
node1Sync.restore();
|
|
103
|
+
const node2 = new LocalNode(new ControlledAgent(agentSecret, Crypto), Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)), Crypto);
|
|
104
|
+
const node2Sync = trackMessages(node2);
|
|
105
|
+
const { peer: peer2 } = await createSQLiteStorage(dbPath);
|
|
106
|
+
node2.syncManager.addPeer(peer2);
|
|
107
|
+
await node2.load(map.id);
|
|
108
|
+
expect(node2.expectCoValueLoaded(map.id)).toBeTruthy();
|
|
109
|
+
expect(node2.expectCoValueLoaded(group.id)).toBeTruthy();
|
|
110
|
+
expect(node2.expectCoValueLoaded(parentGroup.id)).toBeTruthy();
|
|
111
|
+
expect(toSimplifiedMessages({
|
|
112
|
+
Map: map.core,
|
|
113
|
+
Group: group.core,
|
|
114
|
+
ParentGroup: parentGroup.core,
|
|
115
|
+
}, node2Sync.messages)).toMatchInlineSnapshot(`
|
|
116
|
+
[
|
|
117
|
+
"client -> LOAD Map sessions: empty",
|
|
118
|
+
"storage -> KNOWN ParentGroup sessions: header/4",
|
|
119
|
+
"storage -> CONTENT ParentGroup header: true new: After: 0 New: 4",
|
|
120
|
+
"client -> KNOWN ParentGroup sessions: header/4",
|
|
121
|
+
"storage -> KNOWN Group sessions: header/5",
|
|
122
|
+
"storage -> CONTENT Group header: true new: After: 0 New: 5",
|
|
123
|
+
"client -> KNOWN Group sessions: header/5",
|
|
124
|
+
"storage -> KNOWN Map sessions: header/1",
|
|
125
|
+
"storage -> CONTENT Map header: true new: After: 0 New: 1",
|
|
126
|
+
"client -> KNOWN Map sessions: header/1",
|
|
127
|
+
]
|
|
128
|
+
`);
|
|
129
|
+
});
|
|
130
|
+
test("should recover from data loss", async () => {
|
|
131
|
+
const agentSecret = Crypto.newRandomAgentSecret();
|
|
132
|
+
const node1 = new LocalNode(new ControlledAgent(agentSecret, Crypto), Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)), Crypto);
|
|
133
|
+
const node1Sync = trackMessages(node1);
|
|
134
|
+
const { peer, dbPath } = await createSQLiteStorage();
|
|
135
|
+
node1.syncManager.addPeer(peer);
|
|
136
|
+
const group = node1.createGroup();
|
|
137
|
+
const map = group.createMap();
|
|
138
|
+
map.set("0", 0);
|
|
139
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
140
|
+
const mock = vi
|
|
141
|
+
.spyOn(SyncManager.prototype, "handleSyncMessage")
|
|
142
|
+
.mockImplementation(() => Promise.resolve());
|
|
143
|
+
map.set("1", 1);
|
|
144
|
+
map.set("2", 2);
|
|
145
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
146
|
+
mock.mockReset();
|
|
147
|
+
map.set("3", 3);
|
|
148
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
149
|
+
expect(toSimplifiedMessages({
|
|
150
|
+
Map: map.core,
|
|
151
|
+
Group: group.core,
|
|
152
|
+
}, node1Sync.messages)).toMatchInlineSnapshot(`
|
|
153
|
+
[
|
|
154
|
+
"client -> CONTENT Group header: true new: After: 0 New: 3",
|
|
155
|
+
"client -> CONTENT Map header: true new: After: 0 New: 1",
|
|
156
|
+
"client -> CONTENT Map header: false new: After: 3 New: 1",
|
|
157
|
+
"storage -> KNOWN CORRECTION Map sessions: header/1",
|
|
158
|
+
"client -> CONTENT Map header: false new: After: 1 New: 3",
|
|
159
|
+
]
|
|
160
|
+
`);
|
|
161
|
+
node1Sync.restore();
|
|
162
|
+
const node2 = new LocalNode(new ControlledAgent(agentSecret, Crypto), Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)), Crypto);
|
|
163
|
+
const node2Sync = trackMessages(node2);
|
|
164
|
+
const { peer: peer2 } = await createSQLiteStorage(dbPath);
|
|
165
|
+
node2.syncManager.addPeer(peer2);
|
|
166
|
+
const map2 = await node2.load(map.id);
|
|
167
|
+
if (map2 === "unavailable") {
|
|
168
|
+
throw new Error("Map is unavailable");
|
|
169
|
+
}
|
|
170
|
+
expect(map2.toJSON()).toEqual({
|
|
171
|
+
"0": 0,
|
|
172
|
+
"1": 1,
|
|
173
|
+
"2": 2,
|
|
174
|
+
"3": 3,
|
|
175
|
+
});
|
|
176
|
+
expect(toSimplifiedMessages({
|
|
177
|
+
Map: map.core,
|
|
178
|
+
Group: group.core,
|
|
179
|
+
}, node2Sync.messages)).toMatchInlineSnapshot(`
|
|
180
|
+
[
|
|
181
|
+
"client -> LOAD Map sessions: empty",
|
|
182
|
+
"storage -> KNOWN Group sessions: header/3",
|
|
183
|
+
"storage -> CONTENT Group header: true new: After: 0 New: 3",
|
|
184
|
+
"client -> KNOWN Group sessions: header/3",
|
|
185
|
+
"storage -> KNOWN Map sessions: header/4",
|
|
186
|
+
"storage -> CONTENT Map header: true new: After: 0 New: 4",
|
|
187
|
+
"client -> KNOWN Map sessions: header/4",
|
|
188
|
+
]
|
|
189
|
+
`);
|
|
190
|
+
});
|
|
191
|
+
//# sourceMappingURL=sqlite.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite.test.js","sourceRoot":"","sources":["../../src/tests/sqlite.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { LocalNode, SyncMessage } from "cojson";
|
|
2
|
+
export declare function trackMessages(node: LocalNode): {
|
|
3
|
+
messages: {
|
|
4
|
+
from: "client" | "server" | "storage";
|
|
5
|
+
msg: SyncMessage;
|
|
6
|
+
}[];
|
|
7
|
+
restore: () => void;
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=testUtils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testUtils.d.ts","sourceRoot":"","sources":["../../src/tests/testUtils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAIrD,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS;;cAEnC,QAAQ,GAAG,QAAQ,GAAG,SAAS;aAChC,WAAW;;;EAmCnB"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { SyncManager } from "cojson-storage";
|
|
2
|
+
import { onTestFinished } from "vitest";
|
|
3
|
+
export function trackMessages(node) {
|
|
4
|
+
const messages = [];
|
|
5
|
+
const originalHandleSyncMessage = SyncManager.prototype.handleSyncMessage;
|
|
6
|
+
const originalNodeSyncMessage = node.syncManager.handleSyncMessage;
|
|
7
|
+
SyncManager.prototype.handleSyncMessage = async function (msg) {
|
|
8
|
+
messages.push({
|
|
9
|
+
from: "client",
|
|
10
|
+
msg,
|
|
11
|
+
});
|
|
12
|
+
return originalHandleSyncMessage.call(this, msg);
|
|
13
|
+
};
|
|
14
|
+
node.syncManager.handleSyncMessage = async function (msg, peer) {
|
|
15
|
+
messages.push({
|
|
16
|
+
from: "storage",
|
|
17
|
+
msg,
|
|
18
|
+
});
|
|
19
|
+
return originalNodeSyncMessage.call(this, msg, peer);
|
|
20
|
+
};
|
|
21
|
+
const restore = () => {
|
|
22
|
+
SyncManager.prototype.handleSyncMessage = originalHandleSyncMessage;
|
|
23
|
+
node.syncManager.handleSyncMessage = originalNodeSyncMessage;
|
|
24
|
+
};
|
|
25
|
+
onTestFinished(() => {
|
|
26
|
+
restore();
|
|
27
|
+
});
|
|
28
|
+
return {
|
|
29
|
+
messages,
|
|
30
|
+
restore,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=testUtils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testUtils.js","sourceRoot":"","sources":["../../src/tests/testUtils.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAExC,MAAM,UAAU,aAAa,CAAC,IAAe;IAC3C,MAAM,QAAQ,GAGR,EAAE,CAAC;IAET,MAAM,yBAAyB,GAAG,WAAW,CAAC,SAAS,CAAC,iBAAiB,CAAC;IAC1E,MAAM,uBAAuB,GAAG,IAAI,CAAC,WAAW,CAAC,iBAAiB,CAAC;IAEnE,WAAW,CAAC,SAAS,CAAC,iBAAiB,GAAG,KAAK,WAAW,GAAG;QAC3D,QAAQ,CAAC,IAAI,CAAC;YACZ,IAAI,EAAE,QAAQ;YACd,GAAG;SACJ,CAAC,CAAC;QACH,OAAO,yBAAyB,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACnD,CAAC,CAAC;IAEF,IAAI,CAAC,WAAW,CAAC,iBAAiB,GAAG,KAAK,WAAW,GAAG,EAAE,IAAI;QAC5D,QAAQ,CAAC,IAAI,CAAC;YACZ,IAAI,EAAE,SAAS;YACf,GAAG;SACJ,CAAC,CAAC;QACH,OAAO,uBAAuB,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IACvD,CAAC,CAAC;IAEF,MAAM,OAAO,GAAG,GAAG,EAAE;QACnB,WAAW,CAAC,SAAS,CAAC,iBAAiB,GAAG,yBAAyB,CAAC;QACpE,IAAI,CAAC,WAAW,CAAC,iBAAiB,GAAG,uBAAuB,CAAC;IAC/D,CAAC,CAAC;IAEF,cAAc,CAAC,GAAG,EAAE;QAClB,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,QAAQ;QACR,OAAO;KACR,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cojson-storage-sqlite",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.13.
|
|
4
|
+
"version": "0.13.7",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"better-sqlite3": "^11.7.0",
|
|
10
|
-
"cojson": "0.13.
|
|
11
|
-
"cojson-storage": "0.13.
|
|
10
|
+
"cojson": "0.13.7",
|
|
11
|
+
"cojson-storage": "0.13.7"
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
14
14
|
"@types/better-sqlite3": "^7.6.12",
|
|
15
|
-
"typescript": "
|
|
15
|
+
"typescript": "5.6.2"
|
|
16
16
|
},
|
|
17
17
|
"gitHead": "33c27053293b4801b968c61d5c4c989f93a67d13",
|
|
18
18
|
"scripts": {
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { CoValueCore, CojsonInternalTypes, SyncMessage } from "cojson";
|
|
2
|
+
|
|
3
|
+
function simplifySessions(msg: CojsonInternalTypes.CoValueKnownState) {
|
|
4
|
+
const count = Object.values(msg.sessions).reduce(
|
|
5
|
+
(acc: number, session: number) => acc + session,
|
|
6
|
+
0,
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
if (msg.header) {
|
|
10
|
+
return `header/${count}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return "empty";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function simplifyNewContent(
|
|
17
|
+
content: CojsonInternalTypes.NewContentMessage["new"],
|
|
18
|
+
) {
|
|
19
|
+
if (!content) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return Object.values(content)
|
|
24
|
+
.map((c) => `After: ${c.after} New: ${c.newTransactions.length}`)
|
|
25
|
+
.join(" | ");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function toSimplifiedMessages(
|
|
29
|
+
coValues: Record<string, CoValueCore>,
|
|
30
|
+
messages: {
|
|
31
|
+
from: "client" | "server" | "storage";
|
|
32
|
+
msg: SyncMessage;
|
|
33
|
+
}[],
|
|
34
|
+
) {
|
|
35
|
+
function getCoValue(id: string) {
|
|
36
|
+
for (const [name, coValue] of Object.entries(coValues)) {
|
|
37
|
+
if (coValue.id === id) {
|
|
38
|
+
return name;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return `unknown/${id}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function toDebugString(
|
|
46
|
+
from: "client" | "server" | "storage",
|
|
47
|
+
msg: SyncMessage,
|
|
48
|
+
) {
|
|
49
|
+
switch (msg.action) {
|
|
50
|
+
case "known":
|
|
51
|
+
return `${from} -> KNOWN ${msg.isCorrection ? "CORRECTION " : ""}${getCoValue(msg.id)} sessions: ${simplifySessions(msg)}`;
|
|
52
|
+
case "load":
|
|
53
|
+
return `${from} -> LOAD ${getCoValue(msg.id)} sessions: ${simplifySessions(msg)}`;
|
|
54
|
+
case "done":
|
|
55
|
+
return `${from} -> DONE ${getCoValue(msg.id)}`;
|
|
56
|
+
case "content":
|
|
57
|
+
return `${from} -> CONTENT ${getCoValue(msg.id)} header: ${Boolean(msg.header)} new: ${simplifyNewContent(msg.new)}`;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return messages.map((m) => toDebugString(m.from, m.msg));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function debugMessages(
|
|
65
|
+
coValues: Record<string, CoValueCore>,
|
|
66
|
+
messages: {
|
|
67
|
+
from: "client" | "server" | "storage";
|
|
68
|
+
msg: SyncMessage;
|
|
69
|
+
}[],
|
|
70
|
+
) {
|
|
71
|
+
console.log(toSimplifiedMessages(coValues, messages));
|
|
72
|
+
}
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { assert } from "node:console";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { unlinkSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { ControlledAgent, LocalNode } from "cojson";
|
|
7
|
+
import { SyncManager } from "cojson-storage";
|
|
8
|
+
import { WasmCrypto } from "cojson/crypto/WasmCrypto";
|
|
9
|
+
import { expect, onTestFinished, test, vi } from "vitest";
|
|
10
|
+
import { SQLiteNode } from "../index.js";
|
|
11
|
+
import { toSimplifiedMessages } from "./messagesTestUtils.js";
|
|
12
|
+
import { trackMessages } from "./testUtils.js";
|
|
13
|
+
|
|
14
|
+
const Crypto = await WasmCrypto.create();
|
|
15
|
+
|
|
16
|
+
async function createSQLiteStorage(defaultDbPath?: string) {
|
|
17
|
+
const dbPath = defaultDbPath ?? join(tmpdir(), `test-${randomUUID()}.db`);
|
|
18
|
+
|
|
19
|
+
if (!defaultDbPath) {
|
|
20
|
+
onTestFinished(() => {
|
|
21
|
+
unlinkSync(dbPath);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
peer: await SQLiteNode.asPeer({
|
|
27
|
+
filename: dbPath,
|
|
28
|
+
}),
|
|
29
|
+
dbPath,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
test("Should be able to initialize and load from empty DB", async () => {
|
|
34
|
+
const agentSecret = Crypto.newRandomAgentSecret();
|
|
35
|
+
|
|
36
|
+
const node = new LocalNode(
|
|
37
|
+
new ControlledAgent(agentSecret, Crypto),
|
|
38
|
+
Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)),
|
|
39
|
+
Crypto,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
node.syncManager.addPeer((await createSQLiteStorage()).peer);
|
|
43
|
+
|
|
44
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
45
|
+
|
|
46
|
+
expect(node.syncManager.peers.storage).toBeDefined();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("should sync and load data from storage", async () => {
|
|
50
|
+
const agentSecret = Crypto.newRandomAgentSecret();
|
|
51
|
+
|
|
52
|
+
const node1 = new LocalNode(
|
|
53
|
+
new ControlledAgent(agentSecret, Crypto),
|
|
54
|
+
Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)),
|
|
55
|
+
Crypto,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const node1Sync = trackMessages(node1);
|
|
59
|
+
|
|
60
|
+
const { peer, dbPath } = await createSQLiteStorage();
|
|
61
|
+
|
|
62
|
+
node1.syncManager.addPeer(peer);
|
|
63
|
+
|
|
64
|
+
const group = node1.createGroup();
|
|
65
|
+
|
|
66
|
+
const map = group.createMap();
|
|
67
|
+
|
|
68
|
+
map.set("hello", "world");
|
|
69
|
+
|
|
70
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
71
|
+
|
|
72
|
+
expect(
|
|
73
|
+
toSimplifiedMessages(
|
|
74
|
+
{
|
|
75
|
+
Map: map.core,
|
|
76
|
+
Group: group.core,
|
|
77
|
+
},
|
|
78
|
+
node1Sync.messages,
|
|
79
|
+
),
|
|
80
|
+
).toMatchInlineSnapshot(`
|
|
81
|
+
[
|
|
82
|
+
"client -> CONTENT Group header: true new: After: 0 New: 3",
|
|
83
|
+
"client -> CONTENT Map header: true new: After: 0 New: 1",
|
|
84
|
+
]
|
|
85
|
+
`);
|
|
86
|
+
|
|
87
|
+
node1Sync.restore();
|
|
88
|
+
|
|
89
|
+
const node2 = new LocalNode(
|
|
90
|
+
new ControlledAgent(agentSecret, Crypto),
|
|
91
|
+
Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)),
|
|
92
|
+
Crypto,
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const node2Sync = trackMessages(node2);
|
|
96
|
+
|
|
97
|
+
const { peer: peer2 } = await createSQLiteStorage(dbPath);
|
|
98
|
+
|
|
99
|
+
node2.syncManager.addPeer(peer2);
|
|
100
|
+
|
|
101
|
+
const map2 = await node2.load(map.id);
|
|
102
|
+
if (map2 === "unavailable") {
|
|
103
|
+
throw new Error("Map is unavailable");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
expect(map2.get("hello")).toBe("world");
|
|
107
|
+
|
|
108
|
+
expect(
|
|
109
|
+
toSimplifiedMessages(
|
|
110
|
+
{
|
|
111
|
+
Map: map.core,
|
|
112
|
+
Group: group.core,
|
|
113
|
+
},
|
|
114
|
+
node2Sync.messages,
|
|
115
|
+
),
|
|
116
|
+
).toMatchInlineSnapshot(`
|
|
117
|
+
[
|
|
118
|
+
"client -> LOAD Map sessions: empty",
|
|
119
|
+
"storage -> KNOWN Group sessions: header/3",
|
|
120
|
+
"storage -> CONTENT Group header: true new: After: 0 New: 3",
|
|
121
|
+
"client -> KNOWN Group sessions: header/3",
|
|
122
|
+
"storage -> KNOWN Map sessions: header/1",
|
|
123
|
+
"storage -> CONTENT Map header: true new: After: 0 New: 1",
|
|
124
|
+
"client -> KNOWN Map sessions: header/1",
|
|
125
|
+
]
|
|
126
|
+
`);
|
|
127
|
+
|
|
128
|
+
node2Sync.restore();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("should load dependencies correctly (group inheritance)", async () => {
|
|
132
|
+
const agentSecret = Crypto.newRandomAgentSecret();
|
|
133
|
+
|
|
134
|
+
const node1 = new LocalNode(
|
|
135
|
+
new ControlledAgent(agentSecret, Crypto),
|
|
136
|
+
Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)),
|
|
137
|
+
Crypto,
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
const node1Sync = trackMessages(node1);
|
|
141
|
+
|
|
142
|
+
const { peer, dbPath } = await createSQLiteStorage();
|
|
143
|
+
|
|
144
|
+
node1.syncManager.addPeer(peer);
|
|
145
|
+
|
|
146
|
+
const group = node1.createGroup();
|
|
147
|
+
const parentGroup = node1.createGroup();
|
|
148
|
+
|
|
149
|
+
group.extend(parentGroup);
|
|
150
|
+
|
|
151
|
+
const map = group.createMap();
|
|
152
|
+
|
|
153
|
+
map.set("hello", "world");
|
|
154
|
+
|
|
155
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
156
|
+
|
|
157
|
+
expect(
|
|
158
|
+
toSimplifiedMessages(
|
|
159
|
+
{
|
|
160
|
+
Map: map.core,
|
|
161
|
+
Group: group.core,
|
|
162
|
+
ParentGroup: parentGroup.core,
|
|
163
|
+
},
|
|
164
|
+
node1Sync.messages,
|
|
165
|
+
),
|
|
166
|
+
).toMatchInlineSnapshot(`
|
|
167
|
+
[
|
|
168
|
+
"client -> CONTENT ParentGroup header: true new: After: 0 New: 4",
|
|
169
|
+
"client -> CONTENT Group header: true new: After: 0 New: 5",
|
|
170
|
+
"client -> CONTENT Map header: true new: After: 0 New: 1",
|
|
171
|
+
]
|
|
172
|
+
`);
|
|
173
|
+
|
|
174
|
+
node1Sync.restore();
|
|
175
|
+
|
|
176
|
+
const node2 = new LocalNode(
|
|
177
|
+
new ControlledAgent(agentSecret, Crypto),
|
|
178
|
+
Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)),
|
|
179
|
+
Crypto,
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const node2Sync = trackMessages(node2);
|
|
183
|
+
|
|
184
|
+
const { peer: peer2 } = await createSQLiteStorage(dbPath);
|
|
185
|
+
|
|
186
|
+
node2.syncManager.addPeer(peer2);
|
|
187
|
+
|
|
188
|
+
await node2.load(map.id);
|
|
189
|
+
|
|
190
|
+
expect(node2.expectCoValueLoaded(map.id)).toBeTruthy();
|
|
191
|
+
expect(node2.expectCoValueLoaded(group.id)).toBeTruthy();
|
|
192
|
+
expect(node2.expectCoValueLoaded(parentGroup.id)).toBeTruthy();
|
|
193
|
+
|
|
194
|
+
expect(
|
|
195
|
+
toSimplifiedMessages(
|
|
196
|
+
{
|
|
197
|
+
Map: map.core,
|
|
198
|
+
Group: group.core,
|
|
199
|
+
ParentGroup: parentGroup.core,
|
|
200
|
+
},
|
|
201
|
+
node2Sync.messages,
|
|
202
|
+
),
|
|
203
|
+
).toMatchInlineSnapshot(`
|
|
204
|
+
[
|
|
205
|
+
"client -> LOAD Map sessions: empty",
|
|
206
|
+
"storage -> KNOWN ParentGroup sessions: header/4",
|
|
207
|
+
"storage -> CONTENT ParentGroup header: true new: After: 0 New: 4",
|
|
208
|
+
"client -> KNOWN ParentGroup sessions: header/4",
|
|
209
|
+
"storage -> KNOWN Group sessions: header/5",
|
|
210
|
+
"storage -> CONTENT Group header: true new: After: 0 New: 5",
|
|
211
|
+
"client -> KNOWN Group sessions: header/5",
|
|
212
|
+
"storage -> KNOWN Map sessions: header/1",
|
|
213
|
+
"storage -> CONTENT Map header: true new: After: 0 New: 1",
|
|
214
|
+
"client -> KNOWN Map sessions: header/1",
|
|
215
|
+
]
|
|
216
|
+
`);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test("should recover from data loss", async () => {
|
|
220
|
+
const agentSecret = Crypto.newRandomAgentSecret();
|
|
221
|
+
|
|
222
|
+
const node1 = new LocalNode(
|
|
223
|
+
new ControlledAgent(agentSecret, Crypto),
|
|
224
|
+
Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)),
|
|
225
|
+
Crypto,
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
const node1Sync = trackMessages(node1);
|
|
229
|
+
|
|
230
|
+
const { peer, dbPath } = await createSQLiteStorage();
|
|
231
|
+
|
|
232
|
+
node1.syncManager.addPeer(peer);
|
|
233
|
+
|
|
234
|
+
const group = node1.createGroup();
|
|
235
|
+
|
|
236
|
+
const map = group.createMap();
|
|
237
|
+
|
|
238
|
+
map.set("0", 0);
|
|
239
|
+
|
|
240
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
241
|
+
|
|
242
|
+
const mock = vi
|
|
243
|
+
.spyOn(SyncManager.prototype, "handleSyncMessage")
|
|
244
|
+
.mockImplementation(() => Promise.resolve());
|
|
245
|
+
|
|
246
|
+
map.set("1", 1);
|
|
247
|
+
map.set("2", 2);
|
|
248
|
+
|
|
249
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
250
|
+
|
|
251
|
+
mock.mockReset();
|
|
252
|
+
|
|
253
|
+
map.set("3", 3);
|
|
254
|
+
|
|
255
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
256
|
+
|
|
257
|
+
expect(
|
|
258
|
+
toSimplifiedMessages(
|
|
259
|
+
{
|
|
260
|
+
Map: map.core,
|
|
261
|
+
Group: group.core,
|
|
262
|
+
},
|
|
263
|
+
node1Sync.messages,
|
|
264
|
+
),
|
|
265
|
+
).toMatchInlineSnapshot(`
|
|
266
|
+
[
|
|
267
|
+
"client -> CONTENT Group header: true new: After: 0 New: 3",
|
|
268
|
+
"client -> CONTENT Map header: true new: After: 0 New: 1",
|
|
269
|
+
"client -> CONTENT Map header: false new: After: 3 New: 1",
|
|
270
|
+
"storage -> KNOWN CORRECTION Map sessions: header/1",
|
|
271
|
+
"client -> CONTENT Map header: false new: After: 1 New: 3",
|
|
272
|
+
]
|
|
273
|
+
`);
|
|
274
|
+
|
|
275
|
+
node1Sync.restore();
|
|
276
|
+
|
|
277
|
+
const node2 = new LocalNode(
|
|
278
|
+
new ControlledAgent(agentSecret, Crypto),
|
|
279
|
+
Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)),
|
|
280
|
+
Crypto,
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
const node2Sync = trackMessages(node2);
|
|
284
|
+
|
|
285
|
+
const { peer: peer2 } = await createSQLiteStorage(dbPath);
|
|
286
|
+
|
|
287
|
+
node2.syncManager.addPeer(peer2);
|
|
288
|
+
|
|
289
|
+
const map2 = await node2.load(map.id);
|
|
290
|
+
|
|
291
|
+
if (map2 === "unavailable") {
|
|
292
|
+
throw new Error("Map is unavailable");
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
expect(map2.toJSON()).toEqual({
|
|
296
|
+
"0": 0,
|
|
297
|
+
"1": 1,
|
|
298
|
+
"2": 2,
|
|
299
|
+
"3": 3,
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
expect(
|
|
303
|
+
toSimplifiedMessages(
|
|
304
|
+
{
|
|
305
|
+
Map: map.core,
|
|
306
|
+
Group: group.core,
|
|
307
|
+
},
|
|
308
|
+
node2Sync.messages,
|
|
309
|
+
),
|
|
310
|
+
).toMatchInlineSnapshot(`
|
|
311
|
+
[
|
|
312
|
+
"client -> LOAD Map sessions: empty",
|
|
313
|
+
"storage -> KNOWN Group sessions: header/3",
|
|
314
|
+
"storage -> CONTENT Group header: true new: After: 0 New: 3",
|
|
315
|
+
"client -> KNOWN Group sessions: header/3",
|
|
316
|
+
"storage -> KNOWN Map sessions: header/4",
|
|
317
|
+
"storage -> CONTENT Map header: true new: After: 0 New: 4",
|
|
318
|
+
"client -> KNOWN Map sessions: header/4",
|
|
319
|
+
]
|
|
320
|
+
`);
|
|
321
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { LocalNode, SyncMessage } from "cojson";
|
|
2
|
+
import { SyncManager } from "cojson-storage";
|
|
3
|
+
import { onTestFinished } from "vitest";
|
|
4
|
+
|
|
5
|
+
export function trackMessages(node: LocalNode) {
|
|
6
|
+
const messages: {
|
|
7
|
+
from: "client" | "server" | "storage";
|
|
8
|
+
msg: SyncMessage;
|
|
9
|
+
}[] = [];
|
|
10
|
+
|
|
11
|
+
const originalHandleSyncMessage = SyncManager.prototype.handleSyncMessage;
|
|
12
|
+
const originalNodeSyncMessage = node.syncManager.handleSyncMessage;
|
|
13
|
+
|
|
14
|
+
SyncManager.prototype.handleSyncMessage = async function (msg) {
|
|
15
|
+
messages.push({
|
|
16
|
+
from: "client",
|
|
17
|
+
msg,
|
|
18
|
+
});
|
|
19
|
+
return originalHandleSyncMessage.call(this, msg);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
node.syncManager.handleSyncMessage = async function (msg, peer) {
|
|
23
|
+
messages.push({
|
|
24
|
+
from: "storage",
|
|
25
|
+
msg,
|
|
26
|
+
});
|
|
27
|
+
return originalNodeSyncMessage.call(this, msg, peer);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const restore = () => {
|
|
31
|
+
SyncManager.prototype.handleSyncMessage = originalHandleSyncMessage;
|
|
32
|
+
node.syncManager.handleSyncMessage = originalNodeSyncMessage;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
onTestFinished(() => {
|
|
36
|
+
restore();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
messages,
|
|
41
|
+
restore,
|
|
42
|
+
};
|
|
43
|
+
}
|