cojson 0.16.3 → 0.16.4

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 (113) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +7 -0
  3. package/dist/coValue.d.ts +1 -1
  4. package/dist/coValueContentMessage.d.ts +10 -0
  5. package/dist/coValueContentMessage.d.ts.map +1 -0
  6. package/dist/coValueContentMessage.js +46 -0
  7. package/dist/coValueContentMessage.js.map +1 -0
  8. package/dist/coValueCore/coValueCore.d.ts.map +1 -1
  9. package/dist/coValueCore/coValueCore.js +5 -3
  10. package/dist/coValueCore/coValueCore.js.map +1 -1
  11. package/dist/coValueCore/verifiedState.d.ts +1 -0
  12. package/dist/coValueCore/verifiedState.d.ts.map +1 -1
  13. package/dist/coValueCore/verifiedState.js +14 -27
  14. package/dist/coValueCore/verifiedState.js.map +1 -1
  15. package/dist/coValues/group.d.ts.map +1 -1
  16. package/dist/coValues/group.js +16 -8
  17. package/dist/coValues/group.js.map +1 -1
  18. package/dist/localNode.d.ts +6 -1
  19. package/dist/localNode.d.ts.map +1 -1
  20. package/dist/localNode.js +7 -2
  21. package/dist/localNode.js.map +1 -1
  22. package/dist/queue/LocalTransactionsSyncQueue.d.ts +24 -0
  23. package/dist/queue/LocalTransactionsSyncQueue.d.ts.map +1 -0
  24. package/dist/queue/LocalTransactionsSyncQueue.js +55 -0
  25. package/dist/queue/LocalTransactionsSyncQueue.js.map +1 -0
  26. package/dist/queue/StoreQueue.d.ts +9 -6
  27. package/dist/queue/StoreQueue.d.ts.map +1 -1
  28. package/dist/queue/StoreQueue.js +10 -2
  29. package/dist/queue/StoreQueue.js.map +1 -1
  30. package/dist/storage/storageAsync.d.ts +11 -3
  31. package/dist/storage/storageAsync.d.ts.map +1 -1
  32. package/dist/storage/storageAsync.js +59 -46
  33. package/dist/storage/storageAsync.js.map +1 -1
  34. package/dist/storage/storageSync.d.ts +9 -3
  35. package/dist/storage/storageSync.d.ts.map +1 -1
  36. package/dist/storage/storageSync.js +48 -35
  37. package/dist/storage/storageSync.js.map +1 -1
  38. package/dist/storage/syncUtils.d.ts +2 -1
  39. package/dist/storage/syncUtils.d.ts.map +1 -1
  40. package/dist/storage/syncUtils.js +4 -0
  41. package/dist/storage/syncUtils.js.map +1 -1
  42. package/dist/storage/types.d.ts +3 -2
  43. package/dist/storage/types.d.ts.map +1 -1
  44. package/dist/sync.d.ts +6 -6
  45. package/dist/sync.d.ts.map +1 -1
  46. package/dist/sync.js +33 -56
  47. package/dist/sync.js.map +1 -1
  48. package/dist/tests/StorageApiAsync.test.d.ts +2 -0
  49. package/dist/tests/StorageApiAsync.test.d.ts.map +1 -0
  50. package/dist/tests/StorageApiAsync.test.js +574 -0
  51. package/dist/tests/StorageApiAsync.test.js.map +1 -0
  52. package/dist/tests/StorageApiSync.test.d.ts +2 -0
  53. package/dist/tests/StorageApiSync.test.d.ts.map +1 -0
  54. package/dist/tests/StorageApiSync.test.js +426 -0
  55. package/dist/tests/StorageApiSync.test.js.map +1 -0
  56. package/dist/tests/StoreQueue.test.js +9 -21
  57. package/dist/tests/StoreQueue.test.js.map +1 -1
  58. package/dist/tests/SyncStateManager.test.js +18 -8
  59. package/dist/tests/SyncStateManager.test.js.map +1 -1
  60. package/dist/tests/group.inheritance.test.js +79 -2
  61. package/dist/tests/group.inheritance.test.js.map +1 -1
  62. package/dist/tests/sync.auth.test.js +22 -10
  63. package/dist/tests/sync.auth.test.js.map +1 -1
  64. package/dist/tests/sync.load.test.js +25 -23
  65. package/dist/tests/sync.load.test.js.map +1 -1
  66. package/dist/tests/sync.mesh.test.js +12 -6
  67. package/dist/tests/sync.mesh.test.js.map +1 -1
  68. package/dist/tests/sync.peerReconciliation.test.js +6 -4
  69. package/dist/tests/sync.peerReconciliation.test.js.map +1 -1
  70. package/dist/tests/sync.storage.test.js +8 -14
  71. package/dist/tests/sync.storage.test.js.map +1 -1
  72. package/dist/tests/sync.storageAsync.test.js +31 -14
  73. package/dist/tests/sync.storageAsync.test.js.map +1 -1
  74. package/dist/tests/sync.test.js +5 -9
  75. package/dist/tests/sync.test.js.map +1 -1
  76. package/dist/tests/sync.upload.test.js +31 -1
  77. package/dist/tests/sync.upload.test.js.map +1 -1
  78. package/dist/tests/testStorage.d.ts +2 -3
  79. package/dist/tests/testStorage.d.ts.map +1 -1
  80. package/dist/tests/testStorage.js +16 -8
  81. package/dist/tests/testStorage.js.map +1 -1
  82. package/dist/tests/testUtils.d.ts +3 -0
  83. package/dist/tests/testUtils.d.ts.map +1 -1
  84. package/dist/tests/testUtils.js +17 -4
  85. package/dist/tests/testUtils.js.map +1 -1
  86. package/package.json +1 -1
  87. package/src/coValueContentMessage.ts +73 -0
  88. package/src/coValueCore/coValueCore.ts +14 -5
  89. package/src/coValueCore/verifiedState.ts +28 -35
  90. package/src/coValues/group.ts +20 -9
  91. package/src/localNode.ts +8 -3
  92. package/src/queue/LocalTransactionsSyncQueue.ts +96 -0
  93. package/src/queue/StoreQueue.ts +22 -12
  94. package/src/storage/storageAsync.ts +78 -56
  95. package/src/storage/storageSync.ts +66 -45
  96. package/src/storage/syncUtils.ts +9 -1
  97. package/src/storage/types.ts +6 -5
  98. package/src/sync.ts +47 -67
  99. package/src/tests/StorageApiAsync.test.ts +829 -0
  100. package/src/tests/StorageApiSync.test.ts +628 -0
  101. package/src/tests/StoreQueue.test.ts +10 -24
  102. package/src/tests/SyncStateManager.test.ts +22 -21
  103. package/src/tests/group.inheritance.test.ts +136 -1
  104. package/src/tests/sync.auth.test.ts +22 -10
  105. package/src/tests/sync.load.test.ts +27 -24
  106. package/src/tests/sync.mesh.test.ts +12 -6
  107. package/src/tests/sync.peerReconciliation.test.ts +6 -4
  108. package/src/tests/sync.storage.test.ts +8 -14
  109. package/src/tests/sync.storageAsync.test.ts +39 -14
  110. package/src/tests/sync.test.ts +6 -14
  111. package/src/tests/sync.upload.test.ts +38 -1
  112. package/src/tests/testStorage.ts +19 -13
  113. package/src/tests/testUtils.ts +24 -5
