cojson 0.19.22 → 0.20.1

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.
Files changed (223) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +66 -0
  3. package/dist/PeerState.d.ts +6 -1
  4. package/dist/PeerState.d.ts.map +1 -1
  5. package/dist/PeerState.js +18 -3
  6. package/dist/PeerState.js.map +1 -1
  7. package/dist/coValueContentMessage.d.ts +0 -2
  8. package/dist/coValueContentMessage.d.ts.map +1 -1
  9. package/dist/coValueContentMessage.js +0 -8
  10. package/dist/coValueContentMessage.js.map +1 -1
  11. package/dist/coValueCore/SessionMap.d.ts +4 -2
  12. package/dist/coValueCore/SessionMap.d.ts.map +1 -1
  13. package/dist/coValueCore/SessionMap.js +30 -0
  14. package/dist/coValueCore/SessionMap.js.map +1 -1
  15. package/dist/coValueCore/coValueCore.d.ts +70 -5
  16. package/dist/coValueCore/coValueCore.d.ts.map +1 -1
  17. package/dist/coValueCore/coValueCore.js +302 -31
  18. package/dist/coValueCore/coValueCore.js.map +1 -1
  19. package/dist/coValueCore/verifiedState.d.ts +6 -1
  20. package/dist/coValueCore/verifiedState.d.ts.map +1 -1
  21. package/dist/coValueCore/verifiedState.js +9 -0
  22. package/dist/coValueCore/verifiedState.js.map +1 -1
  23. package/dist/coValues/coList.d.ts +4 -2
  24. package/dist/coValues/coList.d.ts.map +1 -1
  25. package/dist/coValues/coList.js +3 -0
  26. package/dist/coValues/coList.js.map +1 -1
  27. package/dist/coValues/group.d.ts.map +1 -1
  28. package/dist/coValues/group.js +3 -6
  29. package/dist/coValues/group.js.map +1 -1
  30. package/dist/config.d.ts +2 -8
  31. package/dist/config.d.ts.map +1 -1
  32. package/dist/config.js +4 -12
  33. package/dist/config.js.map +1 -1
  34. package/dist/crypto/NapiCrypto.d.ts +1 -2
  35. package/dist/crypto/NapiCrypto.d.ts.map +1 -1
  36. package/dist/crypto/NapiCrypto.js +19 -4
  37. package/dist/crypto/NapiCrypto.js.map +1 -1
  38. package/dist/crypto/RNCrypto.d.ts.map +1 -1
  39. package/dist/crypto/RNCrypto.js +19 -4
  40. package/dist/crypto/RNCrypto.js.map +1 -1
  41. package/dist/crypto/WasmCrypto.d.ts +11 -4
  42. package/dist/crypto/WasmCrypto.d.ts.map +1 -1
  43. package/dist/crypto/WasmCrypto.js +52 -10
  44. package/dist/crypto/WasmCrypto.js.map +1 -1
  45. package/dist/crypto/WasmCryptoEdge.d.ts +1 -0
  46. package/dist/crypto/WasmCryptoEdge.d.ts.map +1 -1
  47. package/dist/crypto/WasmCryptoEdge.js +4 -1
  48. package/dist/crypto/WasmCryptoEdge.js.map +1 -1
  49. package/dist/crypto/crypto.d.ts +3 -3
  50. package/dist/crypto/crypto.d.ts.map +1 -1
  51. package/dist/crypto/crypto.js +6 -1
  52. package/dist/crypto/crypto.js.map +1 -1
  53. package/dist/exports.d.ts +5 -5
  54. package/dist/exports.d.ts.map +1 -1
  55. package/dist/exports.js +4 -3
  56. package/dist/exports.js.map +1 -1
  57. package/dist/ids.d.ts +4 -1
  58. package/dist/ids.d.ts.map +1 -1
  59. package/dist/ids.js +4 -0
  60. package/dist/ids.js.map +1 -1
  61. package/dist/knownState.d.ts +2 -0
  62. package/dist/knownState.d.ts.map +1 -1
  63. package/dist/localNode.d.ts +12 -0
  64. package/dist/localNode.d.ts.map +1 -1
  65. package/dist/localNode.js +14 -0
  66. package/dist/localNode.js.map +1 -1
  67. package/dist/platformUtils.d.ts +3 -0
  68. package/dist/platformUtils.d.ts.map +1 -0
  69. package/dist/platformUtils.js +24 -0
  70. package/dist/platformUtils.js.map +1 -0
  71. package/dist/queue/LinkedList.d.ts +9 -3
  72. package/dist/queue/LinkedList.d.ts.map +1 -1
  73. package/dist/queue/LinkedList.js +30 -1
  74. package/dist/queue/LinkedList.js.map +1 -1
  75. package/dist/queue/OutgoingLoadQueue.d.ts +95 -0
  76. package/dist/queue/OutgoingLoadQueue.d.ts.map +1 -0
  77. package/dist/queue/OutgoingLoadQueue.js +240 -0
  78. package/dist/queue/OutgoingLoadQueue.js.map +1 -0
  79. package/dist/storage/DeletedCoValuesEraserScheduler.d.ts +30 -0
  80. package/dist/storage/DeletedCoValuesEraserScheduler.d.ts.map +1 -0
  81. package/dist/storage/DeletedCoValuesEraserScheduler.js +84 -0
  82. package/dist/storage/DeletedCoValuesEraserScheduler.js.map +1 -0
  83. package/dist/storage/sqlite/client.d.ts +3 -0
  84. package/dist/storage/sqlite/client.d.ts.map +1 -1
  85. package/dist/storage/sqlite/client.js +44 -0
  86. package/dist/storage/sqlite/client.js.map +1 -1
  87. package/dist/storage/sqlite/sqliteMigrations.d.ts.map +1 -1
  88. package/dist/storage/sqlite/sqliteMigrations.js +7 -0
  89. package/dist/storage/sqlite/sqliteMigrations.js.map +1 -1
  90. package/dist/storage/sqliteAsync/client.d.ts +3 -0
  91. package/dist/storage/sqliteAsync/client.d.ts.map +1 -1
  92. package/dist/storage/sqliteAsync/client.js +42 -0
  93. package/dist/storage/sqliteAsync/client.js.map +1 -1
  94. package/dist/storage/storageAsync.d.ts +7 -0
  95. package/dist/storage/storageAsync.d.ts.map +1 -1
  96. package/dist/storage/storageAsync.js +48 -0
  97. package/dist/storage/storageAsync.js.map +1 -1
  98. package/dist/storage/storageSync.d.ts +6 -0
  99. package/dist/storage/storageSync.d.ts.map +1 -1
  100. package/dist/storage/storageSync.js +42 -0
  101. package/dist/storage/storageSync.js.map +1 -1
  102. package/dist/storage/types.d.ts +59 -0
  103. package/dist/storage/types.d.ts.map +1 -1
  104. package/dist/storage/types.js +12 -1
  105. package/dist/storage/types.js.map +1 -1
  106. package/dist/sync.d.ts.map +1 -1
  107. package/dist/sync.js +66 -43
  108. package/dist/sync.js.map +1 -1
  109. package/dist/tests/DeletedCoValuesEraserScheduler.test.d.ts +2 -0
  110. package/dist/tests/DeletedCoValuesEraserScheduler.test.d.ts.map +1 -0
  111. package/dist/tests/DeletedCoValuesEraserScheduler.test.js +149 -0
  112. package/dist/tests/DeletedCoValuesEraserScheduler.test.js.map +1 -0
  113. package/dist/tests/GarbageCollector.test.js +5 -6
  114. package/dist/tests/GarbageCollector.test.js.map +1 -1
  115. package/dist/tests/LinkedList.test.js +90 -0
  116. package/dist/tests/LinkedList.test.js.map +1 -1
  117. package/dist/tests/OutgoingLoadQueue.test.d.ts +2 -0
  118. package/dist/tests/OutgoingLoadQueue.test.d.ts.map +1 -0
  119. package/dist/tests/OutgoingLoadQueue.test.js +814 -0
  120. package/dist/tests/OutgoingLoadQueue.test.js.map +1 -0
  121. package/dist/tests/StorageApiAsync.test.js +484 -152
  122. package/dist/tests/StorageApiAsync.test.js.map +1 -1
  123. package/dist/tests/StorageApiSync.test.js +505 -136
  124. package/dist/tests/StorageApiSync.test.js.map +1 -1
  125. package/dist/tests/WasmCrypto.test.js +6 -3
  126. package/dist/tests/WasmCrypto.test.js.map +1 -1
  127. package/dist/tests/coValueCore.loadFromStorage.test.js +3 -0
  128. package/dist/tests/coValueCore.loadFromStorage.test.js.map +1 -1
  129. package/dist/tests/coValueCore.test.js +34 -13
  130. package/dist/tests/coValueCore.test.js.map +1 -1
  131. package/dist/tests/coreWasm.test.js +127 -4
  132. package/dist/tests/coreWasm.test.js.map +1 -1
  133. package/dist/tests/crypto.test.js +89 -93
  134. package/dist/tests/crypto.test.js.map +1 -1
  135. package/dist/tests/deleteCoValue.test.d.ts +2 -0
  136. package/dist/tests/deleteCoValue.test.d.ts.map +1 -0
  137. package/dist/tests/deleteCoValue.test.js +313 -0
  138. package/dist/tests/deleteCoValue.test.js.map +1 -0
  139. package/dist/tests/group.removeMember.test.js +18 -30
  140. package/dist/tests/group.removeMember.test.js.map +1 -1
  141. package/dist/tests/knownState.lazyLoading.test.js +3 -0
  142. package/dist/tests/knownState.lazyLoading.test.js.map +1 -1
  143. package/dist/tests/sync.concurrentLoad.test.d.ts +2 -0
  144. package/dist/tests/sync.concurrentLoad.test.d.ts.map +1 -0
  145. package/dist/tests/sync.concurrentLoad.test.js +481 -0
  146. package/dist/tests/sync.concurrentLoad.test.js.map +1 -0
  147. package/dist/tests/sync.deleted.test.d.ts +2 -0
  148. package/dist/tests/sync.deleted.test.d.ts.map +1 -0
  149. package/dist/tests/sync.deleted.test.js +214 -0
  150. package/dist/tests/sync.deleted.test.js.map +1 -0
  151. package/dist/tests/sync.mesh.test.js +3 -2
  152. package/dist/tests/sync.mesh.test.js.map +1 -1
  153. package/dist/tests/sync.storage.test.js +4 -3
  154. package/dist/tests/sync.storage.test.js.map +1 -1
  155. package/dist/tests/sync.test.js +3 -2
  156. package/dist/tests/sync.test.js.map +1 -1
  157. package/dist/tests/testStorage.d.ts +3 -0
  158. package/dist/tests/testStorage.d.ts.map +1 -1
  159. package/dist/tests/testStorage.js +17 -1
  160. package/dist/tests/testStorage.js.map +1 -1
  161. package/dist/tests/testUtils.d.ts +7 -3
  162. package/dist/tests/testUtils.d.ts.map +1 -1
  163. package/dist/tests/testUtils.js +19 -4
  164. package/dist/tests/testUtils.js.map +1 -1
  165. package/package.json +6 -16
  166. package/src/PeerState.ts +26 -3
  167. package/src/coValueContentMessage.ts +0 -14
  168. package/src/coValueCore/SessionMap.ts +43 -1
  169. package/src/coValueCore/coValueCore.ts +415 -27
  170. package/src/coValueCore/verifiedState.ts +26 -3
  171. package/src/coValues/coList.ts +9 -3
  172. package/src/coValues/group.ts +5 -6
  173. package/src/config.ts +4 -13
  174. package/src/crypto/NapiCrypto.ts +29 -13
  175. package/src/crypto/RNCrypto.ts +29 -11
  176. package/src/crypto/WasmCrypto.ts +67 -20
  177. package/src/crypto/WasmCryptoEdge.ts +5 -1
  178. package/src/crypto/crypto.ts +16 -4
  179. package/src/exports.ts +4 -2
  180. package/src/ids.ts +11 -1
  181. package/src/localNode.ts +15 -0
  182. package/src/platformUtils.ts +26 -0
  183. package/src/queue/LinkedList.ts +34 -4
  184. package/src/queue/OutgoingLoadQueue.ts +307 -0
  185. package/src/storage/DeletedCoValuesEraserScheduler.ts +124 -0
  186. package/src/storage/sqlite/client.ts +77 -0
  187. package/src/storage/sqlite/sqliteMigrations.ts +7 -0
  188. package/src/storage/sqliteAsync/client.ts +75 -0
  189. package/src/storage/storageAsync.ts +62 -0
  190. package/src/storage/storageSync.ts +58 -0
  191. package/src/storage/types.ts +69 -0
  192. package/src/sync.ts +78 -46
  193. package/src/tests/DeletedCoValuesEraserScheduler.test.ts +185 -0
  194. package/src/tests/GarbageCollector.test.ts +6 -10
  195. package/src/tests/LinkedList.test.ts +111 -0
  196. package/src/tests/OutgoingLoadQueue.test.ts +1129 -0
  197. package/src/tests/StorageApiAsync.test.ts +572 -162
  198. package/src/tests/StorageApiSync.test.ts +580 -143
  199. package/src/tests/WasmCrypto.test.ts +8 -3
  200. package/src/tests/coValueCore.loadFromStorage.test.ts +6 -0
  201. package/src/tests/coValueCore.test.ts +49 -14
  202. package/src/tests/coreWasm.test.ts +319 -10
  203. package/src/tests/crypto.test.ts +141 -150
  204. package/src/tests/deleteCoValue.test.ts +528 -0
  205. package/src/tests/group.removeMember.test.ts +35 -35
  206. package/src/tests/knownState.lazyLoading.test.ts +6 -0
  207. package/src/tests/sync.concurrentLoad.test.ts +650 -0
  208. package/src/tests/sync.deleted.test.ts +294 -0
  209. package/src/tests/sync.mesh.test.ts +5 -2
  210. package/src/tests/sync.storage.test.ts +6 -3
  211. package/src/tests/sync.test.ts +5 -2
  212. package/src/tests/testStorage.ts +31 -2
  213. package/src/tests/testUtils.ts +31 -10
  214. package/dist/crypto/PureJSCrypto.d.ts +0 -77
  215. package/dist/crypto/PureJSCrypto.d.ts.map +0 -1
  216. package/dist/crypto/PureJSCrypto.js +0 -236
  217. package/dist/crypto/PureJSCrypto.js.map +0 -1
  218. package/dist/tests/PureJSCrypto.test.d.ts +0 -2
  219. package/dist/tests/PureJSCrypto.test.d.ts.map +0 -1
  220. package/dist/tests/PureJSCrypto.test.js +0 -145
  221. package/dist/tests/PureJSCrypto.test.js.map +0 -1
  222. package/src/crypto/PureJSCrypto.ts +0 -429
  223. 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 { stableStringify } from "../jsonStringify";
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: stableStringify([{ op: "set", key: "hello", value: "updated" }]),
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 { stableStringify } from "../jsonStringify";
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: stableStringify([{ op: "set", key: "hello", value: "updated" }]),
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");
@@ -1021,9 +1024,9 @@ describe("client syncs with a server with storage", () => {
1021
1024
  "server -> bob | CONTENT Map header: true new: After: 0 New: 1",
1022
1025
  "storage -> syncServer | CONTENT ParentGroup header: true new: After: 76 New: 73",
1023
1026
  "server -> bob | CONTENT ParentGroup header: false new: After: 76 New: 73 expectContentUntil: header/205",
1024
- "bob -> server | KNOWN ParentGroup sessions: header/76",
1025
1027
  "storage -> syncServer | CONTENT ParentGroup header: true new: After: 149 New: 56",
1026
1028
  "server -> bob | CONTENT ParentGroup header: false new: After: 149 New: 56",
1029
+ "bob -> server | KNOWN ParentGroup sessions: header/76",
1027
1030
  "bob -> server | KNOWN Group sessions: header/5",
1028
1031
  "bob -> server | KNOWN Map sessions: header/1",
1029
1032
  "bob -> server | KNOWN ParentGroup sessions: header/149",
@@ -18,7 +18,8 @@ import {
18
18
  tearDownTestMetricReader,
19
19
  waitFor,
20
20
  } from "./testUtils.js";
21
- import { stableStringify } from "../jsonStringify.js";
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: stableStringify([{ op: "set", key: "hello", value: "world" }]),
172
+ changes: JSON.stringify([
173
+ { op: "set", key: "hello", value: "world" },
174
+ ]) as Stringified<JsonValue[]>,
172
175
  madeAt: Date.now(),
173
176
  },
174
177
  ],
