cojson 0.13.15 → 0.13.17

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 (62) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +13 -0
  3. package/dist/coValue.d.ts +2 -0
  4. package/dist/coValue.d.ts.map +1 -1
  5. package/dist/coValue.js +1 -0
  6. package/dist/coValue.js.map +1 -1
  7. package/dist/coValueCore.d.ts +1 -0
  8. package/dist/coValueCore.d.ts.map +1 -1
  9. package/dist/coValueCore.js.map +1 -1
  10. package/dist/coValueState.d.ts.map +1 -1
  11. package/dist/coValueState.js +58 -96
  12. package/dist/coValueState.js.map +1 -1
  13. package/dist/coValues/coList.d.ts +7 -3
  14. package/dist/coValues/coList.d.ts.map +1 -1
  15. package/dist/coValues/coList.js +35 -12
  16. package/dist/coValues/coList.js.map +1 -1
  17. package/dist/coValues/coMap.d.ts +1 -0
  18. package/dist/coValues/coMap.d.ts.map +1 -1
  19. package/dist/coValues/coMap.js +2 -0
  20. package/dist/coValues/coMap.js.map +1 -1
  21. package/dist/coValues/coPlainText.d.ts.map +1 -1
  22. package/dist/coValues/coPlainText.js +1 -1
  23. package/dist/coValues/coPlainText.js.map +1 -1
  24. package/dist/coValues/coStream.d.ts +2 -1
  25. package/dist/coValues/coStream.d.ts.map +1 -1
  26. package/dist/coValues/coStream.js +2 -0
  27. package/dist/coValues/coStream.js.map +1 -1
  28. package/dist/coreToCoValue.d.ts +2 -1
  29. package/dist/coreToCoValue.d.ts.map +1 -1
  30. package/dist/localNode.d.ts.map +1 -1
  31. package/dist/localNode.js +21 -14
  32. package/dist/localNode.js.map +1 -1
  33. package/dist/sync.js +3 -3
  34. package/dist/sync.js.map +1 -1
  35. package/dist/tests/coList.test.js +119 -2
  36. package/dist/tests/coList.test.js.map +1 -1
  37. package/dist/tests/coMap.test.js +32 -2
  38. package/dist/tests/coMap.test.js.map +1 -1
  39. package/dist/tests/coStream.test.js +28 -2
  40. package/dist/tests/coStream.test.js.map +1 -1
  41. package/dist/tests/coValueState.test.js +10 -143
  42. package/dist/tests/coValueState.test.js.map +1 -1
  43. package/dist/tests/sync.load.test.js +59 -1
  44. package/dist/tests/sync.load.test.js.map +1 -1
  45. package/dist/tests/sync.mesh.test.js +48 -1
  46. package/dist/tests/sync.mesh.test.js.map +1 -1
  47. package/package.json +1 -1
  48. package/src/coValue.ts +3 -0
  49. package/src/coValueCore.ts +1 -0
  50. package/src/coValueState.ts +67 -122
  51. package/src/coValues/coList.ts +51 -23
  52. package/src/coValues/coMap.ts +4 -0
  53. package/src/coValues/coPlainText.ts +1 -2
  54. package/src/coValues/coStream.ts +3 -1
  55. package/src/localNode.ts +29 -18
  56. package/src/sync.ts +3 -3
  57. package/src/tests/coList.test.ts +184 -2
  58. package/src/tests/coMap.test.ts +54 -2
  59. package/src/tests/coStream.test.ts +56 -2
  60. package/src/tests/coValueState.test.ts +9 -207
  61. package/src/tests/sync.load.test.ts +78 -1
  62. package/src/tests/sync.mesh.test.ts +67 -0
@@ -10,26 +10,15 @@ import {
10
10
  } from "vitest";
11
11
  import { PeerState } from "../PeerState";
12
12
  import { CoValueCore } from "../coValueCore";
13
- import { CO_VALUE_LOADING_CONFIG, CoValueState } from "../coValueState";
13
+ import { CoValueState } from "../coValueState";
14
14
  import { RawCoID } from "../ids";
15
15
  import { Peer } from "../sync";
16
16
  import { createTestMetricReader, tearDownTestMetricReader } from "./testUtils";
17
17
 
18
- const initialMaxRetries = CO_VALUE_LOADING_CONFIG.MAX_RETRIES;
19
-
20
- function mockMaxRetries(maxRetries: number) {
21
- CO_VALUE_LOADING_CONFIG.MAX_RETRIES = maxRetries;
22
-
23
- onTestFinished(() => {
24
- CO_VALUE_LOADING_CONFIG.MAX_RETRIES = initialMaxRetries;
25
- });
26
- }
27
-
28
18
  let metricReader: ReturnType<typeof createTestMetricReader>;
