cojson 0.19.20 → 0.19.22

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 (159) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +13 -0
  3. package/dist/CojsonMessageChannel/CojsonMessageChannel.d.ts +42 -0
  4. package/dist/CojsonMessageChannel/CojsonMessageChannel.d.ts.map +1 -0
  5. package/dist/CojsonMessageChannel/CojsonMessageChannel.js +261 -0
  6. package/dist/CojsonMessageChannel/CojsonMessageChannel.js.map +1 -0
  7. package/dist/CojsonMessageChannel/MessagePortOutgoingChannel.d.ts +18 -0
  8. package/dist/CojsonMessageChannel/MessagePortOutgoingChannel.d.ts.map +1 -0
  9. package/dist/CojsonMessageChannel/MessagePortOutgoingChannel.js +37 -0
  10. package/dist/CojsonMessageChannel/MessagePortOutgoingChannel.js.map +1 -0
  11. package/dist/CojsonMessageChannel/index.d.ts +3 -0
  12. package/dist/CojsonMessageChannel/index.d.ts.map +1 -0
  13. package/dist/CojsonMessageChannel/index.js +2 -0
  14. package/dist/CojsonMessageChannel/index.js.map +1 -0
  15. package/dist/CojsonMessageChannel/types.d.ts +149 -0
  16. package/dist/CojsonMessageChannel/types.d.ts.map +1 -0
  17. package/dist/CojsonMessageChannel/types.js +36 -0
  18. package/dist/CojsonMessageChannel/types.js.map +1 -0
  19. package/dist/GarbageCollector.d.ts +4 -2
  20. package/dist/GarbageCollector.d.ts.map +1 -1
  21. package/dist/GarbageCollector.js +5 -3
  22. package/dist/GarbageCollector.js.map +1 -1
  23. package/dist/SyncStateManager.d.ts +3 -3
  24. package/dist/SyncStateManager.d.ts.map +1 -1
  25. package/dist/SyncStateManager.js +4 -4
  26. package/dist/SyncStateManager.js.map +1 -1
  27. package/dist/coValueCore/coValueCore.d.ts +28 -1
  28. package/dist/coValueCore/coValueCore.d.ts.map +1 -1
  29. package/dist/coValueCore/coValueCore.js +50 -5
  30. package/dist/coValueCore/coValueCore.js.map +1 -1
  31. package/dist/coValues/account.d.ts.map +1 -1
  32. package/dist/coValues/account.js +10 -10
  33. package/dist/coValues/account.js.map +1 -1
  34. package/dist/exports.d.ts +1 -0
  35. package/dist/exports.d.ts.map +1 -1
  36. package/dist/exports.js +1 -0
  37. package/dist/exports.js.map +1 -1
  38. package/dist/ids.d.ts +1 -1
  39. package/dist/ids.d.ts.map +1 -1
  40. package/dist/ids.js.map +1 -1
  41. package/dist/knownState.d.ts +5 -0
  42. package/dist/knownState.d.ts.map +1 -1
  43. package/dist/knownState.js +15 -0
  44. package/dist/knownState.js.map +1 -1
  45. package/dist/localNode.d.ts +1 -3
  46. package/dist/localNode.d.ts.map +1 -1
  47. package/dist/localNode.js +11 -4
  48. package/dist/localNode.js.map +1 -1
  49. package/dist/storage/knownState.d.ts +5 -0
  50. package/dist/storage/knownState.d.ts.map +1 -1
  51. package/dist/storage/knownState.js +11 -0
  52. package/dist/storage/knownState.js.map +1 -1
  53. package/dist/storage/sqlite/client.d.ts +2 -0
  54. package/dist/storage/sqlite/client.d.ts.map +1 -1
  55. package/dist/storage/sqlite/client.js +18 -0
  56. package/dist/storage/sqlite/client.js.map +1 -1
  57. package/dist/storage/sqliteAsync/client.d.ts +2 -0
  58. package/dist/storage/sqliteAsync/client.d.ts.map +1 -1
  59. package/dist/storage/sqliteAsync/client.js +20 -0
  60. package/dist/storage/sqliteAsync/client.js.map +1 -1
  61. package/dist/storage/storageAsync.d.ts +10 -3
  62. package/dist/storage/storageAsync.d.ts.map +1 -1
  63. package/dist/storage/storageAsync.js +52 -3
  64. package/dist/storage/storageAsync.js.map +1 -1
  65. package/dist/storage/storageSync.d.ts +9 -3
  66. package/dist/storage/storageSync.d.ts.map +1 -1
  67. package/dist/storage/storageSync.js +27 -3
  68. package/dist/storage/storageSync.js.map +1 -1
  69. package/dist/storage/types.d.ts +23 -0
  70. package/dist/storage/types.d.ts.map +1 -1
  71. package/dist/sync.d.ts +23 -0
  72. package/dist/sync.d.ts.map +1 -1
  73. package/dist/sync.js +136 -45
  74. package/dist/sync.js.map +1 -1
  75. package/dist/tests/CojsonMessageChannel.test.d.ts +2 -0
  76. package/dist/tests/CojsonMessageChannel.test.d.ts.map +1 -0
  77. package/dist/tests/CojsonMessageChannel.test.js +236 -0
  78. package/dist/tests/CojsonMessageChannel.test.js.map +1 -0
  79. package/dist/tests/GarbageCollector.test.js +87 -13
  80. package/dist/tests/GarbageCollector.test.js.map +1 -1
  81. package/dist/tests/StorageApiAsync.test.js +124 -1
  82. package/dist/tests/StorageApiAsync.test.js.map +1 -1
  83. package/dist/tests/StorageApiSync.test.js +123 -0
  84. package/dist/tests/StorageApiSync.test.js.map +1 -1
  85. package/dist/tests/SyncManager.processQueues.test.js +1 -1
  86. package/dist/tests/SyncManager.processQueues.test.js.map +1 -1
  87. package/dist/tests/SyncStateManager.test.js +1 -1
  88. package/dist/tests/SyncStateManager.test.js.map +1 -1
  89. package/dist/tests/coPlainText.test.js +1 -1
  90. package/dist/tests/coPlainText.test.js.map +1 -1
  91. package/dist/tests/coValueCore.loadFromStorage.test.js +2 -0
  92. package/dist/tests/coValueCore.loadFromStorage.test.js.map +1 -1
  93. package/dist/tests/knownState.lazyLoading.test.d.ts +2 -0
  94. package/dist/tests/knownState.lazyLoading.test.d.ts.map +1 -0
  95. package/dist/tests/knownState.lazyLoading.test.js +167 -0
  96. package/dist/tests/knownState.lazyLoading.test.js.map +1 -0
  97. package/dist/tests/messagesTestUtils.d.ts +5 -2
  98. package/dist/tests/messagesTestUtils.d.ts.map +1 -1
  99. package/dist/tests/messagesTestUtils.js +4 -0
  100. package/dist/tests/messagesTestUtils.js.map +1 -1
  101. package/dist/tests/sync.garbageCollection.test.js +56 -32
  102. package/dist/tests/sync.garbageCollection.test.js.map +1 -1
  103. package/dist/tests/sync.load.test.js +387 -1
  104. package/dist/tests/sync.load.test.js.map +1 -1
  105. package/dist/tests/sync.mesh.test.js +5 -5
  106. package/dist/tests/sync.mesh.test.js.map +1 -1
  107. package/dist/tests/sync.peerReconciliation.test.js +3 -3
  108. package/dist/tests/sync.peerReconciliation.test.js.map +1 -1
  109. package/dist/tests/sync.storage.test.js +9 -9
  110. package/dist/tests/sync.storage.test.js.map +1 -1
  111. package/dist/tests/sync.storageAsync.test.js +7 -7
  112. package/dist/tests/sync.storageAsync.test.js.map +1 -1
  113. package/dist/tests/sync.tracking.test.js +35 -4
  114. package/dist/tests/sync.tracking.test.js.map +1 -1
  115. package/dist/tests/testStorage.js +38 -2
  116. package/dist/tests/testStorage.js.map +1 -1
  117. package/dist/tests/testUtils.d.ts +38 -4
  118. package/dist/tests/testUtils.d.ts.map +1 -1
  119. package/dist/tests/testUtils.js +68 -7
  120. package/dist/tests/testUtils.js.map +1 -1
  121. package/package.json +4 -4
  122. package/src/CojsonMessageChannel/CojsonMessageChannel.ts +332 -0
  123. package/src/CojsonMessageChannel/MessagePortOutgoingChannel.ts +52 -0
  124. package/src/CojsonMessageChannel/index.ts +9 -0
  125. package/src/CojsonMessageChannel/types.ts +200 -0
  126. package/src/GarbageCollector.ts +5 -5
  127. package/src/SyncStateManager.ts +6 -6
  128. package/src/coValueCore/coValueCore.ts +56 -7
  129. package/src/coValues/account.ts +12 -14
  130. package/src/exports.ts +1 -0
  131. package/src/ids.ts +1 -1
  132. package/src/knownState.ts +24 -0
  133. package/src/localNode.ts +12 -7
  134. package/src/storage/knownState.ts +12 -0
  135. package/src/storage/sqlite/client.ts +31 -0
  136. package/src/storage/sqliteAsync/client.ts +35 -0
  137. package/src/storage/storageAsync.ts +66 -4
  138. package/src/storage/storageSync.ts +37 -4
  139. package/src/storage/types.ts +32 -0
  140. package/src/sync.ts +159 -46
  141. package/src/tests/CojsonMessageChannel.test.ts +306 -0
  142. package/src/tests/GarbageCollector.test.ts +114 -13
  143. package/src/tests/StorageApiAsync.test.ts +186 -1
  144. package/src/tests/StorageApiSync.test.ts +181 -0
  145. package/src/tests/SyncManager.processQueues.test.ts +1 -1
  146. package/src/tests/SyncStateManager.test.ts +1 -1
  147. package/src/tests/coPlainText.test.ts +1 -1
  148. package/src/tests/coValueCore.loadFromStorage.test.ts +5 -0
  149. package/src/tests/knownState.lazyLoading.test.ts +219 -0
  150. package/src/tests/messagesTestUtils.ts +10 -3
  151. package/src/tests/sync.garbageCollection.test.ts +69 -36
  152. package/src/tests/sync.load.test.ts +482 -2
  153. package/src/tests/sync.mesh.test.ts +5 -5
  154. package/src/tests/sync.peerReconciliation.test.ts +3 -3
  155. package/src/tests/sync.storage.test.ts +9 -9
  156. package/src/tests/sync.storageAsync.test.ts +7 -7
  157. package/src/tests/sync.tracking.test.ts +54 -4
  158. package/src/tests/testStorage.ts +40 -2
  159. package/src/tests/testUtils.ts +99 -8