@@ -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,12 +132,40 @@ 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
 
137
164
  if (!defaultDbPath) {
138
165
  onTestFinished(() => {
139
- unlinkSync(dbPath);
166
+ setTimeout(() => {
167
+ unlinkSync(dbPath);
168
+ }, 100);
140
169
  });
141
170
  }
142
171
 
@@ -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
 
@@ -271,6 +268,7 @@ export function blockMessageTypeOnOutgoingPeer(
271
268
  opts: {
272
269
  id?: string;
273
270
  once?: boolean;
271
+ matcher?: (msg: SyncMessage) => boolean;
274
272
  },
275
273
  ) {
276
274
  const push = peer.outgoing.push;
@@ -283,7 +281,8 @@ export function blockMessageTypeOnOutgoingPeer(
283
281
  typeof msg === "object" &&
284
282
  msg.action === messageType &&
285
283
  (!opts.id || msg.id === opts.id) &&
286
- (!opts.once || !blockedIds.has(msg.id))
284
+ (!opts.once || !blockedIds.has(msg.id)) &&
285
+ (!opts.matcher || opts.matcher(msg))
287
286
  ) {
288
287
  blockedMessages.push(msg);
289
288
  blockedIds.add(msg.id);
@@ -503,11 +502,11 @@ export function setupTestNode(
503
502
  }
504
503
 
505
504
  async function addAsyncStorage(
506
- opts: { ourName?: string; filename?: string } = {},
505
+ opts: { ourName?: string; filename?: string; storageName?: string } = {},
507
506
  ) {
508
507
  const storage = await createAsyncStorage({
509
508
  nodeName: opts.ourName ?? "client",
510
- storageName: "storage",
509
+ storageName: opts.storageName ?? "storage",
511
510
  filename: opts.filename,
512
511
  });
513
512
  node.setStorage(storage);
@@ -640,10 +639,12 @@ export async function setupTestAccount(
640
639
  return { storage };
641
640
  }
642
641
 
643
- async function addAsyncStorage(opts: { ourName?: string } = {}) {
642
+ async function addAsyncStorage(
643
+ opts: { ourName?: string; storageName?: string } = {},
644
+ ) {
644
645
  const storage = await createAsyncStorage({
645
646
  nodeName: opts.ourName ?? "client",
646
- storageName: "storage",
647
+ storageName: opts.storageName ?? "storage",
647
648
  });
648
649
  ctx.node.setStorage(storage);
649
650
 
@@ -658,9 +659,14 @@ export async function setupTestAccount(
658
659
  await ctx.node.gracefulShutdown();
659
660
  });
660
661
 
662
+ const account = ctx.node
663
+ .getCoValue(ctx.accountID)
664
+ .getCurrentContent() as RawAccount;
665
+
661
666
  return {
662
667
  node: ctx.node,
663
668
  accountID: ctx.accountID,
669
+ account,
664
670
  connectToSyncServer,
665
671
  addStorage,
666
672
  addAsyncStorage,
@@ -814,6 +820,21 @@ export function fillCoMapWithLargeData(map: RawCoMap) {
814
820
  return map;
815
821
  }
816
822
 
823
+ export function importContentIntoNode(
824
+ coValue: CoValueCore,
825
+ node: LocalNode,
826
+ chunks?: number,
827
+ ) {
828
+ const content = coValue.newContentSince(undefined);
829
+ assert(content);
830
+ for (const [i, chunk] of content.entries()) {
831
+ if (chunks && i >= chunks) {
832
+ break;
833
+ }
834
+ node.syncManager.handleNewContent(chunk, "import");
835
+ }
836
+ }
837
+
817
838
  // ============================================================================
818
839
  // MessageChannel Test Helpers
819
840
  // ============================================================================
@@ -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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"}