cojson 0.19.22 → 0.20.0
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 +54 -0
- package/dist/coValueContentMessage.d.ts +0 -2
- package/dist/coValueContentMessage.d.ts.map +1 -1
- package/dist/coValueContentMessage.js +0 -8
- package/dist/coValueContentMessage.js.map +1 -1
- package/dist/coValueCore/SessionMap.d.ts +4 -2
- package/dist/coValueCore/SessionMap.d.ts.map +1 -1
- package/dist/coValueCore/SessionMap.js +30 -0
- package/dist/coValueCore/SessionMap.js.map +1 -1
- package/dist/coValueCore/coValueCore.d.ts +67 -3
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +289 -12
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/coValueCore/verifiedState.d.ts +6 -1
- package/dist/coValueCore/verifiedState.d.ts.map +1 -1
- package/dist/coValueCore/verifiedState.js +9 -0
- package/dist/coValueCore/verifiedState.js.map +1 -1
- package/dist/coValues/coList.d.ts +3 -2
- package/dist/coValues/coList.d.ts.map +1 -1
- package/dist/coValues/coList.js.map +1 -1
- package/dist/coValues/group.d.ts.map +1 -1
- package/dist/coValues/group.js +3 -6
- package/dist/coValues/group.js.map +1 -1
- package/dist/config.d.ts +0 -6
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -8
- package/dist/config.js.map +1 -1
- package/dist/crypto/NapiCrypto.d.ts +1 -2
- package/dist/crypto/NapiCrypto.d.ts.map +1 -1
- package/dist/crypto/NapiCrypto.js +19 -4
- package/dist/crypto/NapiCrypto.js.map +1 -1
- package/dist/crypto/RNCrypto.d.ts.map +1 -1
- package/dist/crypto/RNCrypto.js +19 -4
- package/dist/crypto/RNCrypto.js.map +1 -1
- package/dist/crypto/WasmCrypto.d.ts +11 -4
- package/dist/crypto/WasmCrypto.d.ts.map +1 -1
- package/dist/crypto/WasmCrypto.js +52 -10
- package/dist/crypto/WasmCrypto.js.map +1 -1
- package/dist/crypto/WasmCryptoEdge.d.ts +1 -0
- package/dist/crypto/WasmCryptoEdge.d.ts.map +1 -1
- package/dist/crypto/WasmCryptoEdge.js +4 -1
- package/dist/crypto/WasmCryptoEdge.js.map +1 -1
- package/dist/crypto/crypto.d.ts +3 -3
- package/dist/crypto/crypto.d.ts.map +1 -1
- package/dist/crypto/crypto.js +6 -1
- package/dist/crypto/crypto.js.map +1 -1
- package/dist/exports.d.ts +2 -2
- package/dist/exports.d.ts.map +1 -1
- package/dist/exports.js +2 -1
- package/dist/exports.js.map +1 -1
- package/dist/ids.d.ts +4 -1
- package/dist/ids.d.ts.map +1 -1
- package/dist/ids.js +4 -0
- package/dist/ids.js.map +1 -1
- package/dist/knownState.d.ts +2 -0
- package/dist/knownState.d.ts.map +1 -1
- package/dist/localNode.d.ts +12 -0
- package/dist/localNode.d.ts.map +1 -1
- package/dist/localNode.js +14 -0
- package/dist/localNode.js.map +1 -1
- package/dist/platformUtils.d.ts +3 -0
- package/dist/platformUtils.d.ts.map +1 -0
- package/dist/platformUtils.js +24 -0
- package/dist/platformUtils.js.map +1 -0
- package/dist/storage/DeletedCoValuesEraserScheduler.d.ts +30 -0
- package/dist/storage/DeletedCoValuesEraserScheduler.d.ts.map +1 -0
- package/dist/storage/DeletedCoValuesEraserScheduler.js +84 -0
- package/dist/storage/DeletedCoValuesEraserScheduler.js.map +1 -0
- package/dist/storage/sqlite/client.d.ts +3 -0
- package/dist/storage/sqlite/client.d.ts.map +1 -1
- package/dist/storage/sqlite/client.js +44 -0
- package/dist/storage/sqlite/client.js.map +1 -1
- package/dist/storage/sqlite/sqliteMigrations.d.ts.map +1 -1
- package/dist/storage/sqlite/sqliteMigrations.js +7 -0
- package/dist/storage/sqlite/sqliteMigrations.js.map +1 -1
- package/dist/storage/sqliteAsync/client.d.ts +3 -0
- package/dist/storage/sqliteAsync/client.d.ts.map +1 -1
- package/dist/storage/sqliteAsync/client.js +42 -0
- package/dist/storage/sqliteAsync/client.js.map +1 -1
- package/dist/storage/storageAsync.d.ts +7 -0
- package/dist/storage/storageAsync.d.ts.map +1 -1
- package/dist/storage/storageAsync.js +48 -0
- package/dist/storage/storageAsync.js.map +1 -1
- package/dist/storage/storageSync.d.ts +6 -0
- package/dist/storage/storageSync.d.ts.map +1 -1
- package/dist/storage/storageSync.js +42 -0
- package/dist/storage/storageSync.js.map +1 -1
- package/dist/storage/types.d.ts +59 -0
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/storage/types.js +12 -1
- package/dist/storage/types.js.map +1 -1
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +44 -11
- package/dist/sync.js.map +1 -1
- package/dist/tests/DeletedCoValuesEraserScheduler.test.d.ts +2 -0
- package/dist/tests/DeletedCoValuesEraserScheduler.test.d.ts.map +1 -0
- package/dist/tests/DeletedCoValuesEraserScheduler.test.js +149 -0
- package/dist/tests/DeletedCoValuesEraserScheduler.test.js.map +1 -0
- package/dist/tests/GarbageCollector.test.js +5 -6
- package/dist/tests/GarbageCollector.test.js.map +1 -1
- package/dist/tests/StorageApiAsync.test.js +484 -152
- package/dist/tests/StorageApiAsync.test.js.map +1 -1
- package/dist/tests/StorageApiSync.test.js +505 -136
- package/dist/tests/StorageApiSync.test.js.map +1 -1
- package/dist/tests/WasmCrypto.test.js +6 -3
- package/dist/tests/WasmCrypto.test.js.map +1 -1
- package/dist/tests/coValueCore.loadFromStorage.test.js +3 -0
- package/dist/tests/coValueCore.loadFromStorage.test.js.map +1 -1
- package/dist/tests/coValueCore.test.js +34 -13
- package/dist/tests/coValueCore.test.js.map +1 -1
- package/dist/tests/coreWasm.test.js +127 -4
- package/dist/tests/coreWasm.test.js.map +1 -1
- package/dist/tests/crypto.test.js +89 -93
- package/dist/tests/crypto.test.js.map +1 -1
- package/dist/tests/deleteCoValue.test.d.ts +2 -0
- package/dist/tests/deleteCoValue.test.d.ts.map +1 -0
- package/dist/tests/deleteCoValue.test.js +313 -0
- package/dist/tests/deleteCoValue.test.js.map +1 -0
- package/dist/tests/group.removeMember.test.js +18 -30
- package/dist/tests/group.removeMember.test.js.map +1 -1
- package/dist/tests/knownState.lazyLoading.test.js +3 -0
- package/dist/tests/knownState.lazyLoading.test.js.map +1 -1
- package/dist/tests/sync.deleted.test.d.ts +2 -0
- package/dist/tests/sync.deleted.test.d.ts.map +1 -0
- package/dist/tests/sync.deleted.test.js +214 -0
- package/dist/tests/sync.deleted.test.js.map +1 -0
- package/dist/tests/sync.mesh.test.js +3 -2
- package/dist/tests/sync.mesh.test.js.map +1 -1
- package/dist/tests/sync.storage.test.js +3 -2
- package/dist/tests/sync.storage.test.js.map +1 -1
- package/dist/tests/sync.test.js +3 -2
- package/dist/tests/sync.test.js.map +1 -1
- package/dist/tests/testStorage.d.ts +3 -0
- package/dist/tests/testStorage.d.ts.map +1 -1
- package/dist/tests/testStorage.js +14 -0
- package/dist/tests/testStorage.js.map +1 -1
- package/dist/tests/testUtils.d.ts +6 -3
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +17 -3
- package/dist/tests/testUtils.js.map +1 -1
- package/package.json +6 -16
- package/src/coValueContentMessage.ts +0 -14
- package/src/coValueCore/SessionMap.ts +43 -1
- package/src/coValueCore/coValueCore.ts +400 -8
- package/src/coValueCore/verifiedState.ts +26 -3
- package/src/coValues/coList.ts +5 -3
- package/src/coValues/group.ts +5 -6
- package/src/config.ts +0 -9
- package/src/crypto/NapiCrypto.ts +29 -13
- package/src/crypto/RNCrypto.ts +29 -11
- package/src/crypto/WasmCrypto.ts +67 -20
- package/src/crypto/WasmCryptoEdge.ts +5 -1
- package/src/crypto/crypto.ts +16 -4
- package/src/exports.ts +2 -0
- package/src/ids.ts +11 -1
- package/src/localNode.ts +15 -0
- package/src/platformUtils.ts +26 -0
- package/src/storage/DeletedCoValuesEraserScheduler.ts +124 -0
- package/src/storage/sqlite/client.ts +77 -0
- package/src/storage/sqlite/sqliteMigrations.ts +7 -0
- package/src/storage/sqliteAsync/client.ts +75 -0
- package/src/storage/storageAsync.ts +62 -0
- package/src/storage/storageSync.ts +58 -0
- package/src/storage/types.ts +69 -0
- package/src/sync.ts +51 -11
- package/src/tests/DeletedCoValuesEraserScheduler.test.ts +185 -0
- package/src/tests/GarbageCollector.test.ts +6 -10
- package/src/tests/StorageApiAsync.test.ts +572 -162
- package/src/tests/StorageApiSync.test.ts +580 -143
- package/src/tests/WasmCrypto.test.ts +8 -3
- package/src/tests/coValueCore.loadFromStorage.test.ts +6 -0
- package/src/tests/coValueCore.test.ts +49 -14
- package/src/tests/coreWasm.test.ts +319 -10
- package/src/tests/crypto.test.ts +141 -150
- package/src/tests/deleteCoValue.test.ts +528 -0
- package/src/tests/group.removeMember.test.ts +35 -35
- package/src/tests/knownState.lazyLoading.test.ts +6 -0
- package/src/tests/sync.deleted.test.ts +294 -0
- package/src/tests/sync.mesh.test.ts +5 -2
- package/src/tests/sync.storage.test.ts +5 -2
- package/src/tests/sync.test.ts +5 -2
- package/src/tests/testStorage.ts +28 -1
- package/src/tests/testUtils.ts +28 -9
- package/dist/crypto/PureJSCrypto.d.ts +0 -77
- package/dist/crypto/PureJSCrypto.d.ts.map +0 -1
- package/dist/crypto/PureJSCrypto.js +0 -236
- package/dist/crypto/PureJSCrypto.js.map +0 -1
- package/dist/tests/PureJSCrypto.test.d.ts +0 -2
- package/dist/tests/PureJSCrypto.test.d.ts.map +0 -1
- package/dist/tests/PureJSCrypto.test.js +0 -145
- package/dist/tests/PureJSCrypto.test.js.map +0 -1
- package/src/crypto/PureJSCrypto.ts +0 -429
- package/src/tests/PureJSCrypto.test.ts +0 -217
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { assert, beforeEach, describe, expect, test } from "vitest";
|
|
2
|
+
import { expectMap } from "../coValue";
|
|
3
|
+
import {
|
|
4
|
+
SyncMessagesLog,
|
|
5
|
+
TEST_NODE_CONFIG,
|
|
6
|
+
loadCoValueOrFail,
|
|
7
|
+
setupTestAccount,
|
|
8
|
+
setupTestNode,
|
|
9
|
+
waitFor,
|
|
10
|
+
} from "./testUtils";
|
|
11
|
+
import { isDeleteSessionID, SessionID } from "../ids";
|
|
12
|
+
|
|
13
|
+
let jazzCloud: ReturnType<typeof setupTestNode>;
|
|
14
|
+
|
|
15
|
+
beforeEach(async () => {
|
|
16
|
+
// We want to simulate a real world communication that happens asynchronously
|
|
17
|
+
TEST_NODE_CONFIG.withAsyncPeers = true;
|
|
18
|
+
|
|
19
|
+
SyncMessagesLog.clear();
|
|
20
|
+
jazzCloud = setupTestNode({ isSyncServer: true });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe("syncing deleted coValues", () => {
|
|
24
|
+
test("client loads a deleted coValue from server (tombstone-only)", async () => {
|
|
25
|
+
const { node: client } = setupTestNode({ connected: true });
|
|
26
|
+
|
|
27
|
+
const group = jazzCloud.node.createGroup();
|
|
28
|
+
const map = group.createMap();
|
|
29
|
+
map.set("hello", "world", "trusting");
|
|
30
|
+
|
|
31
|
+
// Delete on the server before the client loads.
|
|
32
|
+
map.core.deleteCoValue();
|
|
33
|
+
expect(map.core.isDeleted).toBe(true);
|
|
34
|
+
|
|
35
|
+
const mapOnClient = await loadCoValueOrFail(client, map.id);
|
|
36
|
+
const mapCoreOnClient = client.expectCoValueLoaded(map.id);
|
|
37
|
+
|
|
38
|
+
expect(mapCoreOnClient.isDeleted).toBe(true);
|
|
39
|
+
// Historical content should not be synced.
|
|
40
|
+
expect(mapOnClient.get("hello")).toBeUndefined();
|
|
41
|
+
|
|
42
|
+
expect(
|
|
43
|
+
SyncMessagesLog.getMessages({
|
|
44
|
+
Group: group.core,
|
|
45
|
+
Map: map.core,
|
|
46
|
+
}),
|
|
47
|
+
).toMatchInlineSnapshot(`
|
|
48
|
+
[
|
|
49
|
+
"client -> server | LOAD Map sessions: empty",
|
|
50
|
+
"server -> client | CONTENT Group header: true new: After: 0 New: 3",
|
|
51
|
+
"server -> client | CONTENT Map header: true new: After: 0 New: 1",
|
|
52
|
+
"client -> server | KNOWN Group sessions: header/3",
|
|
53
|
+
"client -> server | KNOWN Map sessions: header/1",
|
|
54
|
+
]
|
|
55
|
+
`);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("inbound filtering: after deletion, non-delete sessions in the same content message are ignored", async () => {
|
|
59
|
+
const client = setupTestNode({ connected: false });
|
|
60
|
+
|
|
61
|
+
const group = jazzCloud.node.createGroup();
|
|
62
|
+
const map = group.createMap();
|
|
63
|
+
map.set("k", "v", "trusting");
|
|
64
|
+
|
|
65
|
+
const contentBeforeDelete = map.core.newContentSince(undefined)?.[0];
|
|
66
|
+
assert(contentBeforeDelete);
|
|
67
|
+
|
|
68
|
+
// Create a delete marker on the server, but also keep a historical session around.
|
|
69
|
+
map.core.deleteCoValue();
|
|
70
|
+
|
|
71
|
+
const content = map.core.newContentSince(undefined)?.[0];
|
|
72
|
+
assert(content);
|
|
73
|
+
|
|
74
|
+
const groupContent = group.core.newContentSince(undefined)?.[0];
|
|
75
|
+
assert(groupContent);
|
|
76
|
+
|
|
77
|
+
// We merge the content before delete with the content after delete to simulate an older peer that might send extra sessions in the same message
|
|
78
|
+
Object.assign(contentBeforeDelete.new, content.new);
|
|
79
|
+
|
|
80
|
+
client.node.syncManager.handleNewContent(groupContent, "import");
|
|
81
|
+
client.node.syncManager.handleNewContent(content, "import");
|
|
82
|
+
|
|
83
|
+
const coreOnClient = client.node.expectCoValueLoaded(map.id);
|
|
84
|
+
expect(coreOnClient.isDeleted).toBe(true);
|
|
85
|
+
|
|
86
|
+
const contentOnClient = expectMap(coreOnClient.getCurrentContent());
|
|
87
|
+
expect(contentOnClient.get("k")).toBeUndefined();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("should wait for the dependencies to be available before processing the deleted session/transaction", async () => {
|
|
91
|
+
const client = setupTestNode({ connected: false });
|
|
92
|
+
|
|
93
|
+
const group = jazzCloud.node.createGroup();
|
|
94
|
+
const map = group.createMap();
|
|
95
|
+
|
|
96
|
+
// Create a delete marker on the server, but also keep a historical session around.
|
|
97
|
+
map.core.deleteCoValue();
|
|
98
|
+
|
|
99
|
+
const content = map.core.newContentSince(undefined)?.[0];
|
|
100
|
+
assert(content);
|
|
101
|
+
|
|
102
|
+
const groupContent = group.core.newContentSince(undefined)?.[0];
|
|
103
|
+
assert(groupContent);
|
|
104
|
+
|
|
105
|
+
client.node.syncManager.handleNewContent(content, "import");
|
|
106
|
+
client.node.syncManager.handleNewContent(groupContent, "import");
|
|
107
|
+
|
|
108
|
+
await waitFor(() => {
|
|
109
|
+
expect(client.node.expectCoValueLoaded(map.id).isDeleted).toBe(true);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("outbound blocking: post-delete normal writes are ignored and do not produce content uploads", async () => {
|
|
114
|
+
const client = setupTestNode({ connected: true });
|
|
115
|
+
|
|
116
|
+
const group = jazzCloud.node.createGroup();
|
|
117
|
+
group.addMember("everyone", "writer");
|
|
118
|
+
const map = group.createMap();
|
|
119
|
+
map.set("a", 1, "trusting");
|
|
120
|
+
|
|
121
|
+
const mapOnClient = await loadCoValueOrFail(client.node, map.id);
|
|
122
|
+
|
|
123
|
+
// Delete on the server and wait for it to propagate.
|
|
124
|
+
map.core.deleteCoValue();
|
|
125
|
+
await waitFor(() => {
|
|
126
|
+
expect(mapOnClient.core.isDeleted).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
SyncMessagesLog.clear();
|
|
130
|
+
|
|
131
|
+
mapOnClient.set("x", "y", "trusting");
|
|
132
|
+
|
|
133
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
134
|
+
|
|
135
|
+
// Ensure we didn't produce outgoing content uploads as a result of the rejected write.
|
|
136
|
+
const messages = SyncMessagesLog.getMessages({
|
|
137
|
+
Group: group.core,
|
|
138
|
+
Map: map.core,
|
|
139
|
+
});
|
|
140
|
+
expect(messages.some((m) => m.includes("CONTENT Map"))).toBe(false);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("delete should be propagated to client-to-client sync", async () => {
|
|
144
|
+
const alice = setupTestNode();
|
|
145
|
+
alice.connectToSyncServer({
|
|
146
|
+
ourName: "alice",
|
|
147
|
+
});
|
|
148
|
+
const bob = setupTestNode();
|
|
149
|
+
bob.connectToSyncServer({
|
|
150
|
+
ourName: "bob",
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const group = alice.node.createGroup();
|
|
154
|
+
const map = group.createMap();
|
|
155
|
+
map.set("hello", "world", "trusting");
|
|
156
|
+
map.core.deleteCoValue();
|
|
157
|
+
|
|
158
|
+
await loadCoValueOrFail(bob.node, map.id);
|
|
159
|
+
|
|
160
|
+
await waitFor(() => {
|
|
161
|
+
expect(bob.node.expectCoValueLoaded(map.id).isDeleted).toBe(true);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
expect(
|
|
165
|
+
SyncMessagesLog.getMessages({
|
|
166
|
+
Group: group.core,
|
|
167
|
+
Map: map.core,
|
|
168
|
+
}),
|
|
169
|
+
).toMatchInlineSnapshot(`
|
|
170
|
+
[
|
|
171
|
+
"bob -> server | LOAD Map sessions: empty",
|
|
172
|
+
"alice -> server | CONTENT Group header: true new: After: 0 New: 3",
|
|
173
|
+
"alice -> server | CONTENT Map header: true new: After: 0 New: 1",
|
|
174
|
+
"server -> bob | KNOWN Map sessions: empty",
|
|
175
|
+
"server -> alice | KNOWN Group sessions: header/3",
|
|
176
|
+
"server -> alice | KNOWN Map sessions: header/1",
|
|
177
|
+
"server -> bob | CONTENT Group header: true new: After: 0 New: 3",
|
|
178
|
+
"server -> bob | CONTENT Map header: true new: After: 0 New: 1",
|
|
179
|
+
"bob -> server | KNOWN Group sessions: header/3",
|
|
180
|
+
"bob -> server | KNOWN Map sessions: header/1",
|
|
181
|
+
]
|
|
182
|
+
`);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test("content synced after deletion should be ignored", async () => {
|
|
186
|
+
const alice = setupTestNode({ connected: true });
|
|
187
|
+
const bob = setupTestNode();
|
|
188
|
+
|
|
189
|
+
const { peerState: bobConnection } = bob.connectToSyncServer({
|
|
190
|
+
ourName: "bob",
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const group = alice.node.createGroup();
|
|
194
|
+
group.addMember("everyone", "writer");
|
|
195
|
+
const map = group.createMap();
|
|
196
|
+
map.set("hello", "world", "trusting");
|
|
197
|
+
|
|
198
|
+
const mapOnBob = await loadCoValueOrFail(bob.node, map.id);
|
|
199
|
+
|
|
200
|
+
SyncMessagesLog.clear();
|
|
201
|
+
|
|
202
|
+
bobConnection.gracefulShutdown();
|
|
203
|
+
|
|
204
|
+
map.core.deleteCoValue();
|
|
205
|
+
|
|
206
|
+
await map.core.waitForSync();
|
|
207
|
+
|
|
208
|
+
mapOnBob.set("hello", "updated", "trusting");
|
|
209
|
+
|
|
210
|
+
bob.connectToSyncServer({
|
|
211
|
+
ourName: "bob",
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
await mapOnBob.core.waitForSync();
|
|
215
|
+
|
|
216
|
+
expect(
|
|
217
|
+
SyncMessagesLog.getMessages({
|
|
218
|
+
Group: group.core,
|
|
219
|
+
Map: map.core,
|
|
220
|
+
}),
|
|
221
|
+
).toMatchInlineSnapshot(`
|
|
222
|
+
[
|
|
223
|
+
"client -> server | CONTENT Map header: false new: After: 0 New: 1",
|
|
224
|
+
"server -> client | KNOWN Map sessions: header/2",
|
|
225
|
+
"server -> bob | CONTENT Map header: false new: After: 0 New: 1",
|
|
226
|
+
"bob -> server | LOAD Group sessions: header/5",
|
|
227
|
+
"bob -> server | LOAD Map sessions: header/2",
|
|
228
|
+
"bob -> server | CONTENT Map header: false new: After: 0 New: 1",
|
|
229
|
+
"server -> bob | KNOWN Group sessions: header/5",
|
|
230
|
+
"server -> bob | CONTENT Map header: false new: After: 0 New: 1",
|
|
231
|
+
"server -> bob | KNOWN Map sessions: header/3",
|
|
232
|
+
"bob -> server | KNOWN Map sessions: header/2",
|
|
233
|
+
]
|
|
234
|
+
`);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test("should handle concurrent delete operations", async () => {
|
|
238
|
+
const alice = await setupTestAccount();
|
|
239
|
+
alice.connectToSyncServer({
|
|
240
|
+
ourName: "alice",
|
|
241
|
+
});
|
|
242
|
+
const bob = await setupTestAccount();
|
|
243
|
+
bob.connectToSyncServer({
|
|
244
|
+
ourName: "bob",
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const group = jazzCloud.node.createGroup();
|
|
248
|
+
group.addMemberInternal(alice.account, "admin");
|
|
249
|
+
group.addMemberInternal(bob.account, "admin");
|
|
250
|
+
|
|
251
|
+
const map = group.createMap();
|
|
252
|
+
map.set("counter", 0, "trusting");
|
|
253
|
+
map.set("counter", 1, "trusting");
|
|
254
|
+
|
|
255
|
+
const mapOnAlice = await loadCoValueOrFail(alice.node, map.id);
|
|
256
|
+
const mapOnBob = await loadCoValueOrFail(bob.node, map.id);
|
|
257
|
+
|
|
258
|
+
SyncMessagesLog.clear();
|
|
259
|
+
|
|
260
|
+
mapOnAlice.core.deleteCoValue();
|
|
261
|
+
mapOnBob.core.deleteCoValue();
|
|
262
|
+
|
|
263
|
+
await mapOnAlice.core.waitForSync();
|
|
264
|
+
await mapOnBob.core.waitForSync();
|
|
265
|
+
|
|
266
|
+
expect(
|
|
267
|
+
SyncMessagesLog.getMessages({
|
|
268
|
+
Group: group.core,
|
|
269
|
+
Map: map.core,
|
|
270
|
+
}),
|
|
271
|
+
).toMatchInlineSnapshot(`
|
|
272
|
+
[
|
|
273
|
+
"alice -> server | CONTENT Map header: false new: After: 0 New: 1",
|
|
274
|
+
"bob -> server | CONTENT Map header: false new: After: 0 New: 1",
|
|
275
|
+
"server -> alice | KNOWN Map sessions: header/3",
|
|
276
|
+
"server -> bob | CONTENT Map header: false new: After: 0 New: 1",
|
|
277
|
+
"server -> bob | KNOWN Map sessions: header/4",
|
|
278
|
+
"server -> alice | CONTENT Map header: false new: After: 0 New: 1",
|
|
279
|
+
"bob -> server | KNOWN Map sessions: header/4",
|
|
280
|
+
]
|
|
281
|
+
`);
|
|
282
|
+
|
|
283
|
+
expect(map.core.isDeleted).toBe(true);
|
|
284
|
+
|
|
285
|
+
const sessions = map.core.knownState().sessions;
|
|
286
|
+
|
|
287
|
+
expect(Object.keys(sessions)).toHaveLength(2);
|
|
288
|
+
expect(
|
|
289
|
+
Object.keys(sessions).every((sessionID) =>
|
|
290
|
+
isDeleteSessionID(sessionID as SessionID),
|
|
291
|
+
),
|
|
292
|
+
).toBe(true);
|
|
293
|
+
});
|
|
294
|
+
});
|
|
@@ -11,7 +11,8 @@ import {
|
|
|
11
11
|
setupTestNode,
|
|
12
12
|
waitFor,
|
|
13
13
|
} from "./testUtils";
|
|
14
|
-
import {
|
|
14
|
+
import { Stringified } from "../jsonStringify";
|
|
15
|
+
import { JsonValue } from "../jsonValue";
|
|
15
16
|
|
|
16
17
|
// We want to simulate a real world communication that happens asynchronously
|
|
17
18
|
TEST_NODE_CONFIG.withAsyncPeers = true;
|
|
@@ -325,7 +326,9 @@ describe("multiple clients syncing with the a cloud-like server mesh", () => {
|
|
|
325
326
|
|
|
326
327
|
msg.new[mesh.edgeFrance.node.currentSessionID]!.newTransactions.push({
|
|
327
328
|
privacy: "trusting",
|
|
328
|
-
changes:
|
|
329
|
+
changes: JSON.stringify([
|
|
330
|
+
{ op: "set", key: "hello", value: "updated" },
|
|
331
|
+
]) as Stringified<JsonValue[]>,
|
|
329
332
|
madeAt: Date.now(),
|
|
330
333
|
});
|
|
331
334
|
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
vi,
|
|
9
9
|
} from "vitest";
|
|
10
10
|
|
|
11
|
+
import type { JsonValue } from "../exports";
|
|
11
12
|
import { cojsonInternals, emptyKnownState } from "../exports";
|
|
12
13
|
import {
|
|
13
14
|
SyncMessagesLog,
|
|
@@ -19,7 +20,7 @@ import {
|
|
|
19
20
|
tearDownTestMetricReader,
|
|
20
21
|
waitFor,
|
|
21
22
|
} from "./testUtils";
|
|
22
|
-
import {
|
|
23
|
+
import { Stringified } from "../jsonStringify";
|
|
23
24
|
|
|
24
25
|
// We want to simulate a real world communication that happens asynchronously
|
|
25
26
|
TEST_NODE_CONFIG.withAsyncPeers = true;
|
|
@@ -572,7 +573,9 @@ describe("client syncs with a server with storage", () => {
|
|
|
572
573
|
const invalidMapContent = structuredClone(mapContent);
|
|
573
574
|
invalidMapContent.new[bob.node.currentSessionID]!.newTransactions.push({
|
|
574
575
|
privacy: "trusting",
|
|
575
|
-
changes:
|
|
576
|
+
changes: JSON.stringify([
|
|
577
|
+
{ op: "set", key: "hello", value: "updated" },
|
|
578
|
+
]) as Stringified<JsonValue[]>,
|
|
576
579
|
madeAt: Date.now(),
|
|
577
580
|
});
|
|
578
581
|
client.node.syncManager.handleNewContent(invalidMapContent, "import");
|
package/src/tests/sync.test.ts
CHANGED
|
@@ -18,7 +18,8 @@ import {
|
|
|
18
18
|
tearDownTestMetricReader,
|
|
19
19
|
waitFor,
|
|
20
20
|
} from "./testUtils.js";
|
|
21
|
-
import {
|
|
21
|
+
import { Stringified } from "../jsonStringify.js";
|
|
22
|
+
import { JsonValue } from "../jsonValue.js";
|
|
22
23
|
|
|
23
24
|
// We want to simulate a real world communication that happens asynchronously
|
|
24
25
|
TEST_NODE_CONFIG.withAsyncPeers = true;
|
|
@@ -168,7 +169,9 @@ test("should not verify transactions when SyncManager has verification disabled"
|
|
|
168
169
|
[
|
|
169
170
|
{
|
|
170
171
|
privacy: "trusting",
|
|
171
|
-
changes:
|
|
172
|
+
changes: JSON.stringify([
|
|
173
|
+
{ op: "set", key: "hello", value: "world" },
|
|
174
|
+
]) as Stringified<JsonValue[]>,
|
|
172
175
|
madeAt: Date.now(),
|
|
173
176
|
},
|
|
174
177
|
],
|
package/src/tests/testStorage.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { tmpdir } from "node:os";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import Database, { type Database as DatabaseT } from "libsql";
|
|
6
6
|
import { onTestFinished } from "vitest";
|
|
7
|
-
import { RawCoID, StorageAPI } from "../exports";
|
|
7
|
+
import { RawCoID, SessionID, StorageAPI } from "../exports";
|
|
8
8
|
import { SQLiteDatabaseDriver } from "../storage";
|
|
9
9
|
import { getSqliteStorage } from "../storage/sqlite";
|
|
10
10
|
import {
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
getSqliteStorageAsync,
|
|
13
13
|
} from "../storage/sqliteAsync";
|
|
14
14
|
import { SyncMessagesLog, SyncTestMessage } from "./testUtils";
|
|
15
|
+
import { knownStateFromContent } from "../coValueContentMessage";
|
|
15
16
|
|
|
16
17
|
class LibSQLSqliteAsyncDriver implements SQLiteDatabaseDriverAsync {
|
|
17
18
|
private readonly db: DatabaseT;
|
|
@@ -131,6 +132,32 @@ export function createSyncStorage({
|
|
|
131
132
|
return storage;
|
|
132
133
|
}
|
|
133
134
|
|
|
135
|
+
export async function getAllCoValuesWaitingForDelete(
|
|
136
|
+
storage: StorageAPI,
|
|
137
|
+
): Promise<RawCoID[]> {
|
|
138
|
+
// @ts-expect-error - dbClient is private
|
|
139
|
+
return storage.dbClient.getAllCoValuesWaitingForDelete();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export async function getCoValueStoredSessions(
|
|
143
|
+
storage: StorageAPI,
|
|
144
|
+
id: RawCoID,
|
|
145
|
+
): Promise<SessionID[]> {
|
|
146
|
+
return new Promise<SessionID[]>((resolve) => {
|
|
147
|
+
storage.load(
|
|
148
|
+
id,
|
|
149
|
+
(content) => {
|
|
150
|
+
if (content.id === id) {
|
|
151
|
+
resolve(
|
|
152
|
+
Object.keys(knownStateFromContent(content).sessions) as SessionID[],
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
() => {},
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
134
161
|
export function getDbPath(defaultDbPath?: string) {
|
|
135
162
|
const dbPath = defaultDbPath ?? join(tmpdir(), `test-${randomUUID()}.db`);
|
|
136
163
|
|
package/src/tests/testUtils.ts
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
MeterProvider,
|
|
6
6
|
MetricReader,
|
|
7
7
|
} from "@opentelemetry/sdk-metrics";
|
|
8
|
-
import { expect, onTestFinished, vi } from "vitest";
|
|
8
|
+
import { assert, expect, onTestFinished, vi } from "vitest";
|
|
9
9
|
import { ControlledAccount, ControlledAgent } from "../coValues/account.js";
|
|
10
10
|
import { WasmCrypto } from "../crypto/WasmCrypto.js";
|
|
11
11
|
import {
|
|
@@ -28,15 +28,12 @@ import type { Peer, SyncMessage, SyncWhen } from "../sync.js";
|
|
|
28
28
|
import { expectGroup } from "../typeUtils/expectGroup.js";
|
|
29
29
|
import { toSimplifiedMessages } from "./messagesTestUtils.js";
|
|
30
30
|
import { createAsyncStorage, createSyncStorage } from "./testStorage.js";
|
|
31
|
-
import { PureJSCrypto } from "../crypto/PureJSCrypto.js";
|
|
32
31
|
import { CoValueHeader } from "../coValueCore/verifiedState.js";
|
|
33
32
|
import { idforHeader } from "../coValueCore/coValueCore.js";
|
|
34
33
|
|
|
35
34
|
let Crypto = await WasmCrypto.create();
|
|
36
35
|
|
|
37
|
-
export function setCurrentTestCryptoProvider(
|
|
38
|
-
crypto: WasmCrypto | PureJSCrypto,
|
|
39
|
-
) {
|
|
36
|
+
export function setCurrentTestCryptoProvider(crypto: WasmCrypto) {
|
|
40
37
|
Crypto = crypto;
|
|
41
38
|
}
|
|
42
39
|
|
|
@@ -503,11 +500,11 @@ export function setupTestNode(
|
|
|
503
500
|
}
|
|
504
501
|
|
|
505
502
|
async function addAsyncStorage(
|
|
506
|
-
opts: { ourName?: string; filename?: string } = {},
|
|
503
|
+
opts: { ourName?: string; filename?: string; storageName?: string } = {},
|
|
507
504
|
) {
|
|
508
505
|
const storage = await createAsyncStorage({
|
|
509
506
|
nodeName: opts.ourName ?? "client",
|
|
510
|
-
storageName: "storage",
|
|
507
|
+
storageName: opts.storageName ?? "storage",
|
|
511
508
|
filename: opts.filename,
|
|
512
509
|
});
|
|
513
510
|
node.setStorage(storage);
|
|
@@ -640,10 +637,12 @@ export async function setupTestAccount(
|
|
|
640
637
|
return { storage };
|
|
641
638
|
}
|
|
642
639
|
|
|
643
|
-
async function addAsyncStorage(
|
|
640
|
+
async function addAsyncStorage(
|
|
641
|
+
opts: { ourName?: string; storageName?: string } = {},
|
|
642
|
+
) {
|
|
644
643
|
const storage = await createAsyncStorage({
|
|
645
644
|
nodeName: opts.ourName ?? "client",
|
|
646
|
-
storageName: "storage",
|
|
645
|
+
storageName: opts.storageName ?? "storage",
|
|
647
646
|
});
|
|
648
647
|
ctx.node.setStorage(storage);
|
|
649
648
|
|
|
@@ -658,9 +657,14 @@ export async function setupTestAccount(
|
|
|
658
657
|
await ctx.node.gracefulShutdown();
|
|
659
658
|
});
|
|
660
659
|
|
|
660
|
+
const account = ctx.node
|
|
661
|
+
.getCoValue(ctx.accountID)
|
|
662
|
+
.getCurrentContent() as RawAccount;
|
|
663
|
+
|
|
661
664
|
return {
|
|
662
665
|
node: ctx.node,
|
|
663
666
|
accountID: ctx.accountID,
|
|
667
|
+
account,
|
|
664
668
|
connectToSyncServer,
|
|
665
669
|
addStorage,
|
|
666
670
|
addAsyncStorage,
|
|
@@ -814,6 +818,21 @@ export function fillCoMapWithLargeData(map: RawCoMap) {
|
|
|
814
818
|
return map;
|
|
815
819
|
}
|
|
816
820
|
|
|
821
|
+
export function importContentIntoNode(
|
|
822
|
+
coValue: CoValueCore,
|
|
823
|
+
node: LocalNode,
|
|
824
|
+
chunks?: number,
|
|
825
|
+
) {
|
|
826
|
+
const content = coValue.newContentSince(undefined);
|
|
827
|
+
assert(content);
|
|
828
|
+
for (const [i, chunk] of content.entries()) {
|
|
829
|
+
if (chunks && i >= chunks) {
|
|
830
|
+
break;
|
|
831
|
+
}
|
|
832
|
+
node.syncManager.handleNewContent(chunk, "import");
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
817
836
|
// ============================================================================
|
|
818
837
|
// MessageChannel Test Helpers
|
|
819
838
|
// ============================================================================
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { PrivateTransaction, Transaction, TrustingTransaction } from "../coValueCore/verifiedState.js";
|
|
2
|
-
import { RawCoID, SessionID, TransactionID } from "../ids.js";
|
|
3
|
-
import { Stringified } from "../jsonStringify.js";
|
|
4
|
-
import { JsonObject, JsonValue } from "../jsonValue.js";
|
|
5
|
-
import { CryptoProvider, Encrypted, KeyID, KeySecret, Sealed, SealerID, SealerSecret, SessionLogImpl, Signature, SignerID, SignerSecret } from "./crypto.js";
|
|
6
|
-
import { ControlledAccountOrAgent } from "../coValues/account.js";
|
|
7
|
-
export type Blake3State = {
|
|
8
|
-
update: (buf: Uint8Array) => Blake3State;
|
|
9
|
-
digest: () => Uint8Array;
|
|
10
|
-
clone: () => Blake3State;
|
|
11
|
-
};
|
|
12
|
-
/**
|
|
13
|
-
* Pure JavaScript implementation of the CryptoProvider interface using noble-curves and noble-ciphers libraries.
|
|
14
|
-
* This provides a fallback implementation that doesn't require WebAssembly, offering:
|
|
15
|
-
* - Signing/verifying (Ed25519)
|
|
16
|
-
* - Encryption/decryption (XSalsa20)
|
|
17
|
-
* - Sealing/unsealing (X25519 + XSalsa20-Poly1305)
|
|
18
|
-
* - Hashing (BLAKE3)
|
|
19
|
-
*/
|
|
20
|
-
export declare class PureJSCrypto extends CryptoProvider<Blake3State> {
|
|
21
|
-
static create(): Promise<PureJSCrypto>;
|
|
22
|
-
createStreamingHash(): Blake3State;
|
|
23
|
-
blake3HashOnce(data: Uint8Array): Uint8Array;
|
|
24
|
-
blake3HashOnceWithContext(data: Uint8Array, { context }: {
|
|
25
|
-
context: Uint8Array;
|
|
26
|
-
}): Uint8Array;
|
|
27
|
-
generateNonce(input: Uint8Array): Uint8Array;
|
|
28
|
-
generateJsonNonce(material: JsonValue): Uint8Array;
|
|
29
|
-
newEd25519SigningKey(): Uint8Array;
|
|
30
|
-
getSignerID(secret: SignerSecret): SignerID;
|
|
31
|
-
sign(secret: SignerSecret, message: JsonValue): Signature;
|
|
32
|
-
verify(signature: Signature, message: JsonValue, id: SignerID): boolean;
|
|
33
|
-
newX25519StaticSecret(): Uint8Array;
|
|
34
|
-
getSealerID(secret: SealerSecret): SealerID;
|
|
35
|
-
encrypt<T extends JsonValue, N extends JsonValue>(value: T, keySecret: KeySecret, nOnceMaterial: N): Encrypted<T, N>;
|
|
36
|
-
decryptRaw<T extends JsonValue, N extends JsonValue>(encrypted: Encrypted<T, N>, keySecret: KeySecret, nOnceMaterial: N): Stringified<T>;
|
|
37
|
-
seal<T extends JsonValue>({ message, from, to, nOnceMaterial, }: {
|
|
38
|
-
message: T;
|
|
39
|
-
from: SealerSecret;
|
|
40
|
-
to: SealerID;
|
|
41
|
-
nOnceMaterial: {
|
|
42
|
-
in: RawCoID;
|
|
43
|
-
tx: TransactionID;
|
|
44
|
-
};
|
|
45
|
-
}): Sealed<T>;
|
|
46
|
-
unseal<T extends JsonValue>(sealed: Sealed<T>, sealer: SealerSecret, from: SealerID, nOnceMaterial: {
|
|
47
|
-
in: RawCoID;
|
|
48
|
-
tx: TransactionID;
|
|
49
|
-
}): T | undefined;
|
|
50
|
-
createSessionLog(coID: RawCoID, sessionID: SessionID, signerID?: SignerID): SessionLogImpl;
|
|
51
|
-
}
|
|
52
|
-
export declare class PureJSSessionLog implements SessionLogImpl {
|
|
53
|
-
private readonly coID;
|
|
54
|
-
private readonly sessionID;
|
|
55
|
-
private readonly signerID;
|
|
56
|
-
private readonly crypto;
|
|
57
|
-
transactions: string[];
|
|
58
|
-
lastSignature: Signature | undefined;
|
|
59
|
-
streamingHash: Blake3State;
|
|
60
|
-
constructor(coID: RawCoID, sessionID: SessionID, signerID: SignerID | undefined, crypto: PureJSCrypto);
|
|
61
|
-
clone(): SessionLogImpl;
|
|
62
|
-
tryAdd(transactions: Transaction[], newSignature: Signature, skipVerify: boolean): void;
|
|
63
|
-
internalTryAdd(transactions: string[], newSignature: Signature, skipVerify: boolean): `signature_z${string}`;
|
|
64
|
-
internalAddNewTransaction(transaction: string, signerAgent: ControlledAccountOrAgent): `signature_z${string}`;
|
|
65
|
-
addNewPrivateTransaction(signerAgent: ControlledAccountOrAgent, changes: JsonValue[], keyID: KeyID, keySecret: KeySecret, madeAt: number, meta: JsonObject | undefined): {
|
|
66
|
-
signature: Signature;
|
|
67
|
-
transaction: PrivateTransaction;
|
|
68
|
-
};
|
|
69
|
-
addNewTrustingTransaction(signerAgent: ControlledAccountOrAgent, changes: JsonValue[], madeAt: number, meta: JsonObject | undefined): {
|
|
70
|
-
signature: Signature;
|
|
71
|
-
transaction: TrustingTransaction;
|
|
72
|
-
};
|
|
73
|
-
decryptNextTransactionChangesJson(txIndex: number, keySecret: KeySecret): string;
|
|
74
|
-
decryptNextTransactionMetaJson(txIndex: number, keySecret: KeySecret): string | undefined;
|
|
75
|
-
free(): void;
|
|
76
|
-
}
|
|
77
|
-
//# sourceMappingURL=PureJSCrypto.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PureJSCrypto.d.ts","sourceRoot":"","sources":["../../src/crypto/PureJSCrypto.ts"],"names":[],"mappings":"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"}
|