cojson 0.7.11 → 0.7.14
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/.turbo/turbo-build.log +2 -2
- package/.turbo/turbo-test.log +401 -421
- package/CHANGELOG.md +6 -0
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/storage/index.js +22 -30
- package/dist/storage/index.js.map +1 -1
- package/dist/streamUtils.js +27 -105
- package/dist/streamUtils.js.map +1 -1
- package/dist/sync.js +69 -85
- package/dist/sync.js.map +1 -1
- package/dist/tests/account.test.js +4 -2
- package/dist/tests/account.test.js.map +1 -1
- package/dist/tests/sync.test.js +286 -239
- package/dist/tests/sync.test.js.map +1 -1
- package/package.json +2 -3
- package/src/index.ts +18 -2
- package/src/storage/index.ts +37 -67
- package/src/streamUtils.ts +46 -156
- package/src/sync.ts +125 -118
- package/src/tests/account.test.ts +5 -2
- package/src/tests/sync.test.ts +798 -732
package/dist/tests/sync.test.js
CHANGED
|
@@ -1,45 +1,46 @@
|
|
|
1
1
|
import { expect, test } from "vitest";
|
|
2
|
-
import { newRandomSessionID } from "../coValueCore.js";
|
|
3
2
|
import { LocalNode } from "../localNode.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { connectedPeers, newStreamPair } from "../streamUtils.js";
|
|
3
|
+
import { randomAnonymousAccountAndSessionID } from "./testUtils.js";
|
|
4
|
+
import { connectedPeers, newQueuePair } from "../streamUtils.js";
|
|
7
5
|
import { stableStringify } from "../jsonStringify.js";
|
|
8
6
|
import { WasmCrypto } from "../crypto/WasmCrypto.js";
|
|
7
|
+
import { Effect, Queue, Sink, Stream } from "effect";
|
|
8
|
+
import { newRandomSessionID } from "../coValueCore.js";
|
|
9
|
+
import { expectMap } from "../coValue.js";
|
|
9
10
|
const Crypto = await WasmCrypto.create();
|
|
10
|
-
test("Node replies with initial tx and header to empty subscribe",
|
|
11
|
+
test("Node replies with initial tx and header to empty subscribe", () => Effect.gen(function* () {
|
|
11
12
|
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
12
13
|
const node = new LocalNode(admin, session, Crypto);
|
|
13
14
|
const group = node.createGroup();
|
|
14
15
|
const map = group.createMap();
|
|
15
16
|
map.set("hello", "world", "trusting");
|
|
16
|
-
const [inRx, inTx] =
|
|
17
|
-
const [outRx, outTx] =
|
|
17
|
+
const [inRx, inTx] = yield* newQueuePair();
|
|
18
|
+
const [outRx, outTx] = yield* newQueuePair();
|
|
19
|
+
const outRxQ = yield* Queue.unbounded();
|
|
20
|
+
yield* Effect.fork(Stream.run(outRx, Sink.fromQueue(outRxQ)));
|
|
18
21
|
node.syncManager.addPeer({
|
|
19
22
|
id: "test",
|
|
20
23
|
incoming: inRx,
|
|
21
24
|
outgoing: outTx,
|
|
22
25
|
role: "peer",
|
|
23
26
|
});
|
|
24
|
-
|
|
25
|
-
await writer.write({
|
|
27
|
+
yield* Queue.offer(inTx, {
|
|
26
28
|
action: "load",
|
|
27
29
|
id: map.core.id,
|
|
28
30
|
header: false,
|
|
29
31
|
sessions: {},
|
|
30
32
|
});
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
expect(mapTellKnownStateMsg.value).toEqual({
|
|
33
|
+
// expect((yield* Queue.take(outRxQ))).toMatchObject(admStateEx(admin.id));
|
|
34
|
+
expect(yield* Queue.take(outRxQ)).toMatchObject(groupStateEx(group));
|
|
35
|
+
const mapTellKnownStateMsg = yield* Queue.take(outRxQ);
|
|
36
|
+
expect(mapTellKnownStateMsg).toEqual({
|
|
36
37
|
action: "known",
|
|
37
38
|
...map.core.knownState(),
|
|
38
39
|
});
|
|
39
|
-
// expect((
|
|
40
|
-
expect(
|
|
41
|
-
const newContentMsg =
|
|
42
|
-
expect(newContentMsg
|
|
40
|
+
// expect((yield * Queue.take(outRxQ))).toMatchObject(admContEx(admin.id));
|
|
41
|
+
expect(yield* Queue.take(outRxQ)).toMatchObject(groupContentEx(group));
|
|
42
|
+
const newContentMsg = yield* Queue.take(outRxQ);
|
|
43
|
+
expect(newContentMsg).toEqual({
|
|
43
44
|
action: "content",
|
|
44
45
|
id: map.core.id,
|
|
45
46
|
header: {
|
|
@@ -55,8 +56,7 @@ test("Node replies with initial tx and header to empty subscribe", async () => {
|
|
|
55
56
|
newTransactions: [
|
|
56
57
|
{
|
|
57
58
|
privacy: "trusting",
|
|
58
|
-
madeAt: map.core.sessionLogs.get(node.currentSessionID)
|
|
59
|
-
.transactions[0].madeAt,
|
|
59
|
+
madeAt: map.core.sessionLogs.get(node.currentSessionID).transactions[0].madeAt,
|
|
60
60
|
changes: stableStringify([
|
|
61
61
|
{
|
|
62
62
|
op: "set",
|
|
@@ -66,29 +66,29 @@ test("Node replies with initial tx and header to empty subscribe", async () => {
|
|
|
66
66
|
]),
|
|
67
67
|
},
|
|
68
68
|
],
|
|
69
|
-
lastSignature: map.core.sessionLogs.get(node.currentSessionID)
|
|
70
|
-
.lastSignature,
|
|
69
|
+
lastSignature: map.core.sessionLogs.get(node.currentSessionID).lastSignature,
|
|
71
70
|
},
|
|
72
71
|
},
|
|
73
72
|
});
|
|
74
|
-
});
|
|
75
|
-
test("Node replies with only new tx to subscribe with some known state",
|
|
73
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
74
|
+
test("Node replies with only new tx to subscribe with some known state", () => Effect.gen(function* () {
|
|
76
75
|
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
77
76
|
const node = new LocalNode(admin, session, Crypto);
|
|
78
77
|
const group = node.createGroup();
|
|
79
78
|
const map = group.createMap();
|
|
80
79
|
map.set("hello", "world", "trusting");
|
|
81
80
|
map.set("goodbye", "world", "trusting");
|
|
82
|
-
const [inRx, inTx] =
|
|
83
|
-
const [outRx, outTx] =
|
|
81
|
+
const [inRx, inTx] = yield* newQueuePair();
|
|
82
|
+
const [outRx, outTx] = yield* newQueuePair();
|
|
83
|
+
const outRxQ = yield* Queue.unbounded();
|
|
84
|
+
yield* Effect.fork(Stream.run(outRx, Sink.fromQueue(outRxQ)));
|
|
84
85
|
node.syncManager.addPeer({
|
|
85
86
|
id: "test",
|
|
86
87
|
incoming: inRx,
|
|
87
88
|
outgoing: outTx,
|
|
88
89
|
role: "peer",
|
|
89
90
|
});
|
|
90
|
-
|
|
91
|
-
await writer.write({
|
|
91
|
+
yield* Queue.offer(inTx, {
|
|
92
92
|
action: "load",
|
|
93
93
|
id: map.core.id,
|
|
94
94
|
header: true,
|
|
@@ -96,18 +96,17 @@ test("Node replies with only new tx to subscribe with some known state", async (
|
|
|
96
96
|
[node.currentSessionID]: 1,
|
|
97
97
|
},
|
|
98
98
|
});
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
expect(mapTellKnownStateMsg.value).toEqual({
|
|
99
|
+
// expect(yield* Queue.take(outRxQ)).toMatchObject(admStateEx(admin.id));
|
|
100
|
+
expect(yield* Queue.take(outRxQ)).toMatchObject(groupStateEx(group));
|
|
101
|
+
const mapTellKnownStateMsg = yield* Queue.take(outRxQ);
|
|
102
|
+
expect(mapTellKnownStateMsg).toEqual({
|
|
104
103
|
action: "known",
|
|
105
104
|
...map.core.knownState(),
|
|
106
105
|
});
|
|
107
|
-
// expect(
|
|
108
|
-
expect(
|
|
109
|
-
const mapNewContentMsg =
|
|
110
|
-
expect(mapNewContentMsg
|
|
106
|
+
// expect(yield* Queue.take(outRxQ))).toMatchObject(admContEx(admin.id));
|
|
107
|
+
expect(yield* Queue.take(outRxQ)).toMatchObject(groupContentEx(group));
|
|
108
|
+
const mapNewContentMsg = yield* Queue.take(outRxQ);
|
|
109
|
+
expect(mapNewContentMsg).toEqual({
|
|
111
110
|
action: "content",
|
|
112
111
|
id: map.core.id,
|
|
113
112
|
header: undefined,
|
|
@@ -117,8 +116,7 @@ test("Node replies with only new tx to subscribe with some known state", async (
|
|
|
117
116
|
newTransactions: [
|
|
118
117
|
{
|
|
119
118
|
privacy: "trusting",
|
|
120
|
-
madeAt: map.core.sessionLogs.get(node.currentSessionID)
|
|
121
|
-
.transactions[1].madeAt,
|
|
119
|
+
madeAt: map.core.sessionLogs.get(node.currentSessionID).transactions[1].madeAt,
|
|
122
120
|
changes: stableStringify([
|
|
123
121
|
{
|
|
124
122
|
op: "set",
|
|
@@ -128,28 +126,28 @@ test("Node replies with only new tx to subscribe with some known state", async (
|
|
|
128
126
|
]),
|
|
129
127
|
},
|
|
130
128
|
],
|
|
131
|
-
lastSignature: map.core.sessionLogs.get(node.currentSessionID)
|
|
132
|
-
.lastSignature,
|
|
129
|
+
lastSignature: map.core.sessionLogs.get(node.currentSessionID).lastSignature,
|
|
133
130
|
},
|
|
134
131
|
},
|
|
135
132
|
});
|
|
136
|
-
});
|
|
133
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
137
134
|
test.todo("TODO: node only replies with new tx to subscribe with some known state, even in the depended on coValues");
|
|
138
|
-
test("After subscribing, node sends own known state and new txs to peer",
|
|
135
|
+
test("After subscribing, node sends own known state and new txs to peer", () => Effect.gen(function* () {
|
|
139
136
|
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
140
137
|
const node = new LocalNode(admin, session, Crypto);
|
|
141
138
|
const group = node.createGroup();
|
|
142
139
|
const map = group.createMap();
|
|
143
|
-
const [inRx, inTx] =
|
|
144
|
-
const [outRx, outTx] =
|
|
140
|
+
const [inRx, inTx] = yield* newQueuePair();
|
|
141
|
+
const [outRx, outTx] = yield* newQueuePair();
|
|
142
|
+
const outRxQ = yield* Queue.unbounded();
|
|
143
|
+
yield* Effect.fork(Stream.run(outRx, Sink.fromQueue(outRxQ)));
|
|
145
144
|
node.syncManager.addPeer({
|
|
146
145
|
id: "test",
|
|
147
146
|
incoming: inRx,
|
|
148
147
|
outgoing: outTx,
|
|
149
148
|
role: "peer",
|
|
150
149
|
});
|
|
151
|
-
|
|
152
|
-
await writer.write({
|
|
150
|
+
yield* Queue.offer(inTx, {
|
|
153
151
|
action: "load",
|
|
154
152
|
id: map.core.id,
|
|
155
153
|
header: false,
|
|
@@ -157,26 +155,25 @@ test("After subscribing, node sends own known state and new txs to peer", async
|
|
|
157
155
|
[node.currentSessionID]: 0,
|
|
158
156
|
},
|
|
159
157
|
});
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
expect(mapTellKnownStateMsg.value).toEqual({
|
|
158
|
+
// expect(yield* Queue.take(outRxQ)).toMatchObject(admStateEx(admin.id));
|
|
159
|
+
expect(yield* Queue.take(outRxQ)).toMatchObject(groupStateEx(group));
|
|
160
|
+
const mapTellKnownStateMsg = yield* Queue.take(outRxQ);
|
|
161
|
+
expect(mapTellKnownStateMsg).toEqual({
|
|
165
162
|
action: "known",
|
|
166
163
|
...map.core.knownState(),
|
|
167
164
|
});
|
|
168
|
-
// expect(
|
|
169
|
-
expect(
|
|
170
|
-
const mapNewContentHeaderOnlyMsg =
|
|
171
|
-
expect(mapNewContentHeaderOnlyMsg
|
|
165
|
+
// expect(yield* Queue.take(outRxQ)).toMatchObject(admContEx(admin.id));
|
|
166
|
+
expect(yield* Queue.take(outRxQ)).toMatchObject(groupContentEx(group));
|
|
167
|
+
const mapNewContentHeaderOnlyMsg = yield* Queue.take(outRxQ);
|
|
168
|
+
expect(mapNewContentHeaderOnlyMsg).toEqual({
|
|
172
169
|
action: "content",
|
|
173
170
|
id: map.core.id,
|
|
174
171
|
header: map.core.header,
|
|
175
172
|
new: {},
|
|
176
173
|
});
|
|
177
174
|
map.set("hello", "world", "trusting");
|
|
178
|
-
const mapEditMsg1 =
|
|
179
|
-
expect(mapEditMsg1
|
|
175
|
+
const mapEditMsg1 = yield* Queue.take(outRxQ);
|
|
176
|
+
expect(mapEditMsg1).toEqual({
|
|
180
177
|
action: "content",
|
|
181
178
|
id: map.core.id,
|
|
182
179
|
new: {
|
|
@@ -185,8 +182,7 @@ test("After subscribing, node sends own known state and new txs to peer", async
|
|
|
185
182
|
newTransactions: [
|
|
186
183
|
{
|
|
187
184
|
privacy: "trusting",
|
|
188
|
-
madeAt: map.core.sessionLogs.get(node.currentSessionID)
|
|
189
|
-
.transactions[0].madeAt,
|
|
185
|
+
madeAt: map.core.sessionLogs.get(node.currentSessionID).transactions[0].madeAt,
|
|
190
186
|
changes: stableStringify([
|
|
191
187
|
{
|
|
192
188
|
op: "set",
|
|
@@ -196,14 +192,13 @@ test("After subscribing, node sends own known state and new txs to peer", async
|
|
|
196
192
|
]),
|
|
197
193
|
},
|
|
198
194
|
],
|
|
199
|
-
lastSignature: map.core.sessionLogs.get(node.currentSessionID)
|
|
200
|
-
.lastSignature,
|
|
195
|
+
lastSignature: map.core.sessionLogs.get(node.currentSessionID).lastSignature,
|
|
201
196
|
},
|
|
202
197
|
},
|
|
203
198
|
});
|
|
204
199
|
map.set("goodbye", "world", "trusting");
|
|
205
|
-
const mapEditMsg2 =
|
|
206
|
-
expect(mapEditMsg2
|
|
200
|
+
const mapEditMsg2 = yield* Queue.take(outRxQ);
|
|
201
|
+
expect(mapEditMsg2).toEqual({
|
|
207
202
|
action: "content",
|
|
208
203
|
id: map.core.id,
|
|
209
204
|
new: {
|
|
@@ -212,8 +207,7 @@ test("After subscribing, node sends own known state and new txs to peer", async
|
|
|
212
207
|
newTransactions: [
|
|
213
208
|
{
|
|
214
209
|
privacy: "trusting",
|
|
215
|
-
madeAt: map.core.sessionLogs.get(node.currentSessionID)
|
|
216
|
-
.transactions[1].madeAt,
|
|
210
|
+
madeAt: map.core.sessionLogs.get(node.currentSessionID).transactions[1].madeAt,
|
|
217
211
|
changes: stableStringify([
|
|
218
212
|
{
|
|
219
213
|
op: "set",
|
|
@@ -223,30 +217,29 @@ test("After subscribing, node sends own known state and new txs to peer", async
|
|
|
223
217
|
]),
|
|
224
218
|
},
|
|
225
219
|
],
|
|
226
|
-
lastSignature: map.core.sessionLogs.get(node.currentSessionID)
|
|
227
|
-
.lastSignature,
|
|
220
|
+
lastSignature: map.core.sessionLogs.get(node.currentSessionID).lastSignature,
|
|
228
221
|
},
|
|
229
222
|
},
|
|
230
223
|
});
|
|
231
|
-
});
|
|
232
|
-
test("Client replies with known new content to tellKnownState from server",
|
|
224
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
225
|
+
test("Client replies with known new content to tellKnownState from server", () => Effect.gen(function* () {
|
|
233
226
|
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
234
227
|
const node = new LocalNode(admin, session, Crypto);
|
|
235
228
|
const group = node.createGroup();
|
|
236
229
|
const map = group.createMap();
|
|
237
230
|
map.set("hello", "world", "trusting");
|
|
238
|
-
const [inRx, inTx] =
|
|
239
|
-
const [outRx, outTx] =
|
|
231
|
+
const [inRx, inTx] = yield* newQueuePair();
|
|
232
|
+
const [outRx, outTx] = yield* newQueuePair();
|
|
233
|
+
const outRxQ = yield* Queue.unbounded();
|
|
234
|
+
yield* Effect.fork(Stream.run(outRx, Sink.fromQueue(outRxQ)));
|
|
240
235
|
node.syncManager.addPeer({
|
|
241
236
|
id: "test",
|
|
242
237
|
incoming: inRx,
|
|
243
238
|
outgoing: outTx,
|
|
244
239
|
role: "peer",
|
|
245
240
|
});
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
const writer = inTx.getWriter();
|
|
249
|
-
await writer.write({
|
|
241
|
+
// expect(yield* Queue.take(outRxQ)).toMatchObject(groupStateEx(group));
|
|
242
|
+
yield* Queue.offer(inTx, {
|
|
250
243
|
action: "known",
|
|
251
244
|
id: map.core.id,
|
|
252
245
|
header: false,
|
|
@@ -254,17 +247,17 @@ test("Client replies with known new content to tellKnownState from server", asyn
|
|
|
254
247
|
[node.currentSessionID]: 0,
|
|
255
248
|
},
|
|
256
249
|
});
|
|
257
|
-
// expect(
|
|
258
|
-
expect(
|
|
259
|
-
const mapTellKnownStateMsg =
|
|
260
|
-
expect(mapTellKnownStateMsg
|
|
250
|
+
// expect(yield* Queue.take(outRxQ)).toMatchObject(admStateEx(admin.id));
|
|
251
|
+
expect(yield* Queue.take(outRxQ)).toMatchObject(groupStateEx(group));
|
|
252
|
+
const mapTellKnownStateMsg = yield* Queue.take(outRxQ);
|
|
253
|
+
expect(mapTellKnownStateMsg).toEqual({
|
|
261
254
|
action: "known",
|
|
262
255
|
...map.core.knownState(),
|
|
263
256
|
});
|
|
264
|
-
// expect(
|
|
265
|
-
expect(
|
|
266
|
-
const mapNewContentMsg =
|
|
267
|
-
expect(mapNewContentMsg
|
|
257
|
+
// expect(yield* Queue.take(outRxQ)).toMatchObject(admContEx(admin.id));
|
|
258
|
+
expect(yield* Queue.take(outRxQ)).toMatchObject(groupContentEx(group));
|
|
259
|
+
const mapNewContentMsg = yield* Queue.take(outRxQ);
|
|
260
|
+
expect(mapNewContentMsg).toEqual({
|
|
268
261
|
action: "content",
|
|
269
262
|
id: map.core.id,
|
|
270
263
|
header: map.core.header,
|
|
@@ -274,8 +267,7 @@ test("Client replies with known new content to tellKnownState from server", asyn
|
|
|
274
267
|
newTransactions: [
|
|
275
268
|
{
|
|
276
269
|
privacy: "trusting",
|
|
277
|
-
madeAt: map.core.sessionLogs.get(node.currentSessionID)
|
|
278
|
-
.transactions[0].madeAt,
|
|
270
|
+
madeAt: map.core.sessionLogs.get(node.currentSessionID).transactions[0].madeAt,
|
|
279
271
|
changes: stableStringify([
|
|
280
272
|
{
|
|
281
273
|
op: "set",
|
|
@@ -285,27 +277,27 @@ test("Client replies with known new content to tellKnownState from server", asyn
|
|
|
285
277
|
]),
|
|
286
278
|
},
|
|
287
279
|
],
|
|
288
|
-
lastSignature: map.core.sessionLogs.get(node.currentSessionID)
|
|
289
|
-
.lastSignature,
|
|
280
|
+
lastSignature: map.core.sessionLogs.get(node.currentSessionID).lastSignature,
|
|
290
281
|
},
|
|
291
282
|
},
|
|
292
283
|
});
|
|
293
|
-
});
|
|
294
|
-
test("No matter the optimistic known state, node respects invalid known state messages and resyncs",
|
|
284
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
285
|
+
test("No matter the optimistic known state, node respects invalid known state messages and resyncs", () => Effect.gen(function* () {
|
|
295
286
|
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
296
287
|
const node = new LocalNode(admin, session, Crypto);
|
|
297
288
|
const group = node.createGroup();
|
|
298
289
|
const map = group.createMap();
|
|
299
|
-
const [inRx, inTx] =
|
|
300
|
-
const [outRx, outTx] =
|
|
290
|
+
const [inRx, inTx] = yield* newQueuePair();
|
|
291
|
+
const [outRx, outTx] = yield* newQueuePair();
|
|
292
|
+
const outRxQ = yield* Queue.unbounded();
|
|
293
|
+
yield* Effect.fork(Stream.run(outRx, Sink.fromQueue(outRxQ)));
|
|
301
294
|
node.syncManager.addPeer({
|
|
302
295
|
id: "test",
|
|
303
296
|
incoming: inRx,
|
|
304
297
|
outgoing: outTx,
|
|
305
298
|
role: "peer",
|
|
306
299
|
});
|
|
307
|
-
|
|
308
|
-
await writer.write({
|
|
300
|
+
yield* Queue.offer(inTx, {
|
|
309
301
|
action: "load",
|
|
310
302
|
id: map.core.id,
|
|
311
303
|
header: false,
|
|
@@ -313,18 +305,17 @@ test("No matter the optimistic known state, node respects invalid known state me
|
|
|
313
305
|
[node.currentSessionID]: 0,
|
|
314
306
|
},
|
|
315
307
|
});
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
expect(mapTellKnownStateMsg.value).toEqual({
|
|
308
|
+
// expect(yield* Queue.take(outRxQ)).toMatchObject(admStateEx(admin.id));
|
|
309
|
+
expect(yield* Queue.take(outRxQ)).toMatchObject(groupStateEx(group));
|
|
310
|
+
const mapTellKnownStateMsg = yield* Queue.take(outRxQ);
|
|
311
|
+
expect(mapTellKnownStateMsg).toEqual({
|
|
321
312
|
action: "known",
|
|
322
313
|
...map.core.knownState(),
|
|
323
314
|
});
|
|
324
|
-
// expect(
|
|
325
|
-
expect(
|
|
326
|
-
const mapNewContentHeaderOnlyMsg =
|
|
327
|
-
expect(mapNewContentHeaderOnlyMsg
|
|
315
|
+
// expect(yield* Queue.take(outRxQ)).toMatchObject(admContEx(admin.id));
|
|
316
|
+
expect(yield* Queue.take(outRxQ)).toMatchObject(groupContentEx(group));
|
|
317
|
+
const mapNewContentHeaderOnlyMsg = yield* Queue.take(outRxQ);
|
|
318
|
+
expect(mapNewContentHeaderOnlyMsg).toEqual({
|
|
328
319
|
action: "content",
|
|
329
320
|
id: map.core.id,
|
|
330
321
|
header: map.core.header,
|
|
@@ -332,9 +323,9 @@ test("No matter the optimistic known state, node respects invalid known state me
|
|
|
332
323
|
});
|
|
333
324
|
map.set("hello", "world", "trusting");
|
|
334
325
|
map.set("goodbye", "world", "trusting");
|
|
335
|
-
const _mapEditMsgs =
|
|
326
|
+
const _mapEditMsgs = yield* Queue.take(outRxQ);
|
|
336
327
|
console.log("Sending correction");
|
|
337
|
-
|
|
328
|
+
yield* Queue.offer(inTx, {
|
|
338
329
|
action: "known",
|
|
339
330
|
isCorrection: true,
|
|
340
331
|
id: map.core.id,
|
|
@@ -343,8 +334,8 @@ test("No matter the optimistic known state, node respects invalid known state me
|
|
|
343
334
|
[node.currentSessionID]: 1,
|
|
344
335
|
},
|
|
345
336
|
});
|
|
346
|
-
const newContentAfterWrongAssumedState =
|
|
347
|
-
expect(newContentAfterWrongAssumedState
|
|
337
|
+
const newContentAfterWrongAssumedState = yield* Queue.take(outRxQ);
|
|
338
|
+
expect(newContentAfterWrongAssumedState).toEqual({
|
|
348
339
|
action: "content",
|
|
349
340
|
id: map.core.id,
|
|
350
341
|
header: undefined,
|
|
@@ -354,8 +345,7 @@ test("No matter the optimistic known state, node respects invalid known state me
|
|
|
354
345
|
newTransactions: [
|
|
355
346
|
{
|
|
356
347
|
privacy: "trusting",
|
|
357
|
-
madeAt: map.core.sessionLogs.get(node.currentSessionID)
|
|
358
|
-
.transactions[1].madeAt,
|
|
348
|
+
madeAt: map.core.sessionLogs.get(node.currentSessionID).transactions[1].madeAt,
|
|
359
349
|
changes: stableStringify([
|
|
360
350
|
{
|
|
361
351
|
op: "set",
|
|
@@ -365,19 +355,20 @@ test("No matter the optimistic known state, node respects invalid known state me
|
|
|
365
355
|
]),
|
|
366
356
|
},
|
|
367
357
|
],
|
|
368
|
-
lastSignature: map.core.sessionLogs.get(node.currentSessionID)
|
|
369
|
-
.lastSignature,
|
|
358
|
+
lastSignature: map.core.sessionLogs.get(node.currentSessionID).lastSignature,
|
|
370
359
|
},
|
|
371
360
|
},
|
|
372
361
|
});
|
|
373
|
-
});
|
|
374
|
-
test("If we add a peer, but it never subscribes to a coValue, it won't get any messages",
|
|
362
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
363
|
+
test("If we add a peer, but it never subscribes to a coValue, it won't get any messages", () => Effect.gen(function* () {
|
|
375
364
|
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
376
365
|
const node = new LocalNode(admin, session, Crypto);
|
|
377
366
|
const group = node.createGroup();
|
|
378
367
|
const map = group.createMap();
|
|
379
|
-
const [inRx, _inTx] =
|
|
380
|
-
const [outRx, outTx] =
|
|
368
|
+
const [inRx, _inTx] = yield* newQueuePair();
|
|
369
|
+
const [outRx, outTx] = yield* newQueuePair();
|
|
370
|
+
const outRxQ = yield* Queue.unbounded();
|
|
371
|
+
yield* Effect.fork(Stream.run(outRx, Sink.fromQueue(outRxQ)));
|
|
381
372
|
node.syncManager.addPeer({
|
|
382
373
|
id: "test",
|
|
383
374
|
incoming: inRx,
|
|
@@ -385,43 +376,46 @@ test("If we add a peer, but it never subscribes to a coValue, it won't get any m
|
|
|
385
376
|
role: "peer",
|
|
386
377
|
});
|
|
387
378
|
map.set("hello", "world", "trusting");
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
379
|
+
expect(yield* Queue.take(outRxQ).pipe(Effect.timeout(100), Effect.catch("_tag", {
|
|
380
|
+
failure: "TimeoutException",
|
|
381
|
+
onFailure: () => Effect.succeed("neverHappened"),
|
|
382
|
+
}))).toEqual("neverHappened");
|
|
383
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
384
|
+
test.todo("If we add a server peer, all updates to all coValues are sent to it, even if it doesn't subscribe", () => Effect.gen(function* () {
|
|
392
385
|
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
393
386
|
const node = new LocalNode(admin, session, Crypto);
|
|
394
387
|
const group = node.createGroup();
|
|
395
388
|
const map = group.createMap();
|
|
396
|
-
const [inRx, _inTx] =
|
|
397
|
-
const [outRx, outTx] =
|
|
389
|
+
const [inRx, _inTx] = yield* newQueuePair();
|
|
390
|
+
const [outRx, outTx] = yield* newQueuePair();
|
|
391
|
+
const outRxQ = yield* Queue.unbounded();
|
|
392
|
+
yield* Effect.fork(Stream.run(outRx, Sink.fromQueue(outRxQ)));
|
|
398
393
|
node.syncManager.addPeer({
|
|
399
394
|
id: "test",
|
|
400
395
|
incoming: inRx,
|
|
401
396
|
outgoing: outTx,
|
|
402
397
|
role: "server",
|
|
403
398
|
});
|
|
404
|
-
|
|
405
|
-
// expect((await reader.read()).value).toMatchObject({
|
|
399
|
+
// expect(yield* Queue.take(outRxQ)).toMatchObject({
|
|
406
400
|
// action: "load",
|
|
407
401
|
// id: adminID,
|
|
408
402
|
// });
|
|
409
|
-
expect(
|
|
403
|
+
expect(yield* Queue.take(outRxQ)).toMatchObject({
|
|
410
404
|
action: "load",
|
|
411
405
|
id: group.core.id,
|
|
412
406
|
});
|
|
413
|
-
const mapSubscribeMsg =
|
|
414
|
-
expect(mapSubscribeMsg
|
|
407
|
+
const mapSubscribeMsg = Queue.take(outRxQ);
|
|
408
|
+
expect(mapSubscribeMsg).toEqual({
|
|
415
409
|
action: "load",
|
|
416
410
|
id: map.core.id,
|
|
417
411
|
header: true,
|
|
418
412
|
sessions: {},
|
|
419
413
|
});
|
|
420
414
|
map.set("hello", "world", "trusting");
|
|
421
|
-
// expect(
|
|
422
|
-
expect(
|
|
423
|
-
const mapNewContentMsg =
|
|
424
|
-
expect(mapNewContentMsg
|
|
415
|
+
// expect(yield* Queue.take(outRxQ)).toMatchObject(admContEx(admin.id));
|
|
416
|
+
expect(yield* Queue.take(outRxQ)).toMatchObject(groupContentEx(group));
|
|
417
|
+
const mapNewContentMsg = Queue.take(outRxQ);
|
|
418
|
+
expect(mapNewContentMsg).toEqual({
|
|
425
419
|
action: "content",
|
|
426
420
|
id: map.core.id,
|
|
427
421
|
header: map.core.header,
|
|
@@ -445,86 +439,89 @@ test.todo("If we add a server peer, all updates to all coValues are sent to it,
|
|
|
445
439
|
},
|
|
446
440
|
},
|
|
447
441
|
});
|
|
448
|
-
});
|
|
449
|
-
test.skip("If we add a server peer, newly created coValues are auto-subscribed to",
|
|
442
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
443
|
+
test.skip("If we add a server peer, newly created coValues are auto-subscribed to", () => Effect.gen(function* () {
|
|
450
444
|
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
451
445
|
const node = new LocalNode(admin, session, Crypto);
|
|
452
446
|
const group = node.createGroup();
|
|
453
|
-
const [inRx, _inTx] =
|
|
454
|
-
const [outRx, outTx] =
|
|
447
|
+
const [inRx, _inTx] = yield* newQueuePair();
|
|
448
|
+
const [outRx, outTx] = yield* newQueuePair();
|
|
449
|
+
const outRxQ = yield* Queue.unbounded();
|
|
450
|
+
yield* Effect.fork(Stream.run(outRx, Sink.fromQueue(outRxQ)));
|
|
455
451
|
node.syncManager.addPeer({
|
|
456
452
|
id: "test",
|
|
457
453
|
incoming: inRx,
|
|
458
454
|
outgoing: outTx,
|
|
459
455
|
role: "server",
|
|
460
456
|
});
|
|
461
|
-
|
|
462
|
-
// expect((await reader.read()).value).toMatchObject({
|
|
457
|
+
// expect(yield* Queue.take(outRxQ)).toMatchObject({
|
|
463
458
|
// action: "load",
|
|
464
459
|
// id: admin.id,
|
|
465
460
|
// });
|
|
466
|
-
expect(
|
|
461
|
+
expect(yield* Queue.take(outRxQ)).toMatchObject({
|
|
467
462
|
action: "load",
|
|
468
463
|
id: group.core.id,
|
|
469
464
|
});
|
|
470
465
|
const map = group.createMap();
|
|
471
|
-
const mapSubscribeMsg =
|
|
472
|
-
expect(mapSubscribeMsg
|
|
466
|
+
const mapSubscribeMsg = Queue.take(outRxQ);
|
|
467
|
+
expect(mapSubscribeMsg).toEqual({
|
|
473
468
|
action: "load",
|
|
474
469
|
...map.core.knownState(),
|
|
475
470
|
});
|
|
476
|
-
// expect(
|
|
477
|
-
expect(
|
|
478
|
-
const mapContentMsg =
|
|
479
|
-
expect(mapContentMsg
|
|
471
|
+
// expect(yield* Queue.take(outRxQ)).toMatchObject(admContEx(adminID));
|
|
472
|
+
expect(yield* Queue.take(outRxQ)).toMatchObject(groupContentEx(group));
|
|
473
|
+
const mapContentMsg = Queue.take(outRxQ);
|
|
474
|
+
expect(mapContentMsg).toEqual({
|
|
480
475
|
action: "content",
|
|
481
476
|
id: map.core.id,
|
|
482
477
|
header: map.core.header,
|
|
483
478
|
new: {},
|
|
484
479
|
});
|
|
485
|
-
});
|
|
480
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
486
481
|
test.todo("TODO: when receiving a subscribe response that is behind our optimistic state (due to already sent content), we ignore it");
|
|
487
|
-
test("When we connect a new server peer, we try to sync all existing coValues to it",
|
|
482
|
+
test("When we connect a new server peer, we try to sync all existing coValues to it", () => Effect.gen(function* () {
|
|
488
483
|
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
489
484
|
const node = new LocalNode(admin, session, Crypto);
|
|
490
485
|
const group = node.createGroup();
|
|
491
486
|
const map = group.createMap();
|
|
492
|
-
const [inRx, _inTx] =
|
|
493
|
-
const [outRx, outTx] =
|
|
487
|
+
const [inRx, _inTx] = yield* newQueuePair();
|
|
488
|
+
const [outRx, outTx] = yield* newQueuePair();
|
|
489
|
+
const outRxQ = yield* Queue.unbounded();
|
|
490
|
+
yield* Effect.fork(Stream.run(outRx, Sink.fromQueue(outRxQ)));
|
|
494
491
|
node.syncManager.addPeer({
|
|
495
492
|
id: "test",
|
|
496
493
|
incoming: inRx,
|
|
497
494
|
outgoing: outTx,
|
|
498
495
|
role: "server",
|
|
499
496
|
});
|
|
500
|
-
const
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
expect(groupSubscribeMessage.value).toEqual({
|
|
497
|
+
// const _adminSubscribeMessage = yield* Queue.take(outRxQ);
|
|
498
|
+
const groupSubscribeMessage = yield* Queue.take(outRxQ);
|
|
499
|
+
expect(groupSubscribeMessage).toEqual({
|
|
504
500
|
action: "load",
|
|
505
501
|
...group.core.knownState(),
|
|
506
502
|
});
|
|
507
|
-
const secondMessage =
|
|
508
|
-
expect(secondMessage
|
|
503
|
+
const secondMessage = yield* Queue.take(outRxQ);
|
|
504
|
+
expect(secondMessage).toEqual({
|
|
509
505
|
action: "load",
|
|
510
506
|
...map.core.knownState(),
|
|
511
507
|
});
|
|
512
|
-
});
|
|
513
|
-
test("When receiving a subscribe with a known state that is ahead of our own, peers should respond with a corresponding subscribe response message",
|
|
508
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
509
|
+
test("When receiving a subscribe with a known state that is ahead of our own, peers should respond with a corresponding subscribe response message", () => Effect.gen(function* () {
|
|
514
510
|
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
515
511
|
const node = new LocalNode(admin, session, Crypto);
|
|
516
512
|
const group = node.createGroup();
|
|
517
513
|
const map = group.createMap();
|
|
518
|
-
const [inRx, inTx] =
|
|
519
|
-
const [outRx, outTx] =
|
|
514
|
+
const [inRx, inTx] = yield* newQueuePair();
|
|
515
|
+
const [outRx, outTx] = yield* newQueuePair();
|
|
516
|
+
const outRxQ = yield* Queue.unbounded();
|
|
517
|
+
yield* Effect.fork(Stream.run(outRx, Sink.fromQueue(outRxQ)));
|
|
520
518
|
node.syncManager.addPeer({
|
|
521
519
|
id: "test",
|
|
522
520
|
incoming: inRx,
|
|
523
521
|
outgoing: outTx,
|
|
524
522
|
role: "peer",
|
|
525
523
|
});
|
|
526
|
-
|
|
527
|
-
await writer.write({
|
|
524
|
+
yield* Queue.offer(inTx, {
|
|
528
525
|
action: "load",
|
|
529
526
|
id: map.core.id,
|
|
530
527
|
header: true,
|
|
@@ -532,110 +529,123 @@ test("When receiving a subscribe with a known state that is ahead of our own, pe
|
|
|
532
529
|
[node.currentSessionID]: 1,
|
|
533
530
|
},
|
|
534
531
|
});
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
expect(mapTellKnownState.value).toEqual({
|
|
532
|
+
// expect(yield* Queue.take(outRxQ)).toMatchObject(admStateEx(admin.id));
|
|
533
|
+
expect(yield* Queue.take(outRxQ)).toMatchObject(groupStateEx(group));
|
|
534
|
+
const mapTellKnownState = yield* Queue.take(outRxQ);
|
|
535
|
+
expect(mapTellKnownState).toEqual({
|
|
540
536
|
action: "known",
|
|
541
537
|
...map.core.knownState(),
|
|
542
538
|
});
|
|
543
|
-
});
|
|
544
|
-
test.skip("When replaying creation and transactions of a coValue as new content, the receiving peer integrates this information",
|
|
539
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
540
|
+
test.skip("When replaying creation and transactions of a coValue as new content, the receiving peer integrates this information", () => Effect.gen(function* () {
|
|
545
541
|
// TODO: this test is mostly correct but also slightly unrealistic, make sure we pass all messages back and forth as expected and then it should work
|
|
546
542
|
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
547
543
|
const node1 = new LocalNode(admin, session, Crypto);
|
|
548
544
|
const group = node1.createGroup();
|
|
549
|
-
const [inRx1, inTx1] =
|
|
550
|
-
const [outRx1, outTx1] =
|
|
545
|
+
const [inRx1, inTx1] = yield* newQueuePair();
|
|
546
|
+
const [outRx1, outTx1] = yield* newQueuePair();
|
|
547
|
+
const outRxQ1 = yield* Queue.unbounded();
|
|
548
|
+
yield* Effect.fork(Stream.run(outRx1, Sink.fromQueue(outRxQ1)));
|
|
551
549
|
node1.syncManager.addPeer({
|
|
552
550
|
id: "test2",
|
|
553
551
|
incoming: inRx1,
|
|
554
552
|
outgoing: outTx1,
|
|
555
553
|
role: "server",
|
|
556
554
|
});
|
|
557
|
-
const to1 = inTx1.getWriter();
|
|
558
|
-
const from1 = outRx1.getReader();
|
|
559
555
|
const node2 = new LocalNode(admin, newRandomSessionID(admin.id), Crypto);
|
|
560
|
-
const [inRx2, inTx2] =
|
|
561
|
-
const [outRx2, outTx2] =
|
|
556
|
+
const [inRx2, inTx2] = yield* newQueuePair();
|
|
557
|
+
const [outRx2, outTx2] = yield* newQueuePair();
|
|
558
|
+
const outRxQ2 = yield* Queue.unbounded();
|
|
559
|
+
yield* Effect.fork(Stream.run(outRx2, Sink.fromQueue(outRxQ1)));
|
|
562
560
|
node2.syncManager.addPeer({
|
|
563
561
|
id: "test1",
|
|
564
562
|
incoming: inRx2,
|
|
565
563
|
outgoing: outTx2,
|
|
566
564
|
role: "client",
|
|
567
565
|
});
|
|
568
|
-
const
|
|
569
|
-
|
|
570
|
-
const adminSubscribeMessage = await from1.read();
|
|
571
|
-
expect(adminSubscribeMessage.value).toMatchObject({
|
|
566
|
+
const adminSubscribeMessage = yield* Queue.take(outRxQ1);
|
|
567
|
+
expect(adminSubscribeMessage).toMatchObject({
|
|
572
568
|
action: "load",
|
|
573
569
|
id: admin.id,
|
|
574
570
|
});
|
|
575
|
-
const groupSubscribeMsg =
|
|
576
|
-
expect(groupSubscribeMsg
|
|
571
|
+
const groupSubscribeMsg = yield* Queue.take(outRxQ1);
|
|
572
|
+
expect(groupSubscribeMsg).toMatchObject({
|
|
577
573
|
action: "load",
|
|
578
574
|
id: group.core.id,
|
|
579
575
|
});
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
// const adminTellKnownStateMsg =
|
|
583
|
-
// expect(adminTellKnownStateMsg
|
|
584
|
-
const groupTellKnownStateMsg =
|
|
585
|
-
expect(groupTellKnownStateMsg
|
|
576
|
+
yield* Queue.offer(inTx2, adminSubscribeMessage);
|
|
577
|
+
yield* Queue.offer(inTx2, groupSubscribeMsg);
|
|
578
|
+
// const adminTellKnownStateMsg = yield* Queue.take(outRxQ2);
|
|
579
|
+
// expect(adminTellKnownStateMsg).toMatchObject(admStateEx(admin.id));
|
|
580
|
+
const groupTellKnownStateMsg = yield* Queue.take(outRxQ2);
|
|
581
|
+
expect(groupTellKnownStateMsg).toMatchObject(groupStateEx(group));
|
|
586
582
|
expect(node2.syncManager.peers["test1"].optimisticKnownStates[group.core.id]).toBeDefined();
|
|
587
|
-
//
|
|
588
|
-
|
|
589
|
-
// const adminContentMsg =
|
|
590
|
-
// expect(adminContentMsg
|
|
591
|
-
const groupContentMsg =
|
|
592
|
-
expect(groupContentMsg
|
|
593
|
-
//
|
|
594
|
-
|
|
583
|
+
// yield* Queue.offer(inTx1, adminTellKnownStateMsg);
|
|
584
|
+
yield* Queue.offer(inTx1, groupTellKnownStateMsg);
|
|
585
|
+
// const adminContentMsg = yield* Queue.take(outRxQ1);
|
|
586
|
+
// expect(adminContentMsg).toMatchObject(admContEx(admin.id));
|
|
587
|
+
const groupContentMsg = yield* Queue.take(outRxQ1);
|
|
588
|
+
expect(groupContentMsg).toMatchObject(groupContentEx(group));
|
|
589
|
+
// yield* Queue.offer(inTx2, adminContentMsg);
|
|
590
|
+
yield* Queue.offer(inTx2, groupContentMsg);
|
|
595
591
|
const map = group.createMap();
|
|
596
|
-
const mapSubscriptionMsg =
|
|
597
|
-
expect(mapSubscriptionMsg
|
|
592
|
+
const mapSubscriptionMsg = yield* Queue.take(outRxQ1);
|
|
593
|
+
expect(mapSubscriptionMsg).toMatchObject({
|
|
598
594
|
action: "load",
|
|
599
595
|
id: map.core.id,
|
|
600
596
|
});
|
|
601
|
-
const mapNewContentMsg =
|
|
602
|
-
expect(mapNewContentMsg
|
|
597
|
+
const mapNewContentMsg = yield* Queue.take(outRxQ1);
|
|
598
|
+
expect(mapNewContentMsg).toEqual({
|
|
603
599
|
action: "content",
|
|
604
600
|
id: map.core.id,
|
|
605
601
|
header: map.core.header,
|
|
606
602
|
new: {},
|
|
607
603
|
});
|
|
608
|
-
|
|
609
|
-
const mapTellKnownStateMsg =
|
|
610
|
-
expect(mapTellKnownStateMsg
|
|
604
|
+
yield* Queue.offer(inTx2, mapSubscriptionMsg);
|
|
605
|
+
const mapTellKnownStateMsg = yield* Queue.take(outRxQ2);
|
|
606
|
+
expect(mapTellKnownStateMsg).toEqual({
|
|
611
607
|
action: "known",
|
|
612
608
|
id: map.core.id,
|
|
613
609
|
header: false,
|
|
614
610
|
sessions: {},
|
|
615
611
|
});
|
|
616
612
|
expect(node2.coValues[map.core.id]?.state).toEqual("loading");
|
|
617
|
-
|
|
613
|
+
yield* Queue.offer(inTx2, mapNewContentMsg);
|
|
618
614
|
map.set("hello", "world", "trusting");
|
|
619
|
-
const mapEditMsg =
|
|
620
|
-
|
|
621
|
-
|
|
615
|
+
const mapEditMsg = yield* Queue.take(outRxQ1);
|
|
616
|
+
yield* Queue.offer(inTx2, mapEditMsg);
|
|
617
|
+
yield* Effect.sleep(100);
|
|
622
618
|
expect(expectMap(node2.expectCoValueLoaded(map.core.id).getCurrentContent()).get("hello")).toEqual("world");
|
|
623
|
-
});
|
|
619
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
624
620
|
test.skip("When loading a coValue on one node, the server node it is requested from replies with all the necessary depended on coValues to make it work", async () => {
|
|
621
|
+
/*
|
|
625
622
|
// TODO: this test is mostly correct but also slightly unrealistic, make sure we pass all messages back and forth as expected and then it should work
|
|
626
623
|
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
624
|
+
|
|
627
625
|
const node1 = new LocalNode(admin, session, Crypto);
|
|
626
|
+
|
|
628
627
|
const group = node1.createGroup();
|
|
628
|
+
|
|
629
629
|
const map = group.createMap();
|
|
630
630
|
map.set("hello", "world", "trusting");
|
|
631
|
+
|
|
631
632
|
const node2 = new LocalNode(admin, newRandomSessionID(admin.id), Crypto);
|
|
633
|
+
|
|
632
634
|
const [node1asPeer, node2asPeer] = connectedPeers("peer1", "peer2");
|
|
635
|
+
|
|
633
636
|
node1.syncManager.addPeer(node2asPeer);
|
|
634
637
|
node2.syncManager.addPeer(node1asPeer);
|
|
638
|
+
|
|
635
639
|
await node2.loadCoValueCore(map.core.id);
|
|
636
|
-
|
|
640
|
+
|
|
641
|
+
expect(
|
|
642
|
+
expectMap(
|
|
643
|
+
node2.expectCoValueLoaded(map.core.id).getCurrentContent(),
|
|
644
|
+
).get("hello"),
|
|
645
|
+
).toEqual("world");
|
|
646
|
+
*/
|
|
637
647
|
});
|
|
638
|
-
test("Can sync a coValue through a server to another client",
|
|
648
|
+
test("Can sync a coValue through a server to another client", () => Effect.gen(function* () {
|
|
639
649
|
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
640
650
|
const client1 = new LocalNode(admin, session, Crypto);
|
|
641
651
|
const group = client1.createGroup();
|
|
@@ -643,24 +653,28 @@ test("Can sync a coValue through a server to another client", async () => {
|
|
|
643
653
|
map.set("hello", "world", "trusting");
|
|
644
654
|
const [serverUser, serverSession] = randomAnonymousAccountAndSessionID();
|
|
645
655
|
const server = new LocalNode(serverUser, serverSession, Crypto);
|
|
646
|
-
const [
|
|
656
|
+
const [serverAsPeerForClient1, client1AsPeer] = yield* connectedPeers("serverFor1", "client1", {
|
|
647
657
|
peer1role: "server",
|
|
648
658
|
peer2role: "client",
|
|
649
659
|
trace: true,
|
|
650
660
|
});
|
|
651
|
-
client1.syncManager.addPeer(
|
|
661
|
+
client1.syncManager.addPeer(serverAsPeerForClient1);
|
|
652
662
|
server.syncManager.addPeer(client1AsPeer);
|
|
653
663
|
const client2 = new LocalNode(admin, newRandomSessionID(admin.id), Crypto);
|
|
654
|
-
const [
|
|
655
|
-
|
|
664
|
+
const [serverAsPeerForClient2, client2AsPeer] = yield* connectedPeers("serverFor2", "client2", {
|
|
665
|
+
peer1role: "server",
|
|
666
|
+
peer2role: "client",
|
|
667
|
+
trace: true,
|
|
668
|
+
});
|
|
669
|
+
client2.syncManager.addPeer(serverAsPeerForClient2);
|
|
656
670
|
server.syncManager.addPeer(client2AsPeer);
|
|
657
|
-
const mapOnClient2 =
|
|
671
|
+
const mapOnClient2 = yield* Effect.promise(() => client2.loadCoValueCore(map.core.id));
|
|
658
672
|
if (mapOnClient2 === "unavailable") {
|
|
659
673
|
throw new Error("Map is unavailable");
|
|
660
674
|
}
|
|
661
675
|
expect(expectMap(mapOnClient2.getCurrentContent()).get("hello")).toEqual("world");
|
|
662
|
-
});
|
|
663
|
-
test("Can sync a coValue with private transactions through a server to another client",
|
|
676
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
677
|
+
test("Can sync a coValue with private transactions through a server to another client", () => Effect.gen(function* () {
|
|
664
678
|
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
665
679
|
const client1 = new LocalNode(admin, session, Crypto);
|
|
666
680
|
const group = client1.createGroup();
|
|
@@ -668,7 +682,7 @@ test("Can sync a coValue with private transactions through a server to another c
|
|
|
668
682
|
map.set("hello", "world", "private");
|
|
669
683
|
const [serverUser, serverSession] = randomAnonymousAccountAndSessionID();
|
|
670
684
|
const server = new LocalNode(serverUser, serverSession, Crypto);
|
|
671
|
-
const [serverAsPeer, client1AsPeer] = connectedPeers("server", "client1", {
|
|
685
|
+
const [serverAsPeer, client1AsPeer] = yield* connectedPeers("server", "client1", {
|
|
672
686
|
trace: true,
|
|
673
687
|
peer1role: "server",
|
|
674
688
|
peer2role: "client",
|
|
@@ -676,105 +690,138 @@ test("Can sync a coValue with private transactions through a server to another c
|
|
|
676
690
|
client1.syncManager.addPeer(serverAsPeer);
|
|
677
691
|
server.syncManager.addPeer(client1AsPeer);
|
|
678
692
|
const client2 = new LocalNode(admin, newRandomSessionID(admin.id), Crypto);
|
|
679
|
-
const [serverAsOtherPeer, client2AsPeer] = connectedPeers("server", "client2", {
|
|
693
|
+
const [serverAsOtherPeer, client2AsPeer] = yield* connectedPeers("server", "client2", {
|
|
694
|
+
trace: true,
|
|
695
|
+
peer1role: "server",
|
|
696
|
+
peer2role: "client",
|
|
697
|
+
});
|
|
680
698
|
client2.syncManager.addPeer(serverAsOtherPeer);
|
|
681
699
|
server.syncManager.addPeer(client2AsPeer);
|
|
682
|
-
const mapOnClient2 =
|
|
700
|
+
const mapOnClient2 = yield* Effect.promise(() => client2.loadCoValueCore(map.core.id));
|
|
683
701
|
if (mapOnClient2 === "unavailable") {
|
|
684
702
|
throw new Error("Map is unavailable");
|
|
685
703
|
}
|
|
686
704
|
expect(expectMap(mapOnClient2.getCurrentContent()).get("hello")).toEqual("world");
|
|
687
|
-
});
|
|
705
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
688
706
|
test.skip("When a peer's incoming/readable stream closes, we remove the peer", async () => {
|
|
707
|
+
/*
|
|
689
708
|
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
690
709
|
const node = new LocalNode(admin, session, Crypto);
|
|
710
|
+
|
|
691
711
|
const group = node.createGroup();
|
|
692
|
-
|
|
693
|
-
const [
|
|
712
|
+
|
|
713
|
+
const [inRx, inTx] = await Effect.runPromise(newStreamPair());
|
|
714
|
+
const [outRx, outTx] = await Effect.runPromise(newStreamPair());
|
|
715
|
+
|
|
694
716
|
node.syncManager.addPeer({
|
|
695
717
|
id: "test",
|
|
696
718
|
incoming: inRx,
|
|
697
719
|
outgoing: outTx,
|
|
698
720
|
role: "server",
|
|
699
721
|
});
|
|
700
|
-
|
|
701
|
-
// expect(
|
|
722
|
+
|
|
723
|
+
// expect(yield* Queue.take(outRxQ)).toMatchObject({
|
|
702
724
|
// action: "load",
|
|
703
725
|
// id: admin.id,
|
|
704
726
|
// });
|
|
705
|
-
expect(
|
|
727
|
+
expect(yield * Queue.take(outRxQ)).toMatchObject({
|
|
706
728
|
action: "load",
|
|
707
729
|
id: group.core.id,
|
|
708
730
|
});
|
|
731
|
+
|
|
709
732
|
const map = group.createMap();
|
|
733
|
+
|
|
710
734
|
const mapSubscribeMsg = await reader.read();
|
|
735
|
+
|
|
711
736
|
expect(mapSubscribeMsg.value).toEqual({
|
|
712
737
|
action: "load",
|
|
713
738
|
...map.core.knownState(),
|
|
714
|
-
});
|
|
715
|
-
|
|
716
|
-
expect(
|
|
739
|
+
} satisfies SyncMessage);
|
|
740
|
+
|
|
741
|
+
// expect(yield* Queue.take(outRxQ)).toMatchObject(admContEx(admin.id));
|
|
742
|
+
expect(yield * Queue.take(outRxQ)).toMatchObject(groupContentEx(group));
|
|
743
|
+
|
|
717
744
|
const mapContentMsg = await reader.read();
|
|
745
|
+
|
|
718
746
|
expect(mapContentMsg.value).toEqual({
|
|
719
747
|
action: "content",
|
|
720
748
|
id: map.core.id,
|
|
721
749
|
header: map.core.header,
|
|
722
750
|
new: {},
|
|
723
|
-
});
|
|
751
|
+
} satisfies SyncMessage);
|
|
752
|
+
|
|
724
753
|
await inTx.abort();
|
|
754
|
+
|
|
725
755
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
756
|
+
|
|
726
757
|
expect(node.syncManager.peers["test"]).toBeUndefined();
|
|
758
|
+
*/
|
|
727
759
|
});
|
|
728
760
|
test.skip("When a peer's outgoing/writable stream closes, we remove the peer", async () => {
|
|
761
|
+
/*
|
|
729
762
|
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
730
763
|
const node = new LocalNode(admin, session, Crypto);
|
|
764
|
+
|
|
731
765
|
const group = node.createGroup();
|
|
732
|
-
|
|
733
|
-
const [
|
|
766
|
+
|
|
767
|
+
const [inRx] = await Effect.runPromise(newStreamPair());
|
|
768
|
+
const [outRx, outTx] = await Effect.runPromise(newStreamPair());
|
|
769
|
+
|
|
734
770
|
node.syncManager.addPeer({
|
|
735
771
|
id: "test",
|
|
736
772
|
incoming: inRx,
|
|
737
773
|
outgoing: outTx,
|
|
738
774
|
role: "server",
|
|
739
775
|
});
|
|
740
|
-
|
|
741
|
-
// expect(
|
|
776
|
+
|
|
777
|
+
// expect(yield* Queue.take(outRxQ)).toMatchObject({
|
|
742
778
|
// action: "load",
|
|
743
779
|
// id: admin.id,
|
|
744
780
|
// });
|
|
745
|
-
expect(
|
|
781
|
+
expect(yield * Queue.take(outRxQ)).toMatchObject({
|
|
746
782
|
action: "load",
|
|
747
783
|
id: group.core.id,
|
|
748
784
|
});
|
|
785
|
+
|
|
749
786
|
const map = group.createMap();
|
|
787
|
+
|
|
750
788
|
const mapSubscribeMsg = await reader.read();
|
|
789
|
+
|
|
751
790
|
expect(mapSubscribeMsg.value).toEqual({
|
|
752
791
|
action: "load",
|
|
753
792
|
...map.core.knownState(),
|
|
754
|
-
});
|
|
755
|
-
|
|
756
|
-
expect(
|
|
793
|
+
} satisfies SyncMessage);
|
|
794
|
+
|
|
795
|
+
// expect(yield* Queue.take(outRxQ)).toMatchObject(admContEx(admin.id));
|
|
796
|
+
expect(yield * Queue.take(outRxQ)).toMatchObject(groupContentEx(group));
|
|
797
|
+
|
|
757
798
|
const mapContentMsg = await reader.read();
|
|
799
|
+
|
|
758
800
|
expect(mapContentMsg.value).toEqual({
|
|
759
801
|
action: "content",
|
|
760
802
|
id: map.core.id,
|
|
761
803
|
header: map.core.header,
|
|
762
804
|
new: {},
|
|
763
|
-
});
|
|
805
|
+
} satisfies SyncMessage);
|
|
806
|
+
|
|
764
807
|
reader.releaseLock();
|
|
765
808
|
await outRx.cancel();
|
|
809
|
+
|
|
766
810
|
map.set("hello", "world", "trusting");
|
|
811
|
+
|
|
767
812
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
813
|
+
|
|
768
814
|
expect(node.syncManager.peers["test"]).toBeUndefined();
|
|
815
|
+
*/
|
|
769
816
|
});
|
|
770
|
-
test("If we start loading a coValue before connecting to a peer that has it, it will load it once we connect",
|
|
817
|
+
test("If we start loading a coValue before connecting to a peer that has it, it will load it once we connect", () => Effect.gen(function* () {
|
|
771
818
|
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
772
819
|
const node1 = new LocalNode(admin, session, Crypto);
|
|
773
820
|
const group = node1.createGroup();
|
|
774
821
|
const map = group.createMap();
|
|
775
822
|
map.set("hello", "world", "trusting");
|
|
776
823
|
const node2 = new LocalNode(admin, newRandomSessionID(admin.id), Crypto);
|
|
777
|
-
const [node1asPeer, node2asPeer] = connectedPeers("peer1", "peer2", {
|
|
824
|
+
const [node1asPeer, node2asPeer] = yield* connectedPeers("peer1", "peer2", {
|
|
778
825
|
peer1role: "server",
|
|
779
826
|
peer2role: "client",
|
|
780
827
|
trace: true,
|
|
@@ -783,12 +830,12 @@ test("If we start loading a coValue before connecting to a peer that has it, it
|
|
|
783
830
|
const mapOnNode2Promise = node2.loadCoValueCore(map.core.id);
|
|
784
831
|
expect(node2.coValues[map.core.id]?.state).toEqual("loading");
|
|
785
832
|
node2.syncManager.addPeer(node1asPeer);
|
|
786
|
-
const mapOnNode2 =
|
|
833
|
+
const mapOnNode2 = yield* Effect.promise(() => mapOnNode2Promise);
|
|
787
834
|
if (mapOnNode2 === "unavailable") {
|
|
788
835
|
throw new Error("Map is unavailable");
|
|
789
836
|
}
|
|
790
837
|
expect(expectMap(mapOnNode2.getCurrentContent()).get("hello")).toEqual("world");
|
|
791
|
-
});
|
|
838
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
792
839
|
function groupContentEx(group) {
|
|
793
840
|
return {
|
|
794
841
|
action: "content",
|