29
19
 
30
20
  beforeEach(() => {
31
21
  metricReader = createTestMetricReader();
32
- mockMaxRetries(5);
33
22
  });
34
23
 
35
24
  afterEach(() => {
@@ -122,51 +111,6 @@ describe("CoValueState", () => {
122
111
  ).toBe(0);
123
112
  });
124
113
 
125
- test("should retry loading from peers when unsuccessful", async () => {
126
- vi.useFakeTimers();
127
-
128
- const peer1 = createMockPeerState(
129
- {
130
- id: "peer1",
131
- role: "server",
132
- },
133
- async () => {
134
- state.markNotFoundInPeer("peer1");
135
- },
136
- );
137
- const peer2 = createMockPeerState(
138
- {
139
- id: "peer2",
140
- role: "server",
141
- },
142
- async () => {
143
- state.markNotFoundInPeer("peer2");
144
- },
145
- );
146
- const mockPeers = [peer1, peer2] as unknown as PeerState[];
147
-
148
- const state = new CoValueState(mockCoValueId);
149
- const loadPromise = state.loadFromPeers(mockPeers);
150
-
151
- // Should attempt CO_VALUE_LOADING_CONFIG.MAX_RETRIES retries
152
- for (let i = 0; i < CO_VALUE_LOADING_CONFIG.MAX_RETRIES; i++) {
153
- await vi.runAllTimersAsync();
154
- }
155
-
156
- await loadPromise;
157
-
158
- expect(peer1.pushOutgoingMessage).toHaveBeenCalledTimes(
159
- CO_VALUE_LOADING_CONFIG.MAX_RETRIES,
160
- );
161
- expect(peer2.pushOutgoingMessage).toHaveBeenCalledTimes(
162
- CO_VALUE_LOADING_CONFIG.MAX_RETRIES,
163
- );
164
- expect(state.highLevelState).toBe("unavailable");
165
- await expect(state.getCoValue()).resolves.toBe("unavailable");
166
-
167
- vi.useRealTimers();
168
- });
169
-
170
114
  test("should skip errored coValues when loading from peers", async () => {
171
115
  vi.useFakeTimers();
172
116
 
@@ -194,108 +138,18 @@ describe("CoValueState", () => {
194
138
  const state = new CoValueState(mockCoValueId);
195
139
  const loadPromise = state.loadFromPeers(mockPeers);
196
140
 
197
- // Should attempt CO_VALUE_LOADING_CONFIG.MAX_RETRIES retries
198
- for (let i = 0; i < CO_VALUE_LOADING_CONFIG.MAX_RETRIES; i++) {
199
- await vi.runAllTimersAsync();
200
- }
141
+ await vi.runAllTimersAsync();
201
142
 
202
143
  await loadPromise;
203
144
 
204
145
  expect(peer1.pushOutgoingMessage).toHaveBeenCalledTimes(1);
205
- expect(peer2.pushOutgoingMessage).toHaveBeenCalledTimes(
206
- CO_VALUE_LOADING_CONFIG.MAX_RETRIES,
207
- );
146
+ expect(peer2.pushOutgoingMessage).toHaveBeenCalledTimes(1);
208
147
  expect(state.highLevelState).toBe("unavailable");
209
148
  await expect(state.getCoValue()).resolves.toBe("unavailable");
210
149
 
211
150
  vi.useRealTimers();
212
151
  });
213
152
 
214
- test("should retry only on server peers", async () => {
215
- vi.useFakeTimers();
216
-
217
- const peer1 = createMockPeerState(
218
- {
219
- id: "peer1",
220
- role: "storage",
221
- },
222
- async () => {
223
- state.markNotFoundInPeer("peer1");
224
- },
225
- );
226
- const peer2 = createMockPeerState(
227
- {
228
- id: "peer2",
229
- role: "server",
230
- },
231
- async () => {
232
- state.markNotFoundInPeer("peer2");
233
- },
234
- );
235
- const mockPeers = [peer1, peer2] as unknown as PeerState[];
236
-
237
- const state = new CoValueState(mockCoValueId);
238
- const loadPromise = state.loadFromPeers(mockPeers);
239
-
240
- // Should attempt CO_VALUE_LOADING_CONFIG.MAX_RETRIES retries
241
- for (let i = 0; i < CO_VALUE_LOADING_CONFIG.MAX_RETRIES; i++) {
242
- await vi.runAllTimersAsync();
243
- }
244
-
245
- await loadPromise;
246
-
247
- expect(peer1.pushOutgoingMessage).toHaveBeenCalledTimes(1);
248
- expect(peer2.pushOutgoingMessage).toHaveBeenCalledTimes(
249
- CO_VALUE_LOADING_CONFIG.MAX_RETRIES,
250
- );
251
- expect(state.highLevelState).toBe("unavailable");
252
- await expect(state.getCoValue()).resolves.toEqual("unavailable");
253
-
254
- vi.useRealTimers();
255
- });
256
-
257
- test("should handle the coValues that become available in between of the retries", async () => {
258
- vi.useFakeTimers();
259
-
260
- mockMaxRetries(5);
261
-
262
- let retries = 0;
263
-
264
- const peer1 = createMockPeerState(
265
- {
266
- id: "peer1",
267
- role: "server",
268
- },
269
- async () => {
270
- retries++;
271
- state.markNotFoundInPeer("peer1");
272
-
273
- if (retries === 2) {
274
- setTimeout(() => {
275
- state.markAvailable(createMockCoValueCore(mockCoValueId), "peer1");
276
- }, 100);
277
- }
278
- },
279
- );
280
-
281
- const mockPeers = [peer1] as unknown as PeerState[];
282
-
283
- const state = new CoValueState(mockCoValueId);
284
- const loadPromise = state.loadFromPeers(mockPeers);
285
-
286
- // Should attempt CO_VALUE_LOADING_CONFIG.MAX_RETRIES retries
287
- for (let i = 0; i < CO_VALUE_LOADING_CONFIG.MAX_RETRIES + 1; i++) {
288
- await vi.runAllTimersAsync();
289
- }
290
-
291
- await loadPromise;
292
-
293
- expect(peer1.pushOutgoingMessage).toHaveBeenCalledTimes(2);
294
- expect(state.highLevelState).toBe("available");
295
- await expect(state.getCoValue()).resolves.toEqual({ id: mockCoValueId });
296
- vi.useRealTimers();
297
- });
298
-
299
153
  test("should have a coValue as value property when becomes available after that have been marked as unavailable", async () => {
300
154
  vi.useFakeTimers();
301
155
 
@@ -314,57 +168,13 @@ describe("CoValueState", () => {
314
168
  const state = new CoValueState(mockCoValueId);
315
169
  const loadPromise = state.loadFromPeers(mockPeers);
316
170
 
317
- // Should attempt CO_VALUE_LOADING_CONFIG.MAX_RETRIES retries
318
- for (let i = 0; i < CO_VALUE_LOADING_CONFIG.MAX_RETRIES; i++) {
319
- await vi.runAllTimersAsync();
320
- }
171
+ await vi.runAllTimersAsync();
321
172
 
322
173
  state.internalMarkMagicallyAvailable(createMockCoValueCore(mockCoValueId));
323
174
 
324
175
  await loadPromise;
325
176
 
326
- expect(peer1.pushOutgoingMessage).toHaveBeenCalledTimes(
327
- CO_VALUE_LOADING_CONFIG.MAX_RETRIES,
328
- );
329
- expect(state.highLevelState).toBe("available");
330
- await expect(state.getCoValue()).resolves.toEqual({ id: mockCoValueId });
331
-
332
- vi.useRealTimers();
333
- });
334
-
335
- test("should stop retrying when value becomes available", async () => {
336
- vi.useFakeTimers();
337
-
338
- mockMaxRetries(5);
339
-
340
- let run = 1;
341
-
342
- const peer1 = createMockPeerState(
343
- {
344
- id: "peer1",
345
- role: "server",
346
- },
347
- async () => {
348
- if (run > 2) {
349
- state.markAvailable(createMockCoValueCore(mockCoValueId), "peer1");
350
- } else {
351
- state.markNotFoundInPeer("peer1");
352
- run++;
353
- }
354
- },
355
- );
356
-
357
- const mockPeers = [peer1] as unknown as PeerState[];
358
-
359
- const state = new CoValueState(mockCoValueId);
360
- const loadPromise = state.loadFromPeers(mockPeers);
361
-
362
- for (let i = 0; i < CO_VALUE_LOADING_CONFIG.MAX_RETRIES; i++) {
363
- await vi.runAllTimersAsync();
364
- }
365
- await loadPromise;
366
-
367
- expect(peer1.pushOutgoingMessage).toHaveBeenCalledTimes(3);
177
+ expect(peer1.pushOutgoingMessage).toHaveBeenCalledTimes(1);
368
178
  expect(state.highLevelState).toBe("available");
369
179
  await expect(state.getCoValue()).resolves.toEqual({ id: mockCoValueId });
370
180
 
@@ -398,9 +208,7 @@ describe("CoValueState", () => {
398
208
  const state = new CoValueState(mockCoValueId);
399
209
  const loadPromise = state.loadFromPeers([peer1, peer2]);
400
210
 
401
- for (let i = 0; i < CO_VALUE_LOADING_CONFIG.MAX_RETRIES; i++) {
402
- await vi.runAllTimersAsync();
403
- }
211
+ await vi.runAllTimersAsync();
404
212
  await loadPromise;
405
213
 
406
214
  expect(peer1.pushOutgoingMessage).toHaveBeenCalledTimes(1);
@@ -444,9 +252,7 @@ describe("CoValueState", () => {
444
252
  const state = new CoValueState(mockCoValueId);
445
253
  const loadPromise = state.loadFromPeers([peer1, peer2]);
446
254
 
447
- for (let i = 0; i < CO_VALUE_LOADING_CONFIG.MAX_RETRIES; i++) {
448
- await vi.runAllTimersAsync();
449
- }
255
+ await vi.runAllTimersAsync();
450
256
  await loadPromise;
451
257
 
452
258
  expect(peer1.pushOutgoingMessage).toHaveBeenCalledTimes(0);
@@ -472,14 +278,10 @@ describe("CoValueState", () => {
472
278
  const state = new CoValueState(mockCoValueId);
473
279
  const loadPromise = state.loadFromPeers([peer1]);
474
280
 
475
- for (let i = 0; i < CO_VALUE_LOADING_CONFIG.MAX_RETRIES * 2; i++) {
476
- await vi.runAllTimersAsync();
477
- }
281
+ await vi.runAllTimersAsync();
478
282
  await loadPromise;
479
283
 
480
- expect(peer1.pushOutgoingMessage).toHaveBeenCalledTimes(
481
- CO_VALUE_LOADING_CONFIG.MAX_RETRIES,
482
- );
284
+ expect(peer1.pushOutgoingMessage).toHaveBeenCalledTimes(1);
483
285
 
484
286
  expect(state.highLevelState).toBe("unavailable");
485
287
  await expect(state.getCoValue()).resolves.toEqual("unavailable");
@@ -45,6 +45,53 @@ describe("loading coValues from server", () => {
45
45
  `);
46
46
  });
47
47
 
48
+ test("unavailable coValue retry", async () => {
49
+ const client = setupTestNode();
50
+ const client2 = setupTestNode();
51
+
52
+ client2.connectToSyncServer({
53
+ ourName: "client2",
54
+ });
55
+
56
+ const group = client.node.createGroup();
57
+ const map = group.createMap();
58
+ map.set("hello", "world", "trusting");
59
+
60
+ const promise = loadCoValueOrFail(client2.node, map.id);
61
+
62
+ await new Promise((resolve) => setTimeout(resolve, 1));
63
+
64
+ client.connectToSyncServer();
65
+
66
+ const mapOnClient2 = await promise;
67
+
68
+ expect(mapOnClient2.get("hello")).toEqual("world");
69
+
70
+ expect(
71
+ SyncMessagesLog.getMessages({
72
+ Group: group.core,
73
+ Map: map.core,
74
+ }),
75
+ ).toMatchInlineSnapshot(`
76
+ [
77
+ "client2 -> server | LOAD Map sessions: empty",
78
+ "server -> client2 | KNOWN Map sessions: empty",
79
+ "client -> server | LOAD Group sessions: header/3",
80
+ "client -> server | LOAD Map sessions: header/1",
81
+ "server -> client | KNOWN Group sessions: empty",
82
+ "client -> server | CONTENT Group header: true new: After: 0 New: 3",
83
+ "server -> client | KNOWN Map sessions: empty",
84
+ "client -> server | CONTENT Map header: true new: After: 0 New: 1",
85
+ "server -> client | KNOWN Group sessions: header/3",
86
+ "server -> client | KNOWN Map sessions: header/1",
87
+ "server -> client2 | CONTENT Group header: true new: After: 0 New: 3",
88
+ "client2 -> server | KNOWN Group sessions: header/3",
89
+ "server -> client2 | CONTENT Map header: true new: After: 0 New: 1",
90
+ "client2 -> server | KNOWN Map sessions: header/1",
91
+ ]
92
+ `);
93
+ });
94
+
48
95
  test("coValue with parent groups loading", async () => {
49
96
  const client = setupTestNode({
50
97
  connected: true,
@@ -327,5 +374,35 @@ describe("loading coValues from server", () => {
327
374
  `);
328
375
  });
329
376
 
330
- test.todo("should mark the coValue as unavailable if the peer is closed");
377
+ test("should mark the coValue as unavailable if the peer is closed", async () => {
378
+ const client = setupTestNode();
379
+ const { peerState } = client.connectToSyncServer();
380
+
381
+ const group = jazzCloud.node.createGroup();
382
+ group.addMember("everyone", "writer");
383
+
384
+ const map = group.createMap({
385
+ test: "value",
386
+ });
387
+
388
+ const promise = client.node.load(map.id);
389
+
390
+ // Close the peer connection
391
+ peerState.gracefulShutdown();
392
+
393
+ expect(await promise).toEqual("unavailable");
394
+
395
+ expect(
396
+ SyncMessagesLog.getMessages({
397
+ Group: group.core,
398
+ Map: map.core,
399
+ }),
400
+ ).toMatchInlineSnapshot(`
401
+ [
402
+ "client -> server | LOAD Map sessions: empty",
403
+ "server -> client | CONTENT Group header: true new: After: 0 New: 5",
404
+ "server -> client | CONTENT Map header: true new: After: 0 New: 1",
405
+ ]
406
+ `);
407
+ });
331
408
  });
@@ -3,6 +3,7 @@ import { beforeEach, describe, expect, test, vi } from "vitest";
3
3
  import { expectMap } from "../coValue";
4
4
  import {
5
5
  SyncMessagesLog,
6
+ blockMessageTypeOnOutgoingPeer,
6
7
  loadCoValueOrFail,
7
8
  setupTestNode,
8
9
  waitFor,
@@ -322,4 +323,70 @@ describe("multiple clients syncing with the a cloud-like server mesh", () => {
322
323
  ]
323
324
  `);
324
325
  });
326
+
327
+ test("load returns the coValue as soon as one of the peers return the content", async () => {
328
+ const client = setupTestNode();
329
+ const coreServer = setupTestNode({
330
+ isSyncServer: true,
331
+ });
332
+
333
+ const { peerOnServer } = client.connectToSyncServer({
334
+ syncServerName: "core",
335
+ });
336
+
337
+ const storage = setupTestNode();
338
+
339
+ const { peer: storagePeer } = client.connectToSyncServer({
340
+ syncServerName: "storage",
341
+ syncServer: storage.node,
342
+ });
343
+
344
+ storagePeer.priority = 100;
345
+
346
+ const group = coreServer.node.createGroup();
347
+ const map = group.createMap();
348
+
349
+ map.set("hello", "world", "trusting");
350
+
351
+ const { peerState } = storage.connectToSyncServer({
352
+ ourName: "storage-of-client",
353
+ syncServerName: "core",
354
+ });
355
+
356
+ await loadCoValueOrFail(storage.node, map.id);
357
+
358
+ peerState.gracefulShutdown();
359
+
360
+ SyncMessagesLog.clear();
361
+
362
+ await new Promise((resolve) => setTimeout(resolve, 100));
363
+
364
+ map.set("hello", "updated", "trusting");
365
+
366
+ // Block the content message from the core peer to simulate the delay on response
367
+ blockMessageTypeOnOutgoingPeer(peerOnServer, "content");
368
+
369
+ const mapOnClient = await loadCoValueOrFail(client.node, map.id);
370
+
371
+ expect(
372
+ SyncMessagesLog.getMessages({
373
+ Group: group.core,
374
+ Map: map.core,
375
+ }),
376
+ ).toMatchInlineSnapshot(`
377
+ [
378
+ "client -> storage | LOAD Map sessions: empty",
379
+ "storage -> client | CONTENT Group header: true new: After: 0 New: 3",
380
+ "client -> storage | KNOWN Group sessions: header/3",
381
+ "storage -> client | CONTENT Map header: true new: After: 0 New: 1",
382
+ "client -> core | CONTENT Group header: true new: After: 0 New: 3",
383
+ "client -> storage | KNOWN Map sessions: header/1",
384
+ "core -> client | KNOWN Group sessions: header/3",
385
+ "client -> core | LOAD Map sessions: header/1",
386
+ "client -> core | CONTENT Map header: true new: After: 0 New: 1",
387
+ ]
388
+ `);
389
+
390
+ expect(mapOnClient.get("hello")).toEqual("world");
391
+ });
325
392
  });