cojson 0.8.38 → 0.8.39
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.
- package/CHANGELOG.md +7 -0
- package/dist/native/PeerState.js +11 -2
- package/dist/native/PeerState.js.map +1 -1
- package/dist/native/{SyncStateSubscriptionManager.js → SyncStateManager.js} +35 -24
- package/dist/native/SyncStateManager.js.map +1 -0
- package/dist/native/coValueCore.js +3 -0
- package/dist/native/coValueCore.js.map +1 -1
- package/dist/native/exports.js.map +1 -1
- package/dist/native/sync.js +34 -10
- package/dist/native/sync.js.map +1 -1
- package/dist/web/PeerState.js +11 -2
- package/dist/web/PeerState.js.map +1 -1
- package/dist/web/{SyncStateSubscriptionManager.js → SyncStateManager.js} +35 -24
- package/dist/web/SyncStateManager.js.map +1 -0
- package/dist/web/coValueCore.js +3 -0
- package/dist/web/coValueCore.js.map +1 -1
- package/dist/web/exports.js.map +1 -1
- package/dist/web/sync.js +34 -10
- package/dist/web/sync.js.map +1 -1
- package/package.json +1 -1
- package/src/PeerState.ts +12 -2
- package/src/{SyncStateSubscriptionManager.ts → SyncStateManager.ts} +48 -35
- package/src/coValueCore.ts +6 -0
- package/src/exports.ts +2 -1
- package/src/sync.ts +57 -23
- package/src/tests/PeerState.test.ts +49 -0
- package/src/tests/PriorityBasedMessageQueue.test.ts +6 -73
- package/src/tests/{SyncStateSubscriptionManager.test.ts → SyncStateManager.test.ts} +109 -25
- package/src/tests/group.test.ts +6 -9
- package/src/tests/sync.test.ts +112 -71
- package/src/tests/testUtils.ts +108 -4
- package/dist/native/SyncStateSubscriptionManager.js.map +0 -1
- package/dist/web/SyncStateSubscriptionManager.js.map +0 -1
|
@@ -188,4 +188,53 @@ describe("PeerState", () => {
|
|
|
188
188
|
expect(knownStatesSpy).toHaveBeenCalledWith(action);
|
|
189
189
|
expect(optimisticKnownStatesSpy).toHaveBeenCalledWith(action);
|
|
190
190
|
});
|
|
191
|
+
|
|
192
|
+
test("should use same reference for knownStates and optimisticKnownStates for storage peers", () => {
|
|
193
|
+
const mockStoragePeer: Peer = {
|
|
194
|
+
id: "test-storage-peer",
|
|
195
|
+
role: "storage",
|
|
196
|
+
priority: 1,
|
|
197
|
+
crashOnClose: false,
|
|
198
|
+
incoming: (async function* () {})(),
|
|
199
|
+
outgoing: {
|
|
200
|
+
push: vi.fn().mockResolvedValue(undefined),
|
|
201
|
+
close: vi.fn(),
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
const peerState = new PeerState(mockStoragePeer, undefined);
|
|
205
|
+
|
|
206
|
+
// Verify they are the same reference
|
|
207
|
+
expect(peerState.knownStates).toBe(peerState.optimisticKnownStates);
|
|
208
|
+
|
|
209
|
+
// Verify that dispatching only updates one state
|
|
210
|
+
const knownStatesSpy = vi.spyOn(peerState.knownStates, "dispatch");
|
|
211
|
+
const optimisticKnownStatesSpy = vi.spyOn(
|
|
212
|
+
peerState.optimisticKnownStates,
|
|
213
|
+
"dispatch",
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
const action: PeerKnownStateActions = {
|
|
217
|
+
type: "SET",
|
|
218
|
+
id: "co_z1",
|
|
219
|
+
value: {
|
|
220
|
+
id: "co_z1",
|
|
221
|
+
header: false,
|
|
222
|
+
sessions: {},
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
peerState.dispatchToKnownStates(action);
|
|
226
|
+
|
|
227
|
+
// Only one dispatch should happen since they're the same reference
|
|
228
|
+
expect(knownStatesSpy).toHaveBeenCalledTimes(1);
|
|
229
|
+
expect(knownStatesSpy).toHaveBeenCalledWith(action);
|
|
230
|
+
expect(optimisticKnownStatesSpy).toHaveBeenCalledTimes(1);
|
|
231
|
+
expect(optimisticKnownStatesSpy).toHaveBeenCalledWith(action);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("should use separate references for knownStates and optimisticKnownStates for non-storage peers", () => {
|
|
235
|
+
const { peerState } = setup(); // Uses a regular peer
|
|
236
|
+
|
|
237
|
+
// Verify they are different references
|
|
238
|
+
expect(peerState.knownStates).not.toBe(peerState.optimisticKnownStates);
|
|
239
|
+
});
|
|
191
240
|
});
|
|
@@ -1,88 +1,21 @@
|
|
|
1
|
-
import { metrics } from "@opentelemetry/api";
|
|
2
|
-
import {
|
|
3
|
-
AggregationTemporality,
|
|
4
|
-
InMemoryMetricExporter,
|
|
5
|
-
MeterProvider,
|
|
6
|
-
MetricReader,
|
|
7
|
-
type MetricReaderOptions,
|
|
8
|
-
type PushMetricExporter,
|
|
9
|
-
} from "@opentelemetry/sdk-metrics";
|
|
10
1
|
import { afterEach, describe, expect, test } from "vitest";
|
|
11
2
|
import { PriorityBasedMessageQueue } from "../PriorityBasedMessageQueue.js";
|
|
12
3
|
import { CO_VALUE_PRIORITY } from "../priority.js";
|
|
13
4
|
import type { SyncMessage } from "../sync.js";
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* This is a test metric reader that uses an in-memory metric exporter and exposes a method to get the value of a metric given its name and attributes.
|
|
21
|
-
*
|
|
22
|
-
* This is useful for testing the values of metrics that are collected by the SDK.
|
|
23
|
-
*
|
|
24
|
-
* TODO: We could move this to a separate file and make it a utility class that can be used in other tests.
|
|
25
|
-
* TODO: We may want to rethink how we access metrics (see `getMetricValue` method) to make it more flexible.
|
|
26
|
-
*/
|
|
27
|
-
class TestMetricReader extends MetricReader {
|
|
28
|
-
private _exporter = new InMemoryMetricExporter(
|
|
29
|
-
AggregationTemporality.CUMULATIVE,
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
protected onShutdown(): Promise<void> {
|
|
33
|
-
throw new Error("Method not implemented.");
|
|
34
|
-
}
|
|
35
|
-
protected onForceFlush(): Promise<void> {
|
|
36
|
-
throw new Error("Method not implemented.");
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async getMetricValue(
|
|
40
|
-
name: string,
|
|
41
|
-
attributes: { [key: string]: string | number } = {},
|
|
42
|
-
) {
|
|
43
|
-
await this.collectAndExport();
|
|
44
|
-
const metric = this._exporter
|
|
45
|
-
.getMetrics()[0]
|
|
46
|
-
?.scopeMetrics[0]?.metrics.find((m) => m.descriptor.name === name);
|
|
47
|
-
|
|
48
|
-
const dp = metric?.dataPoints.find(
|
|
49
|
-
(dp) => JSON.stringify(dp.attributes) === JSON.stringify(attributes),
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
this._exporter.reset();
|
|
53
|
-
|
|
54
|
-
return dp?.value;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async collectAndExport(): Promise<void> {
|
|
58
|
-
const result = await this.collect();
|
|
59
|
-
await new Promise<void>((resolve, reject) => {
|
|
60
|
-
this._exporter.export(result.resourceMetrics, (result) => {
|
|
61
|
-
if (result.error != null) {
|
|
62
|
-
reject(result.error);
|
|
63
|
-
} else {
|
|
64
|
-
resolve();
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
}
|
|
5
|
+
import {
|
|
6
|
+
createTestMetricReader,
|
|
7
|
+
tearDownTestMetricReader,
|
|
8
|
+
} from "./testUtils.js";
|
|
70
9
|
|
|
71
10
|
function setup() {
|
|
72
|
-
const metricReader =
|
|
73
|
-
metrics.setGlobalMeterProvider(
|
|
74
|
-
new MeterProvider({
|
|
75
|
-
readers: [metricReader],
|
|
76
|
-
}),
|
|
77
|
-
);
|
|
78
|
-
|
|
11
|
+
const metricReader = createTestMetricReader();
|
|
79
12
|
const queue = new PriorityBasedMessageQueue(CO_VALUE_PRIORITY.MEDIUM);
|
|
80
13
|
return { queue, metricReader };
|
|
81
14
|
}
|
|
82
15
|
|
|
83
16
|
describe("PriorityBasedMessageQueue", () => {
|
|
84
17
|
afterEach(() => {
|
|
85
|
-
|
|
18
|
+
tearDownTestMetricReader();
|
|
86
19
|
});
|
|
87
20
|
|
|
88
21
|
test("should initialize with correct properties", () => {
|
|
@@ -2,12 +2,19 @@ import { describe, expect, onTestFinished, test, vi } from "vitest";
|
|
|
2
2
|
import {
|
|
3
3
|
GlobalSyncStateListenerCallback,
|
|
4
4
|
PeerSyncStateListenerCallback,
|
|
5
|
-
} from "../
|
|
5
|
+
} from "../SyncStateManager.js";
|
|
6
|
+
import { accountHeaderForInitialAgentSecret } from "../coValues/account.js";
|
|
6
7
|
import { connectedPeers } from "../streamUtils.js";
|
|
7
8
|
import { emptyKnownState } from "../sync.js";
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
import {
|
|
10
|
+
blockMessageTypeOnOutgoingPeer,
|
|
11
|
+
createTestNode,
|
|
12
|
+
createTwoConnectedNodes,
|
|
13
|
+
loadCoValueOrFail,
|
|
14
|
+
waitFor,
|
|
15
|
+
} from "./testUtils.js";
|
|
16
|
+
|
|
17
|
+
describe("SyncStateManager", () => {
|
|
11
18
|
test("subscribeToUpdates receives updates when peer state changes", async () => {
|
|
12
19
|
// Setup nodes
|
|
13
20
|
const client = createTestNode();
|
|
@@ -31,7 +38,7 @@ describe("SyncStateSubscriptionManager", () => {
|
|
|
31
38
|
client.syncManager.addPeer(jazzCloudAsPeer);
|
|
32
39
|
jazzCloud.syncManager.addPeer(clientAsPeer);
|
|
33
40
|
|
|
34
|
-
const subscriptionManager = client.syncManager.
|
|
41
|
+
const subscriptionManager = client.syncManager.syncState;
|
|
35
42
|
|
|
36
43
|
const updateSpy: GlobalSyncStateListenerCallback = vi.fn();
|
|
37
44
|
const unsubscribe = subscriptionManager.subscribeToUpdates(updateSpy);
|
|
@@ -41,14 +48,14 @@ describe("SyncStateSubscriptionManager", () => {
|
|
|
41
48
|
expect(updateSpy).toHaveBeenCalledWith(
|
|
42
49
|
"jazzCloudConnection",
|
|
43
50
|
emptyKnownState(map.core.id),
|
|
44
|
-
{
|
|
51
|
+
{ uploaded: false },
|
|
45
52
|
);
|
|
46
53
|
|
|
47
54
|
await waitFor(() => {
|
|
48
|
-
return subscriptionManager.
|
|
55
|
+
return subscriptionManager.getCurrentSyncState(
|
|
49
56
|
"jazzCloudConnection",
|
|
50
57
|
map.core.id,
|
|
51
|
-
);
|
|
58
|
+
).uploaded;
|
|
52
59
|
});
|
|
53
60
|
|
|
54
61
|
expect(updateSpy).toHaveBeenCalledWith(
|
|
@@ -56,7 +63,7 @@ describe("SyncStateSubscriptionManager", () => {
|
|
|
56
63
|
client.syncManager.peers["jazzCloudConnection"]!.knownStates.get(
|
|
57
64
|
map.core.id,
|
|
58
65
|
)!,
|
|
59
|
-
{
|
|
66
|
+
{ uploaded: true },
|
|
60
67
|
);
|
|
61
68
|
|
|
62
69
|
// Cleanup
|
|
@@ -92,7 +99,7 @@ describe("SyncStateSubscriptionManager", () => {
|
|
|
92
99
|
client.syncManager.addPeer(clientStoragePeer);
|
|
93
100
|
jazzCloud.syncManager.addPeer(clientAsPeer);
|
|
94
101
|
|
|
95
|
-
const subscriptionManager = client.syncManager.
|
|
102
|
+
const subscriptionManager = client.syncManager.syncState;
|
|
96
103
|
|
|
97
104
|
const updateToJazzCloudSpy: PeerSyncStateListenerCallback = vi.fn();
|
|
98
105
|
const updateToStorageSpy: PeerSyncStateListenerCallback = vi.fn();
|
|
@@ -114,26 +121,26 @@ describe("SyncStateSubscriptionManager", () => {
|
|
|
114
121
|
|
|
115
122
|
expect(updateToJazzCloudSpy).toHaveBeenCalledWith(
|
|
116
123
|
emptyKnownState(map.core.id),
|
|
117
|
-
{
|
|
124
|
+
{ uploaded: false },
|
|
118
125
|
);
|
|
119
126
|
|
|
120
127
|
await waitFor(() => {
|
|
121
|
-
return subscriptionManager.
|
|
128
|
+
return subscriptionManager.getCurrentSyncState(
|
|
122
129
|
"jazzCloudConnection",
|
|
123
130
|
map.core.id,
|
|
124
|
-
);
|
|
131
|
+
).uploaded;
|
|
125
132
|
});
|
|
126
133
|
|
|
127
134
|
expect(updateToJazzCloudSpy).toHaveBeenLastCalledWith(
|
|
128
135
|
client.syncManager.peers["jazzCloudConnection"]!.knownStates.get(
|
|
129
136
|
map.core.id,
|
|
130
137
|
)!,
|
|
131
|
-
{
|
|
138
|
+
{ uploaded: true },
|
|
132
139
|
);
|
|
133
140
|
|
|
134
141
|
expect(updateToStorageSpy).toHaveBeenLastCalledWith(
|
|
135
142
|
emptyKnownState(map.core.id),
|
|
136
|
-
{
|
|
143
|
+
{ uploaded: false },
|
|
137
144
|
);
|
|
138
145
|
});
|
|
139
146
|
|
|
@@ -162,27 +169,27 @@ describe("SyncStateSubscriptionManager", () => {
|
|
|
162
169
|
|
|
163
170
|
await client.syncManager.actuallySyncCoValue(map.core);
|
|
164
171
|
|
|
165
|
-
const subscriptionManager = client.syncManager.
|
|
172
|
+
const subscriptionManager = client.syncManager.syncState;
|
|
166
173
|
|
|
167
174
|
expect(
|
|
168
|
-
subscriptionManager.
|
|
175
|
+
subscriptionManager.getCurrentSyncState(
|
|
169
176
|
"jazzCloudConnection",
|
|
170
177
|
map.core.id,
|
|
171
|
-
),
|
|
178
|
+
).uploaded,
|
|
172
179
|
).toBe(false);
|
|
173
180
|
|
|
174
181
|
await waitFor(() => {
|
|
175
|
-
return subscriptionManager.
|
|
182
|
+
return subscriptionManager.getCurrentSyncState(
|
|
176
183
|
"jazzCloudConnection",
|
|
177
184
|
map.core.id,
|
|
178
|
-
);
|
|
185
|
+
).uploaded;
|
|
179
186
|
});
|
|
180
187
|
|
|
181
188
|
expect(
|
|
182
|
-
subscriptionManager.
|
|
189
|
+
subscriptionManager.getCurrentSyncState(
|
|
183
190
|
"jazzCloudConnection",
|
|
184
191
|
map.core.id,
|
|
185
|
-
),
|
|
192
|
+
).uploaded,
|
|
186
193
|
).toBe(true);
|
|
187
194
|
});
|
|
188
195
|
|
|
@@ -209,7 +216,7 @@ describe("SyncStateSubscriptionManager", () => {
|
|
|
209
216
|
client.syncManager.addPeer(jazzCloudAsPeer);
|
|
210
217
|
jazzCloud.syncManager.addPeer(clientAsPeer);
|
|
211
218
|
|
|
212
|
-
const subscriptionManager = client.syncManager.
|
|
219
|
+
const subscriptionManager = client.syncManager.syncState;
|
|
213
220
|
const anyUpdateSpy = vi.fn();
|
|
214
221
|
const unsubscribe1 = subscriptionManager.subscribeToUpdates(anyUpdateSpy);
|
|
215
222
|
const unsubscribe2 = subscriptionManager.subscribeToPeerUpdates(
|
|
@@ -225,12 +232,89 @@ describe("SyncStateSubscriptionManager", () => {
|
|
|
225
232
|
anyUpdateSpy.mockClear();
|
|
226
233
|
|
|
227
234
|
await waitFor(() => {
|
|
228
|
-
return
|
|
235
|
+
return subscriptionManager.getCurrentSyncState(
|
|
229
236
|
"jazzCloudConnection",
|
|
230
237
|
map.core.id,
|
|
231
|
-
);
|
|
238
|
+
).uploaded;
|
|
232
239
|
});
|
|
233
240
|
|
|
234
241
|
expect(anyUpdateSpy).not.toHaveBeenCalled();
|
|
235
242
|
});
|
|
243
|
+
|
|
244
|
+
test("getCurrentSyncState should return the correct state", async () => {
|
|
245
|
+
// Setup nodes
|
|
246
|
+
const {
|
|
247
|
+
node1: clientNode,
|
|
248
|
+
node2: serverNode,
|
|
249
|
+
node1ToNode2Peer: clientToServerPeer,
|
|
250
|
+
node2ToNode1Peer: serverToClientPeer,
|
|
251
|
+
} = await createTwoConnectedNodes("client", "server");
|
|
252
|
+
|
|
253
|
+
// Create test data
|
|
254
|
+
const group = clientNode.node.createGroup();
|
|
255
|
+
const map = group.createMap();
|
|
256
|
+
map.set("key1", "value1", "trusting");
|
|
257
|
+
group.addMember("everyone", "writer");
|
|
258
|
+
|
|
259
|
+
// Initially should not be synced
|
|
260
|
+
expect(
|
|
261
|
+
clientNode.node.syncManager.syncState.getCurrentSyncState(
|
|
262
|
+
clientToServerPeer.id,
|
|
263
|
+
map.core.id,
|
|
264
|
+
),
|
|
265
|
+
).toEqual({ uploaded: false });
|
|
266
|
+
|
|
267
|
+
// Wait for full sync
|
|
268
|
+
await map.core.waitForSync();
|
|
269
|
+
|
|
270
|
+
expect(
|
|
271
|
+
clientNode.node.syncManager.syncState.getCurrentSyncState(
|
|
272
|
+
clientToServerPeer.id,
|
|
273
|
+
map.core.id,
|
|
274
|
+
),
|
|
275
|
+
).toEqual({ uploaded: true });
|
|
276
|
+
|
|
277
|
+
const mapOnServer = await loadCoValueOrFail(serverNode.node, map.id);
|
|
278
|
+
|
|
279
|
+
// Block the content messages so the client won't fully sync immediately
|
|
280
|
+
const outgoing = blockMessageTypeOnOutgoingPeer(
|
|
281
|
+
serverToClientPeer,
|
|
282
|
+
"content",
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
mapOnServer.set("key2", "value2", "trusting");
|
|
286
|
+
|
|
287
|
+
expect(
|
|
288
|
+
clientNode.node.syncManager.syncState.getCurrentSyncState(
|
|
289
|
+
clientToServerPeer.id,
|
|
290
|
+
map.core.id,
|
|
291
|
+
),
|
|
292
|
+
).toEqual({ uploaded: true });
|
|
293
|
+
|
|
294
|
+
expect(
|
|
295
|
+
serverNode.node.syncManager.syncState.getCurrentSyncState(
|
|
296
|
+
serverToClientPeer.id,
|
|
297
|
+
map.core.id,
|
|
298
|
+
),
|
|
299
|
+
).toEqual({ uploaded: false });
|
|
300
|
+
|
|
301
|
+
await outgoing.sendBlockedMessages();
|
|
302
|
+
outgoing.unblock();
|
|
303
|
+
|
|
304
|
+
await mapOnServer.core.waitForSync();
|
|
305
|
+
|
|
306
|
+
expect(
|
|
307
|
+
clientNode.node.syncManager.syncState.getCurrentSyncState(
|
|
308
|
+
clientToServerPeer.id,
|
|
309
|
+
map.core.id,
|
|
310
|
+
),
|
|
311
|
+
).toEqual({ uploaded: true });
|
|
312
|
+
|
|
313
|
+
expect(
|
|
314
|
+
serverNode.node.syncManager.syncState.getCurrentSyncState(
|
|
315
|
+
serverToClientPeer.id,
|
|
316
|
+
map.core.id,
|
|
317
|
+
),
|
|
318
|
+
).toEqual({ uploaded: true });
|
|
319
|
+
});
|
|
236
320
|
});
|
package/src/tests/group.test.ts
CHANGED
|
@@ -87,12 +87,12 @@ test("Remove a member from a group where the admin role is inherited", async ()
|
|
|
87
87
|
// The reader should be automatically kicked out of the child group
|
|
88
88
|
await group.removeMember(node3.account);
|
|
89
89
|
|
|
90
|
-
await
|
|
90
|
+
await group.core.waitForSync();
|
|
91
91
|
|
|
92
92
|
// Update the map to check that node3 can't read updates anymore
|
|
93
93
|
map.set("test", "Hidden to node3");
|
|
94
94
|
|
|
95
|
-
await
|
|
95
|
+
await map.core.waitForSync();
|
|
96
96
|
|
|
97
97
|
// Check that the value has not been updated on node3
|
|
98
98
|
expect(mapOnNode3.get("test")).toEqual("Available to everyone");
|
|
@@ -119,16 +119,13 @@ test("An admin should be able to rotate the readKey on child groups and keep acc
|
|
|
119
119
|
const childGroup = node2.createGroup();
|
|
120
120
|
childGroup.extend(groupOnNode2);
|
|
121
121
|
|
|
122
|
-
await
|
|
123
|
-
node2ToNode1Peer.id,
|
|
124
|
-
childGroup.id,
|
|
125
|
-
);
|
|
122
|
+
await childGroup.core.waitForSync();
|
|
126
123
|
|
|
127
124
|
// The node1 account removes the reader from the group
|
|
128
125
|
// In this case we want to ensure that node1 is still able to read new coValues
|
|
129
126
|
// Even if some childs are not available when the readKey is rotated
|
|
130
127
|
await group.removeMember(node3.account);
|
|
131
|
-
await
|
|
128
|
+
await group.core.waitForSync();
|
|
132
129
|
|
|
133
130
|
const map = childGroup.createMap();
|
|
134
131
|
map.set("test", "Available to node1");
|
|
@@ -158,7 +155,7 @@ test("An admin should be able to rotate the readKey on child groups even if it w
|
|
|
158
155
|
// In this case we want to ensure that node1 is still able to read new coValues
|
|
159
156
|
// Even if some childs are not available when the readKey is rotated
|
|
160
157
|
await group.removeMember(node3.account);
|
|
161
|
-
await
|
|
158
|
+
await group.core.waitForSync();
|
|
162
159
|
|
|
163
160
|
const map = childGroup.createMap();
|
|
164
161
|
map.set("test", "Available to node1");
|
|
@@ -193,7 +190,7 @@ test("An admin should be able to rotate the readKey on child groups even if it w
|
|
|
193
190
|
// In this case we want to ensure that node1 is still able to read new coValues
|
|
194
191
|
// Even if some childs are not available when the readKey is rotated
|
|
195
192
|
await group.removeMember(node3.account);
|
|
196
|
-
await
|
|
193
|
+
await group.core.waitForSync();
|
|
197
194
|
|
|
198
195
|
const map = childGroup.createMap();
|
|
199
196
|
map.set("test", "Available to node1");
|