@@ -16,6 +16,7 @@ describe("client with storage syncs with server", () => {
16
16
  let jazzCloud: ReturnType<typeof setupTestNode>;
17
17
 
18
18
  beforeEach(async () => {
19
+ vi.resetAllMocks();
19
20
  SyncMessagesLog.clear();
20
21
  jazzCloud = setupTestNode({
21
22
  isSyncServer: true,
@@ -174,18 +175,46 @@ describe("client with storage syncs with server", () => {
174
175
  [
175
176
  "client -> server | LOAD Group sessions: header/3",
176
177
  "client -> server | LOAD Map sessions: header/1",
177
- "server -> client | CONTENT Group header: true new: After: 0 New: 3",
178
- "server -> client | CONTENT Map header: true new: After: 0 New: 2",
179
178
  "server -> client | CONTENT Map header: false new: After: 1 New: 1",
180
- "client -> server | KNOWN Group sessions: header/3",
181
- "client -> storage | CONTENT Group header: true new: After: 0 New: 3",
179
+ "server -> client | KNOWN Group sessions: header/3",
180
+ "server -> client | CONTENT Map header: false new: After: 1 New: 1",
182
181
  "client -> server | KNOWN Map sessions: header/2",
183
- "client -> storage | CONTENT Map header: true new: After: 0 New: 2",
182
+ "client -> storage | CONTENT Map header: false new: After: 1 New: 1",
184
183
  "client -> server | KNOWN Map sessions: header/2",
185
184
  "client -> storage | CONTENT Map header: false new: After: 1 New: 1",
186
185
  ]
187
186
  `);
188
187
  });
188
+
189
+ test("the order of updates between CoValues should be preserved to ensure consistency in case of shutdown in the middle of sync", async () => {
190
+ const client = setupTestNode();
191
+
192
+ await client.addAsyncStorage();
193
+
194
+ const group = client.node.createGroup();
195
+ const initialMap = group.createMap();
196
+
197
+ const child = group.createMap();
198
+ child.set("parent", initialMap.id);
199
+ initialMap.set("child", child.id);
200
+
201
+ await initialMap.core.waitForSync();
202
+
203
+ expect(
204
+ SyncMessagesLog.getMessages({
205
+ Group: group.core,
206
+ InitialMap: initialMap.core,
207
+ ChildMap: child.core,
208
+ }),
209
+ ).toMatchInlineSnapshot(`
210
+ [
211
+ "client -> storage | CONTENT Group header: true new: After: 0 New: 3",
212
+ "client -> storage | CONTENT InitialMap header: true new: ",
213
+ "client -> storage | CONTENT ChildMap header: true new: After: 0 New: 1",
214
+ "client -> storage | CONTENT InitialMap header: false new: After: 0 New: 1",
215
+ ]
216
+ `);
217
+ });
189
218
  });
190
219
 
191
220
  describe("client syncs with a server with storage", () => {
@@ -271,20 +300,16 @@ describe("client syncs with a server with storage", () => {
271
300
  [
272
301
  "client -> storage | CONTENT Group header: true new: After: 0 New: 5",
273
302
  "client -> server | CONTENT Group header: true new: After: 0 New: 5",
274
- "client -> storage | CONTENT Map header: true new: expectContentUntil: header/200",
275
- "client -> storage | CONTENT Map header: false new: After: 0 New: 73",
303
+ "client -> storage | CONTENT Map header: true new: After: 0 New: 73",
304
+ "client -> server | CONTENT Map header: true new: After: 0 New: 73",
276
305
  "client -> storage | CONTENT Map header: false new: After: 73 New: 73",
277
- "client -> storage | CONTENT Map header: false new: After: 146 New: 54",
278
- "client -> server | CONTENT Map header: true new: expectContentUntil: header/200",
279
- "client -> server | CONTENT Map header: false new: After: 0 New: 73",
280
306
  "client -> server | CONTENT Map header: false new: After: 73 New: 73",
307
+ "client -> storage | CONTENT Map header: false new: After: 146 New: 54",
281
308
  "client -> server | CONTENT Map header: false new: After: 146 New: 54",
282
309
  "server -> client | KNOWN Group sessions: header/5",
283
310
  "server -> storage | CONTENT Group header: true new: After: 0 New: 5",
284
- "server -> client | KNOWN Map sessions: header/0",
285
- "server -> storage | CONTENT Map header: true new: expectContentUntil: header/200",
286
311
  "server -> client | KNOWN Map sessions: header/73",
287
- "server -> storage | CONTENT Map header: false new: After: 0 New: 73",
312
+ "server -> storage | CONTENT Map header: true new: After: 0 New: 73",
288
313
  "server -> client | KNOWN Map sessions: header/146",
289
314
  "server -> storage | CONTENT Map header: false new: After: 73 New: 73",
290
315
  "server -> client | KNOWN Map sessions: header/200",
@@ -369,7 +394,7 @@ describe("client syncs with a server with storage", () => {
369
394
 
370
395
  const correctionSpy = vi.fn();
371
396
 
372
- client.node.storage?.store(newContentChunks.slice(1, 2), correctionSpy);
397
+ client.node.storage?.store(newContentChunks[1]!, correctionSpy);
373
398
 
374
399
  // Wait for the content to be stored in the storage
375
400
  // We can't use waitForSync because we are trying to store stale data
@@ -100,15 +100,18 @@ test("Can sync a coValue with private transactions through a server to another c
100
100
 
101
101
  const map = group.createMap();
102
102
  map.set("hello", "world", "private");
103
+
103
104
  group.addMember("everyone", "reader");
104
105
 
105
106
  const { node: client2 } = await setupTestAccount({
106
107
  connected: true,
107
108
  });
108
109
 
109
- const mapOnClient2 = await loadCoValueOrFail(client2, map.id);
110
+ await waitFor(async () => {
111
+ const loadedMap = await loadCoValueOrFail(client2, map.id);
110
112
 
111
- expect(mapOnClient2.get("hello")).toEqual("world");
113
+ expect(loadedMap.get("hello")).toEqual("world");
114
+ });
112
115
  });
113
116
 
114
117
  test("should keep the peer state when the peer closes if persistent is true", async () => {
@@ -563,8 +566,6 @@ describe("SyncManager - knownStates vs optimisticKnownStates", () => {
563
566
  const mapOnClient = group.createMap();
564
567
  mapOnClient.set("key1", "value1", "trusting");
565
568
 
566
- await client.syncManager.syncCoValue(mapOnClient.core);
567
-
568
569
  // Wait for the full sync to complete
569
570
  await mapOnClient.core.waitForSync();
570
571
 
@@ -594,7 +595,6 @@ describe("SyncManager - knownStates vs optimisticKnownStates", () => {
594
595
  const map = group.createMap();
595
596
  map.set("key1", "value1", "trusting");
596
597
 
597
- await client.node.syncManager.syncCoValue(map.core);
598
598
  await map.core.waitForSync();
599
599
 
600
600
  // Block the content messages
@@ -606,7 +606,7 @@ describe("SyncManager - knownStates vs optimisticKnownStates", () => {
606
606
 
607
607
  map.set("key2", "value2", "trusting");
608
608
 
609
- await client.node.syncManager.syncCoValue(map.core);
609
+ await new Promise<void>(queueMicrotask);
610
610
 
611
611
  expect(peerState.optimisticKnownStates.get(map.core.id)).not.toEqual(
612
612
  peerState.knownStates.get(map.core.id),
@@ -638,8 +638,6 @@ describe("SyncManager.addPeer", () => {
638
638
  const map = group.createMap();
639
639
  map.set("key1", "value1", "trusting");
640
640
 
641
- await client.node.syncManager.syncCoValue(map.core);
642
-
643
641
  // Wait for initial sync
644
642
  await map.core.waitForSync();
645
643
 
@@ -671,8 +669,6 @@ describe("SyncManager.addPeer", () => {
671
669
  const map = group.createMap();
672
670
  map.set("key1", "value1", "trusting");
673
671
 
674
- await client.node.syncManager.syncCoValue(map.core);
675
-
676
672
  // Wait for initial sync
677
673
  await map.core.waitForSync();
678
674
 
@@ -843,8 +839,6 @@ describe("waitForSyncWithPeer", () => {
843
839
  const map = group.createMap();
844
840
  map.set("key1", "value1", "trusting");
845
841
 
846
- await client.node.syncManager.syncCoValue(map.core);
847
-
848
842
  await expect(
849
843
  client.node.syncManager.waitForSyncWithPeer(
850
844
  peerState.id,
@@ -868,8 +862,6 @@ describe("waitForSyncWithPeer", () => {
868
862
  return Promise.resolve();
869
863
  });
870
864
 
871
- await client.node.syncManager.syncCoValue(map.core);
872
-
873
865
  await expect(
874
866
  client.node.syncManager.waitForSyncWithPeer(
875
867
  peerState.id,
@@ -78,12 +78,15 @@ describe("client to server upload", () => {
78
78
  }),
79
79
  ).toMatchInlineSnapshot(`
80
80
  [
81
+ "client -> server | CONTENT Group header: true new: After: 0 New: 3",
81
82
  "client -> server | CONTENT ParentGroup header: true new: After: 0 New: 6",
82
- "client -> server | CONTENT Group header: true new: After: 0 New: 5",
83
+ "client -> server | CONTENT Group header: false new: After: 3 New: 2",
83
84
  "client -> server | CONTENT Map header: true new: After: 0 New: 1",
85
+ "server -> client | KNOWN Group sessions: header/3",
84
86
  "server -> client | KNOWN ParentGroup sessions: header/6",
85
87
  "server -> client | KNOWN Group sessions: header/5",
86
88
  "server -> client | KNOWN Map sessions: header/1",
89
+ "client -> server | CONTENT ParentGroup header: true new: ",
87
90
  ]
88
91
  `);
89
92
  });
@@ -253,6 +256,40 @@ describe("client to server upload", () => {
253
256
  `);
254
257
  });
255
258
 
259
+ test("local updates batching", async () => {
260
+ const client = setupTestNode({
261
+ connected: true,
262
+ });
263
+
264
+ const group = client.node.createGroup();
265
+ const initialMap = group.createMap();
266
+
267
+ const child = group.createMap();
268
+ child.set("parent", initialMap.id);
269
+ initialMap.set("child", child.id);
270
+
271
+ await initialMap.core.waitForSync();
272
+
273
+ expect(
274
+ SyncMessagesLog.getMessages({
275
+ Group: group.core,
276
+ InitialMap: initialMap.core,
277
+ ChildMap: child.core,
278
+ }),
279
+ ).toMatchInlineSnapshot(`
280
+ [
281
+ "client -> server | CONTENT Group header: true new: After: 0 New: 3",
282
+ "client -> server | CONTENT InitialMap header: true new: ",
283
+ "client -> server | CONTENT ChildMap header: true new: After: 0 New: 1",
284
+ "client -> server | CONTENT InitialMap header: false new: After: 0 New: 1",
285
+ "server -> client | KNOWN Group sessions: header/3",
286
+ "server -> client | KNOWN InitialMap sessions: header/0",
287
+ "server -> client | KNOWN ChildMap sessions: header/1",
288
+ "server -> client | KNOWN InitialMap sessions: header/1",
289
+ ]
290
+ `);
291
+ });
292
+
256
293
  test("large coValue upload streaming", async () => {
257
294
  const client = setupTestNode({
258
295
  connected: true,
@@ -5,11 +5,7 @@ import { join } from "node:path";
5
5
  import Database, { type Database as DatabaseT } from "libsql";
6
6
  import { onTestFinished } from "vitest";
7
7
  import { RawCoID, StorageAPI } from "../exports";
8
- import {
9
- SQLiteDatabaseDriver,
10
- StorageApiAsync,
11
- StorageApiSync,
12
- } from "../storage";
8
+ import { SQLiteDatabaseDriver } from "../storage";
13
9
  import { getSqliteStorage } from "../storage/sqlite";
14
10
  import {
15
11
  SQLiteDatabaseDriverAsync,
@@ -148,13 +144,11 @@ function trackStorageMessages(
148
144
  const originalLoad = storage.load;
149
145
 
150
146
  storage.store = function (data, correctionCallback) {
151
- for (const msg of data ?? []) {
152
- SyncMessagesLog.add({
153
- from: nodeName,
154
- to: storageName,
155
- msg,
156
- });
157
- }
147
+ SyncMessagesLog.add({
148
+ from: nodeName,
149
+ to: storageName,
150
+ msg: data,
151
+ });
158
152
 
159
153
  return originalStore.call(storage, data, (correction) => {
160
154
  SyncMessagesLog.add({
@@ -167,7 +161,19 @@ function trackStorageMessages(
167
161
  },
168
162
  });
169
163
 
170
- return correctionCallback(correction);
164
+ const correctionMessages = correctionCallback(correction);
165
+
166
+ if (correctionMessages) {
167
+ for (const msg of correctionMessages) {
168
+ SyncMessagesLog.add({
169
+ from: nodeName,
170
+ to: storageName,
171
+ msg,
172
+ });
173
+ }
174
+ }
175
+
176
+ return correctionMessages;
171
177
  });
172
178
  };
173
179
 
@@ -40,6 +40,14 @@ export function randomAgentAndSessionID(): [ControlledAgent, SessionID] {
40
40
  return [new ControlledAgent(agentSecret, Crypto), sessionID];
41
41
  }
42
42
 
43
+ export function agentAndSessionIDFromSecret(
44
+ secret: AgentSecret,
45
+ ): [ControlledAgent, SessionID] {
46
+ const sessionID = Crypto.newRandomSessionID(Crypto.getAgentID(secret));
47
+
48
+ return [new ControlledAgent(secret, Crypto), sessionID];
49
+ }
50
+
43
51
  export function nodeWithRandomAgentAndSessionID() {
44
52
  const [agent, session] = randomAgentAndSessionID();
45
53
  return new LocalNode(agent.agentSecret, session, Crypto);
@@ -154,8 +162,8 @@ export function connectTwoPeers(
154
162
  bRole: "client" | "server",
155
163
  ) {
156
164
  const [aAsPeer, bAsPeer] = connectedPeers(
157
- "peer:" + a.getCurrentAgent().id,
158
- "peer:" + b.getCurrentAgent().id,
165
+ "peer:" + a.currentSessionID,
166
+ "peer:" + b.currentSessionID,
159
167
  {
160
168
  peer1role: aRole,
161
169
  peer2role: bRole,
@@ -443,7 +451,7 @@ export function getSyncServerConnectedPeer(opts: {
443
451
 
444
452
  const { peer1, peer2 } = connectedPeersWithMessagesTracking({
445
453
  peer1: {
446
- id: currentSyncServer.getCurrentAgent().id,
454
+ id: currentSyncServer.currentSessionID,
447
455
  role: "server",
448
456
  name: opts.syncServerName,
449
457
  },
@@ -472,9 +480,13 @@ export function setupTestNode(
472
480
  opts: {
473
481
  isSyncServer?: boolean;
474
482
  connected?: boolean;
483
+ secret?: AgentSecret;
475
484
  } = {},
476
485
  ) {
477
- const [admin, session] = randomAgentAndSessionID();
486
+ const [admin, session] = opts.secret
487
+ ? agentAndSessionIDFromSecret(opts.secret)
488
+ : randomAgentAndSessionID();
489
+
478
490
  let node = new LocalNode(admin.agentSecret, session, Crypto);
479
491
 
480
492
  if (opts.isSyncServer) {
@@ -489,7 +501,7 @@ export function setupTestNode(
489
501
  }) {
490
502
  const { peer, peerStateOnServer, peerOnServer } =
491
503
  getSyncServerConnectedPeer({
492
- peerId: node.getCurrentAgent().id,
504
+ peerId: session,
493
505
  syncServerName: opts?.syncServerName,
494
506
  ourName: opts?.ourName,
495
507
  syncServer: opts?.syncServer,
@@ -551,6 +563,13 @@ export function setupTestNode(
551
563
 
552
564
  return node;
553
565
  },
566
+ spawnNewSession: () => {
567
+ return setupTestNode({
568
+ secret: node.agentSecret,
569
+ connected: opts.connected,
570
+ isSyncServer: opts.isSyncServer,
571
+ });
572
+ },
554
573
  };
555
574
 
556
575
  return ctx;