@@ -73,6 +73,56 @@ describe("coValue sync state tracking", () => {
73
73
  expect(unsyncedTracker.has(map.id)).toBe(true);
74
74
  });
75
75
 
76
+ test("coValue is marked as synced after connecting to a server peer", async () => {
77
+ const client = setupTestNode({ connected: false });
78
+
79
+ const group = client.node.createGroup();
80
+ const map = group.createMap();
81
+ map.set("key", "value");
82
+
83
+ await new Promise<void>((resolve) => queueMicrotask(resolve));
84
+
85
+ const unsyncedTracker = client.node.syncManager.unsyncedTracker;
86
+ expect(unsyncedTracker.has(map.id)).toBe(true);
87
+
88
+ client.connectToSyncServer();
89
+
90
+ const serverPeer =
91
+ client.node.syncManager.peers[jazzCloud.node.currentSessionID]!;
92
+ await waitFor(() =>
93
+ client.node.syncManager.syncState.isSynced(serverPeer, map.id),
94
+ );
95
+ expect(unsyncedTracker.has(map.id)).toBe(false);
96
+ });
97
+
98
+ test("coValue is NOT marked as synced after uploading it to a client peer", async () => {
99
+ const client = setupTestNode({ connected: false });
100
+
101
+ const group = client.node.createGroup();
102
+ const map = group.createMap();
103
+ map.set("key", "value");
104
+
105
+ await new Promise<void>((resolve) => queueMicrotask(resolve));
106
+
107
+ const unsyncedTracker = client.node.syncManager.unsyncedTracker;
108
+ expect(unsyncedTracker.has(map.id)).toBe(true);
109
+
110
+ const anotherClient = setupTestNode({ connected: false });
111
+ anotherClient.connectToSyncServer({
112
+ syncServer: client.node,
113
+ });
114
+
115
+ // Load the coValue from the client to trigger sync between the server and the client
116
+ await anotherClient.node.loadCoValueCore(map.id);
117
+
118
+ const clientPeer =
119
+ client.node.syncManager.peers[anotherClient.node.currentSessionID]!;
120
+ await waitFor(() =>
121
+ client.node.syncManager.syncState.isSynced(clientPeer, map.id),
122
+ );
123
+ expect(unsyncedTracker.has(map.id)).toBe(true);
124
+ });
125
+
76
126
  test("only tracks sync state for persistent servers peers", async () => {
77
127
  const { node: client, connectToSyncServer } = setupTestNode({
78
128
  connected: true,
@@ -262,7 +312,7 @@ describe("sync resumption", () => {
262
312
  expect(unsyncedTracker.has(map.id)).toBe(true);
263
313
  expect(await getUnsyncedCoValueIDsFromStorage()).toHaveLength(2);
264
314
 
265
- client.restart();
315
+ await client.restart();
266
316
  client.addStorage({ storage });
267
317
  const { peerState: serverPeerState } = client.connectToSyncServer();
268
318
 
@@ -300,7 +350,7 @@ describe("sync resumption", () => {
300
350
  }
301
351
  expect(await getUnsyncedCoValueIDsFromStorage()).toHaveLength(101);
302
352
 
303
- client.restart();
353
+ await client.restart();
304
354
  client.addStorage({ storage });
305
355
  const { peerState: serverPeerState } = client.connectToSyncServer();
306
356
 
@@ -339,7 +389,7 @@ describe("sync resumption", () => {
339
389
 
340
390
  expect(await getUnsyncedCoValueIDsFromStorage()).toHaveLength(2);
341
391
 
342
- client.restart();
392
+ await client.restart();
343
393
  client.addStorage({ storage });
344
394
  const newSyncServer = setupTestNode({ isSyncServer: true });
345
395
  const { peerState: newServerPeerState } = client.connectToSyncServer({
@@ -377,7 +427,7 @@ describe("sync resumption", () => {
377
427
  expect(unsyncedCoValueIDs).toContain(map.id);
378
428
  expect(unsyncedCoValueIDs).toContain(group.id);
379
429
 
380
- client.restart();
430
+ await client.restart();
381
431
  client.addStorage({ storage });
382
432
  const newPeer = setupTestNode({ isSyncServer: true });
383
433
  client.connectToSyncServer({
@@ -104,8 +104,8 @@ export async function createAsyncStorage({
104
104
  new LibSQLSqliteAsyncDriver(getDbPath(filename)),
105
105
  );
106
106
 
107
- onTestFinished(() => {
108
- storage.close();
107
+ onTestFinished(async () => {
108
+ await storage.close();
109
109
  });
110
110
 
111
111
  trackStorageMessages(storage, nodeName, storageName);
@@ -150,6 +150,44 @@ function trackStorageMessages(
150
150
  ) {
151
151
  const originalStore = storage.store;
152
152
  const originalLoad = storage.load;
153
+ const originalLoadKnownState = storage.loadKnownState;
154
+
155
+ storage.loadKnownState = function (id, callback) {
156
+ SyncMessagesLog.add({
157
+ from: nodeName,
158
+ to: storageName,
159
+ msg: {
160
+ action: "lazyLoad",
161
+ id: id as RawCoID,
162
+ },
163
+ });
164
+
165
+ return originalLoadKnownState.call(storage, id, (knownState) => {
166
+ if (knownState) {
167
+ SyncMessagesLog.add({
168
+ from: storageName,
169
+ to: nodeName,
170
+ msg: {
171
+ action: "lazyLoadResult",
172
+ ...knownState,
173
+ },
174
+ });
175
+ } else {
176
+ SyncMessagesLog.add({
177
+ from: storageName,
178
+ to: nodeName,
179
+ msg: {
180
+ action: "lazyLoadResult",
181
+ id: id as RawCoID,
182
+ header: false,
183
+ sessions: {},
184
+ },
185
+ });
186
+ }
187
+
188
+ return callback(knownState);
189
+ });
190
+ };
153
191
 
154
192
  storage.store = function (data, correctionCallback) {
155
193
  SyncMessagesLog.add({
@@ -13,13 +13,15 @@ import {
13
13
  AnyRawCoValue,
14
14
  type CoID,
15
15
  type CoValueCore,
16
+ MessageChannelLike,
17
+ MessagePortLike,
16
18
  type RawAccount,
17
19
  RawAccountID,
18
20
  RawCoMap,
19
21
  type RawCoValue,
20
22
  StorageAPI,
21
23
  } from "../exports.js";
22
- import type { SessionID } from "../ids.js";
24
+ import type { RawCoID, SessionID } from "../ids.js";
23
25
  import { LocalNode } from "../localNode.js";
24
26
  import { connectedPeers } from "../streamUtils.js";
25
27
  import type { Peer, SyncMessage, SyncWhen } from "../sync.js";
@@ -187,8 +189,8 @@ export function newGroupHighLevel() {
187
189
 
188
190
  const group = node.createGroup();
189
191
 
190
- onTestFinished(() => {
191
- node.gracefulShutdown();
192
+ onTestFinished(async () => {
193
+ await node.gracefulShutdown();
192
194
  });
193
195
  return { admin, node, group };
194
196
  }
@@ -517,8 +519,8 @@ export function setupTestNode(
517
519
  connectToSyncServer();
518
520
  }
519
521
 
520
- onTestFinished(() => {
521
- node.gracefulShutdown();
522
+ onTestFinished(async () => {
523
+ await node.gracefulShutdown();
522
524
  });
523
525
 
524
526
  const ctx = {
@@ -526,8 +528,8 @@ export function setupTestNode(
526
528
  connectToSyncServer,
527
529
  addStorage,
528
530
  addAsyncStorage,
529
- restart: () => {
530
- node.gracefulShutdown();
531
+ restart: async () => {
532
+ await node.gracefulShutdown();
531
533
  ctx.node = node = new LocalNode(
532
534
  admin.agentSecret,
533
535
  session,
@@ -679,10 +681,22 @@ export async function setupTestAccount(
679
681
  };
680
682
  }
681
683
 
684
+ export type LazyLoadMessage = {
685
+ action: "lazyLoad";
686
+ id: RawCoID;
687
+ };
688
+
689
+ export type LazyLoadResultMessage = {
690
+ action: "lazyLoadResult";
691
+ id: RawCoID;
692
+ header: boolean;
693
+ sessions: { [sessionID: string]: number };
694
+ };
695
+
682
696
  export type SyncTestMessage = {
683
697
  from: string;
684
698
  to: string;
685
- msg: SyncMessage;
699
+ msg: SyncMessage | LazyLoadMessage | LazyLoadResultMessage;
686
700
  };
687
701
 
688
702
  export function connectedPeersWithMessagesTracking(opts: {
@@ -799,3 +813,80 @@ export function fillCoMapWithLargeData(map: RawCoMap) {
799
813
 
800
814
  return map;
801
815
  }
816
+
817
+ // ============================================================================
818
+ // MessageChannel Test Helpers
819
+ // ============================================================================
820
+
821
+ /**
822
+ * Type guard to check if a message is a SyncMessage.
823
+ */
824
+ export function isSyncMessage(msg: unknown): msg is SyncMessage {
825
+ return (
826
+ typeof msg === "object" &&
827
+ msg !== null &&
828
+ "action" in msg &&
829
+ typeof (msg as { action: unknown }).action === "string"
830
+ );
831
+ }
832
+
833
+ /**
834
+ * Creates a MessageChannel that logs all sync messages exchanged between ports.
835
+ * Similar to connectedPeersWithMessagesTracking but for MessageChannel.
836
+ */
837
+ export function createTrackedMessageChannel(opts: {
838
+ port1Name?: string;
839
+ port2Name?: string;
840
+ }) {
841
+ const { port1, port2 } = new MessageChannel();
842
+ const port1Name = opts.port1Name ?? "port1";
843
+ const port2Name = opts.port2Name ?? "port2";
844
+
845
+ // Wrap port1.postMessage to log messages
846
+ const originalPort1PostMessage = port1.postMessage.bind(port1);
847
+ port1.postMessage = (message, transfer) => {
848
+ if (isSyncMessage(message)) {
849
+ SyncMessagesLog.add({
850
+ from: port1Name,
851
+ to: port2Name,
852
+ msg: message,
853
+ });
854
+ }
855
+
856
+ originalPort1PostMessage(message, transfer);
857
+ };
858
+
859
+ // Wrap port2.postMessage to log messages
860
+ const originalPort2PostMessage = port2.postMessage.bind(port2);
861
+ port2.postMessage = (message, transfer) => {
862
+ if (isSyncMessage(message)) {
863
+ SyncMessagesLog.add({
864
+ from: port2Name,
865
+ to: port1Name,
866
+ msg: message,
867
+ });
868
+ }
869
+
870
+ originalPort2PostMessage(message, transfer);
871
+ };
872
+
873
+ return { port1, port2 };
874
+ }
875
+
876
+ /**
877
+ * Creates a mock worker target that simulates receiving a port
878
+ * and calling a callback with the received port (simulating a connection handshake).
879
+ */
880
+ export function createMockWorkerWithAccept(
881
+ onPortReceived: (port: MessagePortLike) => Promise<void>,
882
+ ) {
883
+ return {
884
+ postMessage: vi.fn().mockImplementation((data, transfer) => {
885
+ if (data?.type === "jazz:port" && transfer?.[0]) {
886
+ const port = transfer[0] as MessagePortLike;
887
+ // Simulate the worker receiving the port and calling accept
888
+ onPortReceived(port);
889
+ }
890
+ }),
891
+ };
892
+ }