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.
Files changed (194) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +54 -0
  3. package/dist/coValueContentMessage.d.ts +0 -2
  4. package/dist/coValueContentMessage.d.ts.map +1 -1
  5. package/dist/coValueContentMessage.js +0 -8
  6. package/dist/coValueContentMessage.js.map +1 -1
  7. package/dist/coValueCore/SessionMap.d.ts +4 -2
  8. package/dist/coValueCore/SessionMap.d.ts.map +1 -1
  9. package/dist/coValueCore/SessionMap.js +30 -0
  10. package/dist/coValueCore/SessionMap.js.map +1 -1
  11. package/dist/coValueCore/coValueCore.d.ts +67 -3
  12. package/dist/coValueCore/coValueCore.d.ts.map +1 -1
  13. package/dist/coValueCore/coValueCore.js +289 -12
  14. package/dist/coValueCore/coValueCore.js.map +1 -1
  15. package/dist/coValueCore/verifiedState.d.ts +6 -1
  16. package/dist/coValueCore/verifiedState.d.ts.map +1 -1
  17. package/dist/coValueCore/verifiedState.js +9 -0
  18. package/dist/coValueCore/verifiedState.js.map +1 -1
  19. package/dist/coValues/coList.d.ts +3 -2
  20. package/dist/coValues/coList.d.ts.map +1 -1
  21. package/dist/coValues/coList.js.map +1 -1
  22. package/dist/coValues/group.d.ts.map +1 -1
  23. package/dist/coValues/group.js +3 -6
  24. package/dist/coValues/group.js.map +1 -1
  25. package/dist/config.d.ts +0 -6
  26. package/dist/config.d.ts.map +1 -1
  27. package/dist/config.js +0 -8
  28. package/dist/config.js.map +1 -1
  29. package/dist/crypto/NapiCrypto.d.ts +1 -2
  30. package/dist/crypto/NapiCrypto.d.ts.map +1 -1
  31. package/dist/crypto/NapiCrypto.js +19 -4
  32. package/dist/crypto/NapiCrypto.js.map +1 -1
  33. package/dist/crypto/RNCrypto.d.ts.map +1 -1
  34. package/dist/crypto/RNCrypto.js +19 -4
  35. package/dist/crypto/RNCrypto.js.map +1 -1
  36. package/dist/crypto/WasmCrypto.d.ts +11 -4
  37. package/dist/crypto/WasmCrypto.d.ts.map +1 -1
  38. package/dist/crypto/WasmCrypto.js +52 -10
  39. package/dist/crypto/WasmCrypto.js.map +1 -1
  40. package/dist/crypto/WasmCryptoEdge.d.ts +1 -0
  41. package/dist/crypto/WasmCryptoEdge.d.ts.map +1 -1
  42. package/dist/crypto/WasmCryptoEdge.js +4 -1
  43. package/dist/crypto/WasmCryptoEdge.js.map +1 -1
  44. package/dist/crypto/crypto.d.ts +3 -3
  45. package/dist/crypto/crypto.d.ts.map +1 -1
  46. package/dist/crypto/crypto.js +6 -1
  47. package/dist/crypto/crypto.js.map +1 -1
  48. package/dist/exports.d.ts +2 -2
  49. package/dist/exports.d.ts.map +1 -1
  50. package/dist/exports.js +2 -1
  51. package/dist/exports.js.map +1 -1
  52. package/dist/ids.d.ts +4 -1
  53. package/dist/ids.d.ts.map +1 -1
  54. package/dist/ids.js +4 -0
  55. package/dist/ids.js.map +1 -1
  56. package/dist/knownState.d.ts +2 -0
  57. package/dist/knownState.d.ts.map +1 -1
  58. package/dist/localNode.d.ts +12 -0
  59. package/dist/localNode.d.ts.map +1 -1
  60. package/dist/localNode.js +14 -0
  61. package/dist/localNode.js.map +1 -1
  62. package/dist/platformUtils.d.ts +3 -0
  63. package/dist/platformUtils.d.ts.map +1 -0
  64. package/dist/platformUtils.js +24 -0
  65. package/dist/platformUtils.js.map +1 -0
  66. package/dist/storage/DeletedCoValuesEraserScheduler.d.ts +30 -0
  67. package/dist/storage/DeletedCoValuesEraserScheduler.d.ts.map +1 -0
  68. package/dist/storage/DeletedCoValuesEraserScheduler.js +84 -0
  69. package/dist/storage/DeletedCoValuesEraserScheduler.js.map +1 -0
  70. package/dist/storage/sqlite/client.d.ts +3 -0
  71. package/dist/storage/sqlite/client.d.ts.map +1 -1
  72. package/dist/storage/sqlite/client.js +44 -0
  73. package/dist/storage/sqlite/client.js.map +1 -1
  74. package/dist/storage/sqlite/sqliteMigrations.d.ts.map +1 -1
  75. package/dist/storage/sqlite/sqliteMigrations.js +7 -0
  76. package/dist/storage/sqlite/sqliteMigrations.js.map +1 -1
  77. package/dist/storage/sqliteAsync/client.d.ts +3 -0
  78. package/dist/storage/sqliteAsync/client.d.ts.map +1 -1
  79. package/dist/storage/sqliteAsync/client.js +42 -0
  80. package/dist/storage/sqliteAsync/client.js.map +1 -1
  81. package/dist/storage/storageAsync.d.ts +7 -0
  82. package/dist/storage/storageAsync.d.ts.map +1 -1
  83. package/dist/storage/storageAsync.js +48 -0
  84. package/dist/storage/storageAsync.js.map +1 -1
  85. package/dist/storage/storageSync.d.ts +6 -0
  86. package/dist/storage/storageSync.d.ts.map +1 -1
  87. package/dist/storage/storageSync.js +42 -0
  88. package/dist/storage/storageSync.js.map +1 -1
  89. package/dist/storage/types.d.ts +59 -0
  90. package/dist/storage/types.d.ts.map +1 -1
  91. package/dist/storage/types.js +12 -1
  92. package/dist/storage/types.js.map +1 -1
  93. package/dist/sync.d.ts.map +1 -1
  94. package/dist/sync.js +44 -11
  95. package/dist/sync.js.map +1 -1
  96. package/dist/tests/DeletedCoValuesEraserScheduler.test.d.ts +2 -0
  97. package/dist/tests/DeletedCoValuesEraserScheduler.test.d.ts.map +1 -0
  98. package/dist/tests/DeletedCoValuesEraserScheduler.test.js +149 -0
  99. package/dist/tests/DeletedCoValuesEraserScheduler.test.js.map +1 -0
  100. package/dist/tests/GarbageCollector.test.js +5 -6
  101. package/dist/tests/GarbageCollector.test.js.map +1 -1
  102. package/dist/tests/StorageApiAsync.test.js +484 -152
  103. package/dist/tests/StorageApiAsync.test.js.map +1 -1
  104. package/dist/tests/StorageApiSync.test.js +505 -136
  105. package/dist/tests/StorageApiSync.test.js.map +1 -1
  106. package/dist/tests/WasmCrypto.test.js +6 -3
  107. package/dist/tests/WasmCrypto.test.js.map +1 -1
  108. package/dist/tests/coValueCore.loadFromStorage.test.js +3 -0
  109. package/dist/tests/coValueCore.loadFromStorage.test.js.map +1 -1
  110. package/dist/tests/coValueCore.test.js +34 -13
  111. package/dist/tests/coValueCore.test.js.map +1 -1
  112. package/dist/tests/coreWasm.test.js +127 -4
  113. package/dist/tests/coreWasm.test.js.map +1 -1
  114. package/dist/tests/crypto.test.js +89 -93
  115. package/dist/tests/crypto.test.js.map +1 -1
  116. package/dist/tests/deleteCoValue.test.d.ts +2 -0
  117. package/dist/tests/deleteCoValue.test.d.ts.map +1 -0
  118. package/dist/tests/deleteCoValue.test.js +313 -0
  119. package/dist/tests/deleteCoValue.test.js.map +1 -0
  120. package/dist/tests/group.removeMember.test.js +18 -30
  121. package/dist/tests/group.removeMember.test.js.map +1 -1
  122. package/dist/tests/knownState.lazyLoading.test.js +3 -0
  123. package/dist/tests/knownState.lazyLoading.test.js.map +1 -1
  124. package/dist/tests/sync.deleted.test.d.ts +2 -0
  125. package/dist/tests/sync.deleted.test.d.ts.map +1 -0
  126. package/dist/tests/sync.deleted.test.js +214 -0
  127. package/dist/tests/sync.deleted.test.js.map +1 -0
  128. package/dist/tests/sync.mesh.test.js +3 -2
  129. package/dist/tests/sync.mesh.test.js.map +1 -1
  130. package/dist/tests/sync.storage.test.js +3 -2
  131. package/dist/tests/sync.storage.test.js.map +1 -1
  132. package/dist/tests/sync.test.js +3 -2
  133. package/dist/tests/sync.test.js.map +1 -1
  134. package/dist/tests/testStorage.d.ts +3 -0
  135. package/dist/tests/testStorage.d.ts.map +1 -1
  136. package/dist/tests/testStorage.js +14 -0
  137. package/dist/tests/testStorage.js.map +1 -1
  138. package/dist/tests/testUtils.d.ts +6 -3
  139. package/dist/tests/testUtils.d.ts.map +1 -1
  140. package/dist/tests/testUtils.js +17 -3
  141. package/dist/tests/testUtils.js.map +1 -1
  142. package/package.json +6 -16
  143. package/src/coValueContentMessage.ts +0 -14
  144. package/src/coValueCore/SessionMap.ts +43 -1
  145. package/src/coValueCore/coValueCore.ts +400 -8
  146. package/src/coValueCore/verifiedState.ts +26 -3
  147. package/src/coValues/coList.ts +5 -3
  148. package/src/coValues/group.ts +5 -6
  149. package/src/config.ts +0 -9
  150. package/src/crypto/NapiCrypto.ts +29 -13
  151. package/src/crypto/RNCrypto.ts +29 -11
  152. package/src/crypto/WasmCrypto.ts +67 -20
  153. package/src/crypto/WasmCryptoEdge.ts +5 -1
  154. package/src/crypto/crypto.ts +16 -4
  155. package/src/exports.ts +2 -0
  156. package/src/ids.ts +11 -1
  157. package/src/localNode.ts +15 -0
  158. package/src/platformUtils.ts +26 -0
  159. package/src/storage/DeletedCoValuesEraserScheduler.ts +124 -0
  160. package/src/storage/sqlite/client.ts +77 -0
  161. package/src/storage/sqlite/sqliteMigrations.ts +7 -0
  162. package/src/storage/sqliteAsync/client.ts +75 -0
  163. package/src/storage/storageAsync.ts +62 -0
  164. package/src/storage/storageSync.ts +58 -0
  165. package/src/storage/types.ts +69 -0
  166. package/src/sync.ts +51 -11
  167. package/src/tests/DeletedCoValuesEraserScheduler.test.ts +185 -0
  168. package/src/tests/GarbageCollector.test.ts +6 -10
  169. package/src/tests/StorageApiAsync.test.ts +572 -162
  170. package/src/tests/StorageApiSync.test.ts +580 -143
  171. package/src/tests/WasmCrypto.test.ts +8 -3
  172. package/src/tests/coValueCore.loadFromStorage.test.ts +6 -0
  173. package/src/tests/coValueCore.test.ts +49 -14
  174. package/src/tests/coreWasm.test.ts +319 -10
  175. package/src/tests/crypto.test.ts +141 -150
  176. package/src/tests/deleteCoValue.test.ts +528 -0
  177. package/src/tests/group.removeMember.test.ts +35 -35
  178. package/src/tests/knownState.lazyLoading.test.ts +6 -0
  179. package/src/tests/sync.deleted.test.ts +294 -0
  180. package/src/tests/sync.mesh.test.ts +5 -2
  181. package/src/tests/sync.storage.test.ts +5 -2
  182. package/src/tests/sync.test.ts +5 -2
  183. package/src/tests/testStorage.ts +28 -1
  184. package/src/tests/testUtils.ts +28 -9
  185. package/dist/crypto/PureJSCrypto.d.ts +0 -77
  186. package/dist/crypto/PureJSCrypto.d.ts.map +0 -1
  187. package/dist/crypto/PureJSCrypto.js +0 -236
  188. package/dist/crypto/PureJSCrypto.js.map +0 -1
  189. package/dist/tests/PureJSCrypto.test.d.ts +0 -2
  190. package/dist/tests/PureJSCrypto.test.d.ts.map +0 -1
  191. package/dist/tests/PureJSCrypto.test.js +0 -145
  192. package/dist/tests/PureJSCrypto.test.js.map +0 -1
  193. package/src/crypto/PureJSCrypto.ts +0 -429
  194. 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");
@@ -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,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
 
@@ -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(opts: { ourName?: string } = {}) {
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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"}