cojson 0.7.23 → 0.7.26
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 +264 -339
- package/CHANGELOG.md +6 -0
- package/dist/coValueCore.js +8 -1
- package/dist/coValueCore.js.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/localNode.js +3 -0
- package/dist/localNode.js.map +1 -1
- package/dist/storage/FileSystem.js +48 -62
- package/dist/storage/FileSystem.js.map +1 -1
- package/dist/storage/chunksAndKnownStates.js +2 -3
- package/dist/storage/chunksAndKnownStates.js.map +1 -1
- package/dist/storage/index.js +285 -310
- package/dist/storage/index.js.map +1 -1
- package/dist/streamUtils.js +34 -32
- package/dist/streamUtils.js.map +1 -1
- package/dist/sync.js +48 -35
- package/dist/sync.js.map +1 -1
- package/dist/tests/account.test.js +2 -3
- package/dist/tests/account.test.js.map +1 -1
- package/dist/tests/sync.test.js +183 -182
- package/dist/tests/sync.test.js.map +1 -1
- package/package.json +4 -4
- package/src/coValueCore.ts +8 -1
- package/src/index.ts +5 -10
- package/src/localNode.ts +4 -0
- package/src/storage/FileSystem.ts +83 -110
- package/src/storage/chunksAndKnownStates.ts +3 -4
- package/src/storage/index.ts +391 -491
- package/src/streamUtils.ts +46 -48
- package/src/sync.ts +68 -73
- package/src/tests/account.test.ts +5 -8
- package/src/tests/sync.test.ts +731 -798
package/src/tests/sync.test.ts
CHANGED
|
@@ -8,369 +8,480 @@ import { connectedPeers, newQueuePair } from "../streamUtils.js";
|
|
|
8
8
|
import { AccountID } from "../coValues/account.js";
|
|
9
9
|
import { stableStringify } from "../jsonStringify.js";
|
|
10
10
|
import { WasmCrypto } from "../crypto/WasmCrypto.js";
|
|
11
|
-
import { Effect, Queue, Sink, Stream } from "effect";
|
|
12
|
-
import { newRandomSessionID } from "../coValueCore.js";
|
|
13
11
|
import { expectMap } from "../coValue.js";
|
|
12
|
+
import { newRandomSessionID } from "../coValueCore.js";
|
|
14
13
|
|
|
15
14
|
const Crypto = await WasmCrypto.create();
|
|
16
15
|
|
|
17
|
-
test("Node replies with initial tx and header to empty subscribe", () =>
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const node = new LocalNode(admin, session, Crypto);
|
|
16
|
+
test("Node replies with initial tx and header to empty subscribe", async () => {
|
|
17
|
+
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
18
|
+
const node = new LocalNode(admin, session, Crypto);
|
|
21
19
|
|
|
22
|
-
|
|
20
|
+
const group = node.createGroup();
|
|
23
21
|
|
|
24
|
-
|
|
22
|
+
const map = group.createMap();
|
|
25
23
|
|
|
26
|
-
|
|
24
|
+
map.set("hello", "world", "trusting");
|
|
27
25
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
yield* Effect.fork(Stream.run(outRx, Sink.fromQueue(outRxQ)));
|
|
26
|
+
const [inRx, inTx] = newQueuePair();
|
|
27
|
+
const [outRx, outTx] = newQueuePair();
|
|
28
|
+
const outRxQ = outRx[Symbol.asyncIterator]();
|
|
32
29
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
node.syncManager.addPeer({
|
|
31
|
+
id: "test",
|
|
32
|
+
incoming: inRx,
|
|
33
|
+
outgoing: outTx,
|
|
34
|
+
role: "peer",
|
|
35
|
+
});
|
|
39
36
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
37
|
+
await inTx.push({
|
|
38
|
+
action: "load",
|
|
39
|
+
id: map.core.id,
|
|
40
|
+
header: false,
|
|
41
|
+
sessions: {},
|
|
42
|
+
});
|
|
46
43
|
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
// expect((await outRxQ.next()).value).toMatchObject(admStateEx(admin.id));
|
|
45
|
+
expect((await outRxQ.next()).value).toMatchObject(groupStateEx(group));
|
|
49
46
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
47
|
+
const mapTellKnownStateMsg = (await outRxQ.next()).value;
|
|
48
|
+
expect(mapTellKnownStateMsg).toEqual({
|
|
49
|
+
action: "known",
|
|
50
|
+
...map.core.knownState(),
|
|
51
|
+
} satisfies SyncMessage);
|
|
55
52
|
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
// expect((await outRxQ.next()).value).toMatchObject(admContEx(admin.id));
|
|
54
|
+
expect((await outRxQ.next()).value).toMatchObject(groupContentEx(group));
|
|
58
55
|
|
|
59
|
-
|
|
56
|
+
const newContentMsg = (await outRxQ.next()).value;
|
|
60
57
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
lastSignature
|
|
90
|
-
node.currentSessionID,
|
|
91
|
-
)!.lastSignature!,
|
|
92
|
-
},
|
|
58
|
+
expect(newContentMsg).toEqual({
|
|
59
|
+
action: "content",
|
|
60
|
+
id: map.core.id,
|
|
61
|
+
header: {
|
|
62
|
+
type: "comap",
|
|
63
|
+
ruleset: { type: "ownedByGroup", group: group.id },
|
|
64
|
+
meta: null,
|
|
65
|
+
createdAt: map.core.header.createdAt,
|
|
66
|
+
uniqueness: map.core.header.uniqueness,
|
|
67
|
+
},
|
|
68
|
+
new: {
|
|
69
|
+
[node.currentSessionID]: {
|
|
70
|
+
after: 0,
|
|
71
|
+
newTransactions: [
|
|
72
|
+
{
|
|
73
|
+
privacy: "trusting" as const,
|
|
74
|
+
madeAt: map.core.sessionLogs.get(node.currentSessionID)!
|
|
75
|
+
.transactions[0]!.madeAt,
|
|
76
|
+
changes: stableStringify([
|
|
77
|
+
{
|
|
78
|
+
op: "set",
|
|
79
|
+
key: "hello",
|
|
80
|
+
value: "world",
|
|
81
|
+
} satisfies MapOpPayload<string, string>,
|
|
82
|
+
]),
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
lastSignature: map.core.sessionLogs.get(node.currentSessionID)!
|
|
86
|
+
.lastSignature!,
|
|
93
87
|
},
|
|
94
|
-
}
|
|
95
|
-
}
|
|
88
|
+
},
|
|
89
|
+
} satisfies SyncMessage);
|
|
90
|
+
});
|
|
96
91
|
|
|
97
|
-
test("Node replies with only new tx to subscribe with some known state", () =>
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const node = new LocalNode(admin, session, Crypto);
|
|
92
|
+
test("Node replies with only new tx to subscribe with some known state", async () => {
|
|
93
|
+
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
94
|
+
const node = new LocalNode(admin, session, Crypto);
|
|
101
95
|
|
|
102
|
-
|
|
96
|
+
const group = node.createGroup();
|
|
103
97
|
|
|
104
|
-
|
|
98
|
+
const map = group.createMap();
|
|
105
99
|
|
|
106
|
-
|
|
107
|
-
|
|
100
|
+
map.set("hello", "world", "trusting");
|
|
101
|
+
map.set("goodbye", "world", "trusting");
|
|
108
102
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
yield* Effect.fork(Stream.run(outRx, Sink.fromQueue(outRxQ)));
|
|
103
|
+
const [inRx, inTx] = newQueuePair();
|
|
104
|
+
const [outRx, outTx] = newQueuePair();
|
|
105
|
+
const outRxQ = outRx[Symbol.asyncIterator]();
|
|
113
106
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
107
|
+
node.syncManager.addPeer({
|
|
108
|
+
id: "test",
|
|
109
|
+
incoming: inRx,
|
|
110
|
+
outgoing: outTx,
|
|
111
|
+
role: "peer",
|
|
112
|
+
});
|
|
120
113
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
114
|
+
await inTx.push({
|
|
115
|
+
action: "load",
|
|
116
|
+
id: map.core.id,
|
|
117
|
+
header: true,
|
|
118
|
+
sessions: {
|
|
119
|
+
[node.currentSessionID]: 1,
|
|
120
|
+
},
|
|
121
|
+
});
|
|
129
122
|
|
|
130
|
-
|
|
131
|
-
|
|
123
|
+
// expect((await outRxQ.next()).value).toMatchObject(admStateEx(admin.id));
|
|
124
|
+
expect((await outRxQ.next()).value).toMatchObject(groupStateEx(group));
|
|
132
125
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
126
|
+
const mapTellKnownStateMsg = (await outRxQ.next()).value;
|
|
127
|
+
expect(mapTellKnownStateMsg).toEqual({
|
|
128
|
+
action: "known",
|
|
129
|
+
...map.core.knownState(),
|
|
130
|
+
} satisfies SyncMessage);
|
|
138
131
|
|
|
139
|
-
|
|
140
|
-
|
|
132
|
+
// expect((await outRxQ.next()).value).toMatchObject(admContEx(admin.id));
|
|
133
|
+
expect((await outRxQ.next()).value).toMatchObject(groupContentEx(group));
|
|
141
134
|
|
|
142
|
-
|
|
135
|
+
const mapNewContentMsg = (await outRxQ.next()).value;
|
|
143
136
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
lastSignature
|
|
167
|
-
node.currentSessionID,
|
|
168
|
-
)!.lastSignature!,
|
|
169
|
-
},
|
|
137
|
+
expect(mapNewContentMsg).toEqual({
|
|
138
|
+
action: "content",
|
|
139
|
+
id: map.core.id,
|
|
140
|
+
header: undefined,
|
|
141
|
+
new: {
|
|
142
|
+
[node.currentSessionID]: {
|
|
143
|
+
after: 1,
|
|
144
|
+
newTransactions: [
|
|
145
|
+
{
|
|
146
|
+
privacy: "trusting" as const,
|
|
147
|
+
madeAt: map.core.sessionLogs.get(node.currentSessionID)!
|
|
148
|
+
.transactions[1]!.madeAt,
|
|
149
|
+
changes: stableStringify([
|
|
150
|
+
{
|
|
151
|
+
op: "set",
|
|
152
|
+
key: "goodbye",
|
|
153
|
+
value: "world",
|
|
154
|
+
} satisfies MapOpPayload<string, string>,
|
|
155
|
+
]),
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
lastSignature: map.core.sessionLogs.get(node.currentSessionID)!
|
|
159
|
+
.lastSignature!,
|
|
170
160
|
},
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
161
|
+
},
|
|
162
|
+
} satisfies SyncMessage);
|
|
163
|
+
});
|
|
174
164
|
test.todo(
|
|
175
165
|
"TODO: node only replies with new tx to subscribe with some known state, even in the depended on coValues",
|
|
176
166
|
);
|
|
177
167
|
|
|
178
|
-
test("After subscribing, node sends own known state and new txs to peer", () =>
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const node = new LocalNode(admin, session, Crypto);
|
|
168
|
+
test("After subscribing, node sends own known state and new txs to peer", async () => {
|
|
169
|
+
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
170
|
+
const node = new LocalNode(admin, session, Crypto);
|
|
182
171
|
|
|
183
|
-
|
|
172
|
+
const group = node.createGroup();
|
|
184
173
|
|
|
185
|
-
|
|
174
|
+
const map = group.createMap();
|
|
186
175
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
yield* Effect.fork(Stream.run(outRx, Sink.fromQueue(outRxQ)));
|
|
176
|
+
const [inRx, inTx] = newQueuePair();
|
|
177
|
+
const [outRx, outTx] = newQueuePair();
|
|
178
|
+
const outRxQ = outRx[Symbol.asyncIterator]();
|
|
191
179
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
180
|
+
node.syncManager.addPeer({
|
|
181
|
+
id: "test",
|
|
182
|
+
incoming: inRx,
|
|
183
|
+
outgoing: outTx,
|
|
184
|
+
role: "peer",
|
|
185
|
+
});
|
|
198
186
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
187
|
+
await inTx.push({
|
|
188
|
+
action: "load",
|
|
189
|
+
id: map.core.id,
|
|
190
|
+
header: false,
|
|
191
|
+
sessions: {
|
|
192
|
+
[node.currentSessionID]: 0,
|
|
193
|
+
},
|
|
194
|
+
});
|
|
207
195
|
|
|
208
|
-
|
|
209
|
-
|
|
196
|
+
// expect((await outRxQ.next()).value).toMatchObject(admStateEx(admin.id));
|
|
197
|
+
expect((await outRxQ.next()).value).toMatchObject(groupStateEx(group));
|
|
210
198
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
199
|
+
const mapTellKnownStateMsg = (await outRxQ.next()).value;
|
|
200
|
+
expect(mapTellKnownStateMsg).toEqual({
|
|
201
|
+
action: "known",
|
|
202
|
+
...map.core.knownState(),
|
|
203
|
+
} satisfies SyncMessage);
|
|
216
204
|
|
|
217
|
-
|
|
218
|
-
|
|
205
|
+
// expect((await outRxQ.next()).value).toMatchObject(admContEx(admin.id));
|
|
206
|
+
expect((await outRxQ.next()).value).toMatchObject(groupContentEx(group));
|
|
219
207
|
|
|
220
|
-
|
|
208
|
+
const mapNewContentHeaderOnlyMsg = (await outRxQ.next()).value;
|
|
221
209
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
210
|
+
expect(mapNewContentHeaderOnlyMsg).toEqual({
|
|
211
|
+
action: "content",
|
|
212
|
+
id: map.core.id,
|
|
213
|
+
header: map.core.header,
|
|
214
|
+
new: {},
|
|
215
|
+
} satisfies SyncMessage);
|
|
228
216
|
|
|
229
|
-
|
|
217
|
+
map.set("hello", "world", "trusting");
|
|
230
218
|
|
|
231
|
-
|
|
219
|
+
const mapEditMsg1 = (await outRxQ.next()).value;
|
|
232
220
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
lastSignature
|
|
255
|
-
node.currentSessionID,
|
|
256
|
-
)!.lastSignature!,
|
|
257
|
-
},
|
|
221
|
+
expect(mapEditMsg1).toEqual({
|
|
222
|
+
action: "content",
|
|
223
|
+
id: map.core.id,
|
|
224
|
+
new: {
|
|
225
|
+
[node.currentSessionID]: {
|
|
226
|
+
after: 0,
|
|
227
|
+
newTransactions: [
|
|
228
|
+
{
|
|
229
|
+
privacy: "trusting" as const,
|
|
230
|
+
madeAt: map.core.sessionLogs.get(node.currentSessionID)!
|
|
231
|
+
.transactions[0]!.madeAt,
|
|
232
|
+
changes: stableStringify([
|
|
233
|
+
{
|
|
234
|
+
op: "set",
|
|
235
|
+
key: "hello",
|
|
236
|
+
value: "world",
|
|
237
|
+
} satisfies MapOpPayload<string, string>,
|
|
238
|
+
]),
|
|
239
|
+
},
|
|
240
|
+
],
|
|
241
|
+
lastSignature: map.core.sessionLogs.get(node.currentSessionID)!
|
|
242
|
+
.lastSignature!,
|
|
258
243
|
},
|
|
259
|
-
}
|
|
244
|
+
},
|
|
245
|
+
} satisfies SyncMessage);
|
|
260
246
|
|
|
261
|
-
|
|
247
|
+
map.set("goodbye", "world", "trusting");
|
|
262
248
|
|
|
263
|
-
|
|
249
|
+
const mapEditMsg2 = (await outRxQ.next()).value;
|
|
264
250
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
lastSignature
|
|
287
|
-
node.currentSessionID,
|
|
288
|
-
)!.lastSignature!,
|
|
289
|
-
},
|
|
251
|
+
expect(mapEditMsg2).toEqual({
|
|
252
|
+
action: "content",
|
|
253
|
+
id: map.core.id,
|
|
254
|
+
new: {
|
|
255
|
+
[node.currentSessionID]: {
|
|
256
|
+
after: 1,
|
|
257
|
+
newTransactions: [
|
|
258
|
+
{
|
|
259
|
+
privacy: "trusting" as const,
|
|
260
|
+
madeAt: map.core.sessionLogs.get(node.currentSessionID)!
|
|
261
|
+
.transactions[1]!.madeAt,
|
|
262
|
+
changes: stableStringify([
|
|
263
|
+
{
|
|
264
|
+
op: "set",
|
|
265
|
+
key: "goodbye",
|
|
266
|
+
value: "world",
|
|
267
|
+
} satisfies MapOpPayload<string, string>,
|
|
268
|
+
]),
|
|
269
|
+
},
|
|
270
|
+
],
|
|
271
|
+
lastSignature: map.core.sessionLogs.get(node.currentSessionID)!
|
|
272
|
+
.lastSignature!,
|
|
290
273
|
},
|
|
291
|
-
}
|
|
292
|
-
}
|
|
274
|
+
},
|
|
275
|
+
} satisfies SyncMessage);
|
|
276
|
+
});
|
|
293
277
|
|
|
294
|
-
test("Client replies with known new content to tellKnownState from server", () =>
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
const node = new LocalNode(admin, session, Crypto);
|
|
278
|
+
test("Client replies with known new content to tellKnownState from server", async () => {
|
|
279
|
+
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
280
|
+
const node = new LocalNode(admin, session, Crypto);
|
|
298
281
|
|
|
299
|
-
|
|
282
|
+
const group = node.createGroup();
|
|
300
283
|
|
|
301
|
-
|
|
284
|
+
const map = group.createMap();
|
|
302
285
|
|
|
303
|
-
|
|
286
|
+
map.set("hello", "world", "trusting");
|
|
304
287
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
yield* Effect.fork(Stream.run(outRx, Sink.fromQueue(outRxQ)));
|
|
288
|
+
const [inRx, inTx] = newQueuePair();
|
|
289
|
+
const [outRx, outTx] = newQueuePair();
|
|
290
|
+
const outRxQ = outRx[Symbol.asyncIterator]();
|
|
309
291
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
292
|
+
node.syncManager.addPeer({
|
|
293
|
+
id: "test",
|
|
294
|
+
incoming: inRx,
|
|
295
|
+
outgoing: outTx,
|
|
296
|
+
role: "peer",
|
|
297
|
+
});
|
|
316
298
|
|
|
317
|
-
|
|
299
|
+
// expect((await outRxQ.next()).value).toMatchObject(groupStateEx(group));
|
|
318
300
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
301
|
+
await inTx.push({
|
|
302
|
+
action: "known",
|
|
303
|
+
id: map.core.id,
|
|
304
|
+
header: false,
|
|
305
|
+
sessions: {
|
|
306
|
+
[node.currentSessionID]: 0,
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// expect((await outRxQ.next()).value).toMatchObject(admStateEx(admin.id));
|
|
311
|
+
expect((await outRxQ.next()).value).toMatchObject(groupStateEx(group));
|
|
312
|
+
|
|
313
|
+
const mapTellKnownStateMsg = (await outRxQ.next()).value;
|
|
314
|
+
expect(mapTellKnownStateMsg).toEqual({
|
|
315
|
+
action: "known",
|
|
316
|
+
...map.core.knownState(),
|
|
317
|
+
} satisfies SyncMessage);
|
|
318
|
+
|
|
319
|
+
// expect((await outRxQ.next()).value).toMatchObject(admContEx(admin.id));
|
|
320
|
+
expect((await outRxQ.next()).value).toMatchObject(groupContentEx(group));
|
|
321
|
+
|
|
322
|
+
const mapNewContentMsg = (await outRxQ.next()).value;
|
|
323
|
+
|
|
324
|
+
expect(mapNewContentMsg).toEqual({
|
|
325
|
+
action: "content",
|
|
326
|
+
id: map.core.id,
|
|
327
|
+
header: map.core.header,
|
|
328
|
+
new: {
|
|
329
|
+
[node.currentSessionID]: {
|
|
330
|
+
after: 0,
|
|
331
|
+
newTransactions: [
|
|
332
|
+
{
|
|
333
|
+
privacy: "trusting" as const,
|
|
334
|
+
madeAt: map.core.sessionLogs.get(node.currentSessionID)!
|
|
335
|
+
.transactions[0]!.madeAt,
|
|
336
|
+
changes: stableStringify([
|
|
337
|
+
{
|
|
338
|
+
op: "set",
|
|
339
|
+
key: "hello",
|
|
340
|
+
value: "world",
|
|
341
|
+
} satisfies MapOpPayload<string, string>,
|
|
342
|
+
]),
|
|
343
|
+
},
|
|
344
|
+
],
|
|
345
|
+
lastSignature: map.core.sessionLogs.get(node.currentSessionID)!
|
|
346
|
+
.lastSignature!,
|
|
325
347
|
},
|
|
326
|
-
}
|
|
348
|
+
},
|
|
349
|
+
} satisfies SyncMessage);
|
|
350
|
+
});
|
|
327
351
|
|
|
328
|
-
|
|
329
|
-
|
|
352
|
+
test("No matter the optimistic known state, node respects invalid known state messages and resyncs", async () => {
|
|
353
|
+
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
354
|
+
const node = new LocalNode(admin, session, Crypto);
|
|
330
355
|
|
|
331
|
-
|
|
332
|
-
expect(mapTellKnownStateMsg).toEqual({
|
|
333
|
-
action: "known",
|
|
334
|
-
...map.core.knownState(),
|
|
335
|
-
} satisfies SyncMessage);
|
|
356
|
+
const group = node.createGroup();
|
|
336
357
|
|
|
337
|
-
|
|
338
|
-
expect(yield* Queue.take(outRxQ)).toMatchObject(groupContentEx(group));
|
|
358
|
+
const map = group.createMap();
|
|
339
359
|
|
|
340
|
-
|
|
360
|
+
const [inRx, inTx] = newQueuePair();
|
|
361
|
+
const [outRx, outTx] = newQueuePair();
|
|
362
|
+
const outRxQ = outRx[Symbol.asyncIterator]();
|
|
341
363
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
364
|
+
node.syncManager.addPeer({
|
|
365
|
+
id: "test",
|
|
366
|
+
incoming: inRx,
|
|
367
|
+
outgoing: outTx,
|
|
368
|
+
role: "peer",
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
await inTx.push({
|
|
372
|
+
action: "load",
|
|
373
|
+
id: map.core.id,
|
|
374
|
+
header: false,
|
|
375
|
+
sessions: {
|
|
376
|
+
[node.currentSessionID]: 0,
|
|
377
|
+
},
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// expect((await outRxQ.next()).value).toMatchObject(admStateEx(admin.id));
|
|
381
|
+
expect((await outRxQ.next()).value).toMatchObject(groupStateEx(group));
|
|
382
|
+
|
|
383
|
+
const mapTellKnownStateMsg = (await outRxQ.next()).value;
|
|
384
|
+
expect(mapTellKnownStateMsg).toEqual({
|
|
385
|
+
action: "known",
|
|
386
|
+
...map.core.knownState(),
|
|
387
|
+
} satisfies SyncMessage);
|
|
388
|
+
|
|
389
|
+
// expect((await outRxQ.next()).value).toMatchObject(admContEx(admin.id));
|
|
390
|
+
expect((await outRxQ.next()).value).toMatchObject(groupContentEx(group));
|
|
391
|
+
|
|
392
|
+
const mapNewContentHeaderOnlyMsg = (await outRxQ.next()).value;
|
|
393
|
+
|
|
394
|
+
expect(mapNewContentHeaderOnlyMsg).toEqual({
|
|
395
|
+
action: "content",
|
|
396
|
+
id: map.core.id,
|
|
397
|
+
header: map.core.header,
|
|
398
|
+
new: {},
|
|
399
|
+
} satisfies SyncMessage);
|
|
400
|
+
|
|
401
|
+
map.set("hello", "world", "trusting");
|
|
402
|
+
|
|
403
|
+
map.set("goodbye", "world", "trusting");
|
|
404
|
+
|
|
405
|
+
const _mapEditMsgs = (await outRxQ.next()).value;
|
|
406
|
+
|
|
407
|
+
console.log("Sending correction");
|
|
408
|
+
|
|
409
|
+
await inTx.push({
|
|
410
|
+
action: "known",
|
|
411
|
+
isCorrection: true,
|
|
412
|
+
id: map.core.id,
|
|
413
|
+
header: true,
|
|
414
|
+
sessions: {
|
|
415
|
+
[node.currentSessionID]: 1,
|
|
416
|
+
},
|
|
417
|
+
} satisfies SyncMessage);
|
|
418
|
+
|
|
419
|
+
const newContentAfterWrongAssumedState = (await outRxQ.next()).value;
|
|
420
|
+
|
|
421
|
+
expect(newContentAfterWrongAssumedState).toEqual({
|
|
422
|
+
action: "content",
|
|
423
|
+
id: map.core.id,
|
|
424
|
+
header: undefined,
|
|
425
|
+
new: {
|
|
426
|
+
[node.currentSessionID]: {
|
|
427
|
+
after: 1,
|
|
428
|
+
newTransactions: [
|
|
429
|
+
{
|
|
430
|
+
privacy: "trusting" as const,
|
|
431
|
+
madeAt: map.core.sessionLogs.get(node.currentSessionID)!
|
|
432
|
+
.transactions[1]!.madeAt,
|
|
433
|
+
changes: stableStringify([
|
|
434
|
+
{
|
|
435
|
+
op: "set",
|
|
436
|
+
key: "goodbye",
|
|
437
|
+
value: "world",
|
|
438
|
+
} satisfies MapOpPayload<string, string>,
|
|
439
|
+
]),
|
|
440
|
+
},
|
|
441
|
+
],
|
|
442
|
+
lastSignature: map.core.sessionLogs.get(node.currentSessionID)!
|
|
443
|
+
.lastSignature!,
|
|
368
444
|
},
|
|
369
|
-
}
|
|
370
|
-
}
|
|
445
|
+
},
|
|
446
|
+
} satisfies SyncMessage);
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
test("If we add a peer, but it never subscribes to a coValue, it won't get any messages", async () => {
|
|
450
|
+
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
451
|
+
const node = new LocalNode(admin, session, Crypto);
|
|
452
|
+
|
|
453
|
+
const group = node.createGroup();
|
|
454
|
+
|
|
455
|
+
const map = group.createMap();
|
|
456
|
+
|
|
457
|
+
const [inRx, _inTx] = newQueuePair();
|
|
458
|
+
const [outRx, outTx] = newQueuePair();
|
|
459
|
+
const outRxQ = outRx[Symbol.asyncIterator]();
|
|
460
|
+
|
|
461
|
+
node.syncManager.addPeer({
|
|
462
|
+
id: "test",
|
|
463
|
+
incoming: inRx,
|
|
464
|
+
outgoing: outTx,
|
|
465
|
+
role: "peer",
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
map.set("hello", "world", "trusting");
|
|
371
469
|
|
|
372
|
-
|
|
373
|
-
|
|
470
|
+
const timeoutPromise = new Promise((resolve) =>
|
|
471
|
+
setTimeout(() => resolve("neverHappened"), 100),
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
const result = await Promise.race([
|
|
475
|
+
outRxQ.next().then((value) => value.value),
|
|
476
|
+
timeoutPromise,
|
|
477
|
+
]);
|
|
478
|
+
|
|
479
|
+
expect(result).toEqual("neverHappened");
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
test.todo(
|
|
483
|
+
"If we add a server peer, all updates to all coValues are sent to it, even if it doesn't subscribe",
|
|
484
|
+
async () => {
|
|
374
485
|
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
375
486
|
const node = new LocalNode(admin, session, Crypto);
|
|
376
487
|
|
|
@@ -378,85 +489,61 @@ test("No matter the optimistic known state, node respects invalid known state me
|
|
|
378
489
|
|
|
379
490
|
const map = group.createMap();
|
|
380
491
|
|
|
381
|
-
const [inRx,
|
|
382
|
-
const [outRx, outTx] =
|
|
383
|
-
const outRxQ =
|
|
384
|
-
yield* Effect.fork(Stream.run(outRx, Sink.fromQueue(outRxQ)));
|
|
492
|
+
const [inRx, _inTx] = newQueuePair();
|
|
493
|
+
const [outRx, outTx] = newQueuePair();
|
|
494
|
+
const outRxQ = outRx[Symbol.asyncIterator]();
|
|
385
495
|
|
|
386
496
|
node.syncManager.addPeer({
|
|
387
497
|
id: "test",
|
|
388
498
|
incoming: inRx,
|
|
389
499
|
outgoing: outTx,
|
|
390
|
-
role: "
|
|
500
|
+
role: "server",
|
|
391
501
|
});
|
|
392
502
|
|
|
393
|
-
|
|
503
|
+
// expect((await outRxQ.next()).value).toMatchObject({
|
|
504
|
+
// action: "load",
|
|
505
|
+
// id: adminID,
|
|
506
|
+
// });
|
|
507
|
+
expect((await outRxQ.next()).value).toMatchObject({
|
|
394
508
|
action: "load",
|
|
395
|
-
id:
|
|
396
|
-
header: false,
|
|
397
|
-
sessions: {
|
|
398
|
-
[node.currentSessionID]: 0,
|
|
399
|
-
},
|
|
509
|
+
id: group.core.id,
|
|
400
510
|
});
|
|
401
511
|
|
|
402
|
-
|
|
403
|
-
expect(yield* Queue.take(outRxQ)).toMatchObject(groupStateEx(group));
|
|
512
|
+
const mapSubscribeMsg = (await outRxQ.next()).value;
|
|
404
513
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
action: "known",
|
|
408
|
-
...map.core.knownState(),
|
|
409
|
-
} satisfies SyncMessage);
|
|
410
|
-
|
|
411
|
-
// expect(yield* Queue.take(outRxQ)).toMatchObject(admContEx(admin.id));
|
|
412
|
-
expect(yield* Queue.take(outRxQ)).toMatchObject(groupContentEx(group));
|
|
413
|
-
|
|
414
|
-
const mapNewContentHeaderOnlyMsg = yield* Queue.take(outRxQ);
|
|
415
|
-
|
|
416
|
-
expect(mapNewContentHeaderOnlyMsg).toEqual({
|
|
417
|
-
action: "content",
|
|
418
|
-
id: map.core.id,
|
|
419
|
-
header: map.core.header,
|
|
420
|
-
new: {},
|
|
421
|
-
} satisfies SyncMessage);
|
|
422
|
-
|
|
423
|
-
map.set("hello", "world", "trusting");
|
|
424
|
-
|
|
425
|
-
map.set("goodbye", "world", "trusting");
|
|
426
|
-
|
|
427
|
-
const _mapEditMsgs = yield* Queue.take(outRxQ);
|
|
428
|
-
|
|
429
|
-
console.log("Sending correction");
|
|
430
|
-
|
|
431
|
-
yield* Queue.offer(inTx, {
|
|
432
|
-
action: "known",
|
|
433
|
-
isCorrection: true,
|
|
514
|
+
expect(mapSubscribeMsg).toEqual({
|
|
515
|
+
action: "load",
|
|
434
516
|
id: map.core.id,
|
|
435
517
|
header: true,
|
|
436
|
-
sessions: {
|
|
437
|
-
[node.currentSessionID]: 1,
|
|
438
|
-
},
|
|
518
|
+
sessions: {},
|
|
439
519
|
} satisfies SyncMessage);
|
|
440
520
|
|
|
441
|
-
|
|
521
|
+
map.set("hello", "world", "trusting");
|
|
522
|
+
|
|
523
|
+
// expect((await outRxQ.next()).value).toMatchObject(admContEx(admin.id));
|
|
524
|
+
expect((await outRxQ.next()).value).toMatchObject(
|
|
525
|
+
groupContentEx(group),
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
const mapNewContentMsg = (await outRxQ.next()).value;
|
|
442
529
|
|
|
443
|
-
expect(
|
|
530
|
+
expect(mapNewContentMsg).toEqual({
|
|
444
531
|
action: "content",
|
|
445
532
|
id: map.core.id,
|
|
446
|
-
header:
|
|
533
|
+
header: map.core.header,
|
|
447
534
|
new: {
|
|
448
535
|
[node.currentSessionID]: {
|
|
449
|
-
after:
|
|
536
|
+
after: 0,
|
|
450
537
|
newTransactions: [
|
|
451
538
|
{
|
|
452
539
|
privacy: "trusting" as const,
|
|
453
540
|
madeAt: map.core.sessionLogs.get(
|
|
454
541
|
node.currentSessionID,
|
|
455
|
-
)!.transactions[
|
|
542
|
+
)!.transactions[0]!.madeAt,
|
|
456
543
|
changes: stableStringify([
|
|
457
544
|
{
|
|
458
545
|
op: "set",
|
|
459
|
-
key: "
|
|
546
|
+
key: "hello",
|
|
460
547
|
value: "world",
|
|
461
548
|
} satisfies MapOpPayload<string, string>,
|
|
462
549
|
]),
|
|
@@ -468,375 +555,246 @@ test("No matter the optimistic known state, node respects invalid known state me
|
|
|
468
555
|
},
|
|
469
556
|
},
|
|
470
557
|
} satisfies SyncMessage);
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
test("If we add a peer, but it never subscribes to a coValue, it won't get any messages", () =>
|
|
474
|
-
Effect.gen(function* () {
|
|
475
|
-
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
476
|
-
const node = new LocalNode(admin, session, Crypto);
|
|
477
|
-
|
|
478
|
-
const group = node.createGroup();
|
|
479
|
-
|
|
480
|
-
const map = group.createMap();
|
|
481
|
-
|
|
482
|
-
const [inRx, _inTx] = yield* newQueuePair();
|
|
483
|
-
const [outRx, outTx] = yield* newQueuePair();
|
|
484
|
-
const outRxQ = yield* Queue.unbounded<SyncMessage>();
|
|
485
|
-
yield* Effect.fork(Stream.run(outRx, Sink.fromQueue(outRxQ)));
|
|
486
|
-
|
|
487
|
-
node.syncManager.addPeer({
|
|
488
|
-
id: "test",
|
|
489
|
-
incoming: inRx,
|
|
490
|
-
outgoing: outTx,
|
|
491
|
-
role: "peer",
|
|
492
|
-
});
|
|
493
|
-
|
|
494
|
-
map.set("hello", "world", "trusting");
|
|
495
|
-
|
|
496
|
-
expect(
|
|
497
|
-
yield* Queue.take(outRxQ).pipe(
|
|
498
|
-
Effect.timeout(100),
|
|
499
|
-
Effect.catch("_tag", {
|
|
500
|
-
failure: "TimeoutException",
|
|
501
|
-
onFailure: () => Effect.succeed("neverHappened"),
|
|
502
|
-
}),
|
|
503
|
-
),
|
|
504
|
-
).toEqual("neverHappened");
|
|
505
|
-
}).pipe(Effect.scoped, Effect.runPromise));
|
|
506
|
-
|
|
507
|
-
test.todo(
|
|
508
|
-
"If we add a server peer, all updates to all coValues are sent to it, even if it doesn't subscribe",
|
|
509
|
-
() =>
|
|
510
|
-
Effect.gen(function* () {
|
|
511
|
-
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
512
|
-
const node = new LocalNode(admin, session, Crypto);
|
|
513
|
-
|
|
514
|
-
const group = node.createGroup();
|
|
515
|
-
|
|
516
|
-
const map = group.createMap();
|
|
517
|
-
|
|
518
|
-
const [inRx, _inTx] = yield* newQueuePair();
|
|
519
|
-
const [outRx, outTx] = yield* newQueuePair();
|
|
520
|
-
const outRxQ = yield* Queue.unbounded<SyncMessage>();
|
|
521
|
-
yield* Effect.fork(Stream.run(outRx, Sink.fromQueue(outRxQ)));
|
|
522
|
-
|
|
523
|
-
node.syncManager.addPeer({
|
|
524
|
-
id: "test",
|
|
525
|
-
incoming: inRx,
|
|
526
|
-
outgoing: outTx,
|
|
527
|
-
role: "server",
|
|
528
|
-
});
|
|
529
|
-
|
|
530
|
-
// expect(yield* Queue.take(outRxQ)).toMatchObject({
|
|
531
|
-
// action: "load",
|
|
532
|
-
// id: adminID,
|
|
533
|
-
// });
|
|
534
|
-
expect(yield* Queue.take(outRxQ)).toMatchObject({
|
|
535
|
-
action: "load",
|
|
536
|
-
id: group.core.id,
|
|
537
|
-
});
|
|
538
|
-
|
|
539
|
-
const mapSubscribeMsg = Queue.take(outRxQ);
|
|
540
|
-
|
|
541
|
-
expect(mapSubscribeMsg).toEqual({
|
|
542
|
-
action: "load",
|
|
543
|
-
id: map.core.id,
|
|
544
|
-
header: true,
|
|
545
|
-
sessions: {},
|
|
546
|
-
} satisfies SyncMessage);
|
|
547
|
-
|
|
548
|
-
map.set("hello", "world", "trusting");
|
|
549
|
-
|
|
550
|
-
// expect(yield* Queue.take(outRxQ)).toMatchObject(admContEx(admin.id));
|
|
551
|
-
expect(yield* Queue.take(outRxQ)).toMatchObject(
|
|
552
|
-
groupContentEx(group),
|
|
553
|
-
);
|
|
554
|
-
|
|
555
|
-
const mapNewContentMsg = Queue.take(outRxQ);
|
|
556
|
-
|
|
557
|
-
expect(mapNewContentMsg).toEqual({
|
|
558
|
-
action: "content",
|
|
559
|
-
id: map.core.id,
|
|
560
|
-
header: map.core.header,
|
|
561
|
-
new: {
|
|
562
|
-
[node.currentSessionID]: {
|
|
563
|
-
after: 0,
|
|
564
|
-
newTransactions: [
|
|
565
|
-
{
|
|
566
|
-
privacy: "trusting" as const,
|
|
567
|
-
madeAt: map.core.sessionLogs.get(
|
|
568
|
-
node.currentSessionID,
|
|
569
|
-
)!.transactions[0]!.madeAt,
|
|
570
|
-
changes: stableStringify([
|
|
571
|
-
{
|
|
572
|
-
op: "set",
|
|
573
|
-
key: "hello",
|
|
574
|
-
value: "world",
|
|
575
|
-
} satisfies MapOpPayload<string, string>,
|
|
576
|
-
]),
|
|
577
|
-
},
|
|
578
|
-
],
|
|
579
|
-
lastSignature: map.core.sessionLogs.get(
|
|
580
|
-
node.currentSessionID,
|
|
581
|
-
)!.lastSignature!,
|
|
582
|
-
},
|
|
583
|
-
},
|
|
584
|
-
} satisfies SyncMessage);
|
|
585
|
-
}).pipe(Effect.scoped, Effect.runPromise),
|
|
558
|
+
},
|
|
586
559
|
);
|
|
587
560
|
|
|
588
|
-
test.skip("If we add a server peer, newly created coValues are auto-subscribed to", () =>
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
const node = new LocalNode(admin, session, Crypto);
|
|
561
|
+
test.skip("If we add a server peer, newly created coValues are auto-subscribed to", async () => {
|
|
562
|
+
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
563
|
+
const node = new LocalNode(admin, session, Crypto);
|
|
592
564
|
|
|
593
|
-
|
|
565
|
+
const group = node.createGroup();
|
|
594
566
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
yield* Effect.fork(Stream.run(outRx, Sink.fromQueue(outRxQ)));
|
|
567
|
+
const [inRx, _inTx] = newQueuePair();
|
|
568
|
+
const [outRx, outTx] = newQueuePair();
|
|
569
|
+
const outRxQ = outRx[Symbol.asyncIterator]();
|
|
599
570
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
571
|
+
node.syncManager.addPeer({
|
|
572
|
+
id: "test",
|
|
573
|
+
incoming: inRx,
|
|
574
|
+
outgoing: outTx,
|
|
575
|
+
role: "server",
|
|
576
|
+
});
|
|
606
577
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
578
|
+
// expect((await outRxQ.next()).value).toMatchObject({
|
|
579
|
+
// action: "load",
|
|
580
|
+
// id: admin.id,
|
|
581
|
+
// });
|
|
582
|
+
expect((await outRxQ.next()).value).toMatchObject({
|
|
583
|
+
action: "load",
|
|
584
|
+
id: group.core.id,
|
|
585
|
+
});
|
|
615
586
|
|
|
616
|
-
|
|
587
|
+
const map = group.createMap();
|
|
617
588
|
|
|
618
|
-
|
|
589
|
+
const mapSubscribeMsg = (await outRxQ.next()).value;
|
|
619
590
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
591
|
+
expect(mapSubscribeMsg).toEqual({
|
|
592
|
+
action: "load",
|
|
593
|
+
...map.core.knownState(),
|
|
594
|
+
} satisfies SyncMessage);
|
|
624
595
|
|
|
625
|
-
|
|
626
|
-
|
|
596
|
+
// expect((await outRxQ.next()).value).toMatchObject(admContEx(adminID));
|
|
597
|
+
expect((await outRxQ.next()).value).toMatchObject(groupContentEx(group));
|
|
627
598
|
|
|
628
|
-
|
|
599
|
+
const mapContentMsg = (await outRxQ.next()).value;
|
|
629
600
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
601
|
+
expect(mapContentMsg).toEqual({
|
|
602
|
+
action: "content",
|
|
603
|
+
id: map.core.id,
|
|
604
|
+
header: map.core.header,
|
|
605
|
+
new: {},
|
|
606
|
+
} satisfies SyncMessage);
|
|
607
|
+
});
|
|
637
608
|
|
|
638
609
|
test.todo(
|
|
639
610
|
"TODO: when receiving a subscribe response that is behind our optimistic state (due to already sent content), we ignore it",
|
|
640
611
|
);
|
|
641
612
|
|
|
642
|
-
test("When we connect a new server peer, we try to sync all existing coValues to it", () =>
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
const node = new LocalNode(admin, session, Crypto);
|
|
613
|
+
test("When we connect a new server peer, we try to sync all existing coValues to it", async () => {
|
|
614
|
+
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
615
|
+
const node = new LocalNode(admin, session, Crypto);
|
|
646
616
|
|
|
647
|
-
|
|
617
|
+
const group = node.createGroup();
|
|
648
618
|
|
|
649
|
-
|
|
619
|
+
const map = group.createMap();
|
|
650
620
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
yield* Effect.fork(Stream.run(outRx, Sink.fromQueue(outRxQ)));
|
|
621
|
+
const [inRx, _inTx] = newQueuePair();
|
|
622
|
+
const [outRx, outTx] = newQueuePair();
|
|
623
|
+
const outRxQ = outRx[Symbol.asyncIterator]();
|
|
655
624
|
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
625
|
+
node.syncManager.addPeer({
|
|
626
|
+
id: "test",
|
|
627
|
+
incoming: inRx,
|
|
628
|
+
outgoing: outTx,
|
|
629
|
+
role: "server",
|
|
630
|
+
});
|
|
662
631
|
|
|
663
|
-
|
|
664
|
-
|
|
632
|
+
// const _adminSubscribeMessage = await outRxQ.next();
|
|
633
|
+
const groupSubscribeMessage = (await outRxQ.next()).value;
|
|
665
634
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
635
|
+
expect(groupSubscribeMessage).toEqual({
|
|
636
|
+
action: "load",
|
|
637
|
+
...group.core.knownState(),
|
|
638
|
+
} satisfies SyncMessage);
|
|
670
639
|
|
|
671
|
-
|
|
640
|
+
const secondMessage = (await outRxQ.next()).value;
|
|
672
641
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
642
|
+
expect(secondMessage).toEqual({
|
|
643
|
+
action: "load",
|
|
644
|
+
...map.core.knownState(),
|
|
645
|
+
} satisfies SyncMessage);
|
|
646
|
+
});
|
|
678
647
|
|
|
679
|
-
test("When receiving a subscribe with a known state that is ahead of our own, peers should respond with a corresponding subscribe response message", () =>
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
const node = new LocalNode(admin, session, Crypto);
|
|
648
|
+
test("When receiving a subscribe with a known state that is ahead of our own, peers should respond with a corresponding subscribe response message", async () => {
|
|
649
|
+
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
650
|
+
const node = new LocalNode(admin, session, Crypto);
|
|
683
651
|
|
|
684
|
-
|
|
652
|
+
const group = node.createGroup();
|
|
685
653
|
|
|
686
|
-
|
|
654
|
+
const map = group.createMap();
|
|
687
655
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
yield* Effect.fork(Stream.run(outRx, Sink.fromQueue(outRxQ)));
|
|
656
|
+
const [inRx, inTx] = newQueuePair();
|
|
657
|
+
const [outRx, outTx] = newQueuePair();
|
|
658
|
+
const outRxQ = outRx[Symbol.asyncIterator]();
|
|
692
659
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
660
|
+
node.syncManager.addPeer({
|
|
661
|
+
id: "test",
|
|
662
|
+
incoming: inRx,
|
|
663
|
+
outgoing: outTx,
|
|
664
|
+
role: "peer",
|
|
665
|
+
});
|
|
699
666
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
667
|
+
await inTx.push({
|
|
668
|
+
action: "load",
|
|
669
|
+
id: map.core.id,
|
|
670
|
+
header: true,
|
|
671
|
+
sessions: {
|
|
672
|
+
[node.currentSessionID]: 1,
|
|
673
|
+
},
|
|
674
|
+
});
|
|
708
675
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
676
|
+
// expect((await outRxQ.next()).value).toMatchObject(admStateEx(admin.id));
|
|
677
|
+
expect((await outRxQ.next()).value).toMatchObject(groupStateEx(group));
|
|
678
|
+
const mapTellKnownState = (await outRxQ.next()).value;
|
|
712
679
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
680
|
+
expect(mapTellKnownState).toEqual({
|
|
681
|
+
action: "known",
|
|
682
|
+
...map.core.knownState(),
|
|
683
|
+
} satisfies SyncMessage);
|
|
684
|
+
});
|
|
718
685
|
|
|
719
|
-
test.skip("When replaying creation and transactions of a coValue as new content, the receiving peer integrates this information", () =>
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
686
|
+
test.skip("When replaying creation and transactions of a coValue as new content, the receiving peer integrates this information", async () => {
|
|
687
|
+
// 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
|
|
688
|
+
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
723
689
|
|
|
724
|
-
|
|
690
|
+
const node1 = new LocalNode(admin, session, Crypto);
|
|
725
691
|
|
|
726
|
-
|
|
692
|
+
const group = node1.createGroup();
|
|
727
693
|
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
yield* Effect.fork(Stream.run(outRx1, Sink.fromQueue(outRxQ1)));
|
|
694
|
+
const [inRx1, inTx1] = newQueuePair();
|
|
695
|
+
const [outRx1, outTx1] = newQueuePair();
|
|
696
|
+
const outRxQ1 = outRx1[Symbol.asyncIterator]();
|
|
732
697
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
698
|
+
node1.syncManager.addPeer({
|
|
699
|
+
id: "test2",
|
|
700
|
+
incoming: inRx1,
|
|
701
|
+
outgoing: outTx1,
|
|
702
|
+
role: "server",
|
|
703
|
+
});
|
|
739
704
|
|
|
740
|
-
|
|
741
|
-
admin,
|
|
742
|
-
newRandomSessionID(admin.id),
|
|
743
|
-
Crypto,
|
|
744
|
-
);
|
|
705
|
+
const node2 = new LocalNode(admin, newRandomSessionID(admin.id), Crypto);
|
|
745
706
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
yield* Effect.fork(Stream.run(outRx2, Sink.fromQueue(outRxQ1)));
|
|
707
|
+
const [inRx2, inTx2] = newQueuePair();
|
|
708
|
+
const [outRx2, outTx2] = newQueuePair();
|
|
709
|
+
const outRxQ2 = outRx2[Symbol.asyncIterator]();
|
|
750
710
|
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
711
|
+
node2.syncManager.addPeer({
|
|
712
|
+
id: "test1",
|
|
713
|
+
incoming: inRx2,
|
|
714
|
+
outgoing: outTx2,
|
|
715
|
+
role: "client",
|
|
716
|
+
});
|
|
757
717
|
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
718
|
+
const adminSubscribeMessage = (await outRxQ1.next()).value;
|
|
719
|
+
expect(adminSubscribeMessage).toMatchObject({
|
|
720
|
+
action: "load",
|
|
721
|
+
id: admin.id,
|
|
722
|
+
});
|
|
723
|
+
const groupSubscribeMsg = (await outRxQ1.next()).value;
|
|
724
|
+
expect(groupSubscribeMsg).toMatchObject({
|
|
725
|
+
action: "load",
|
|
726
|
+
id: group.core.id,
|
|
727
|
+
});
|
|
768
728
|
|
|
769
|
-
|
|
770
|
-
|
|
729
|
+
await inTx2.push(adminSubscribeMessage);
|
|
730
|
+
await inTx2.push(groupSubscribeMsg);
|
|
771
731
|
|
|
772
|
-
|
|
773
|
-
|
|
732
|
+
// const adminTellKnownStateMsg = (await outRxQ2.next()).value;
|
|
733
|
+
// expect(adminTellKnownStateMsg).toMatchObject(admStateEx(admin.id));
|
|
774
734
|
|
|
775
|
-
|
|
776
|
-
|
|
735
|
+
const groupTellKnownStateMsg = (await outRxQ2.next()).value;
|
|
736
|
+
expect(groupTellKnownStateMsg).toMatchObject(groupStateEx(group));
|
|
777
737
|
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
],
|
|
782
|
-
).toBeDefined();
|
|
738
|
+
expect(
|
|
739
|
+
node2.syncManager.peers["test1"]!.optimisticKnownStates[group.core.id],
|
|
740
|
+
).toBeDefined();
|
|
783
741
|
|
|
784
|
-
|
|
785
|
-
|
|
742
|
+
// await inTx1.push(adminTellKnownStateMsg);
|
|
743
|
+
await inTx1.push(groupTellKnownStateMsg);
|
|
786
744
|
|
|
787
|
-
|
|
788
|
-
|
|
745
|
+
// const adminContentMsg = (await outRxQ1.next()).value;
|
|
746
|
+
// expect(adminContentMsg).toMatchObject(admContEx(admin.id));
|
|
789
747
|
|
|
790
|
-
|
|
791
|
-
|
|
748
|
+
const groupContentMsg = (await outRxQ1.next()).value;
|
|
749
|
+
expect(groupContentMsg).toMatchObject(groupContentEx(group));
|
|
792
750
|
|
|
793
|
-
|
|
794
|
-
|
|
751
|
+
// await inTx2.push(adminContentMsg);
|
|
752
|
+
await inTx2.push(groupContentMsg);
|
|
795
753
|
|
|
796
|
-
|
|
754
|
+
const map = group.createMap();
|
|
797
755
|
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
756
|
+
const mapSubscriptionMsg = (await outRxQ1.next()).value;
|
|
757
|
+
expect(mapSubscriptionMsg).toMatchObject({
|
|
758
|
+
action: "load",
|
|
759
|
+
id: map.core.id,
|
|
760
|
+
});
|
|
803
761
|
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
762
|
+
const mapNewContentMsg = (await outRxQ1.next()).value;
|
|
763
|
+
expect(mapNewContentMsg).toEqual({
|
|
764
|
+
action: "content",
|
|
765
|
+
id: map.core.id,
|
|
766
|
+
header: map.core.header,
|
|
767
|
+
new: {},
|
|
768
|
+
} satisfies SyncMessage);
|
|
811
769
|
|
|
812
|
-
|
|
770
|
+
await inTx2.push(mapSubscriptionMsg);
|
|
813
771
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
772
|
+
const mapTellKnownStateMsg = (await outRxQ2.next()).value;
|
|
773
|
+
expect(mapTellKnownStateMsg).toEqual({
|
|
774
|
+
action: "known",
|
|
775
|
+
id: map.core.id,
|
|
776
|
+
header: false,
|
|
777
|
+
sessions: {},
|
|
778
|
+
} satisfies SyncMessage);
|
|
821
779
|
|
|
822
|
-
|
|
780
|
+
expect(node2.coValues[map.core.id]?.state).toEqual("loading");
|
|
823
781
|
|
|
824
|
-
|
|
782
|
+
await inTx2.push(mapNewContentMsg);
|
|
825
783
|
|
|
826
|
-
|
|
784
|
+
map.set("hello", "world", "trusting");
|
|
827
785
|
|
|
828
|
-
|
|
786
|
+
const mapEditMsg = (await outRxQ1.next()).value;
|
|
829
787
|
|
|
830
|
-
|
|
788
|
+
await inTx2.push(mapEditMsg);
|
|
831
789
|
|
|
832
|
-
|
|
790
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
833
791
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
792
|
+
expect(
|
|
793
|
+
expectMap(
|
|
794
|
+
node2.expectCoValueLoaded(map.core.id).getCurrentContent(),
|
|
795
|
+
).get("hello"),
|
|
796
|
+
).toEqual("world");
|
|
797
|
+
});
|
|
840
798
|
|
|
841
799
|
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 () => {
|
|
842
800
|
/*
|
|
@@ -867,125 +825,109 @@ test.skip("When loading a coValue on one node, the server node it is requested f
|
|
|
867
825
|
*/
|
|
868
826
|
});
|
|
869
827
|
|
|
870
|
-
test("Can sync a coValue through a server to another client", () =>
|
|
871
|
-
|
|
872
|
-
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
873
|
-
|
|
874
|
-
const client1 = new LocalNode(admin, session, Crypto);
|
|
875
|
-
|
|
876
|
-
const group = client1.createGroup();
|
|
877
|
-
|
|
878
|
-
const map = group.createMap();
|
|
879
|
-
map.set("hello", "world", "trusting");
|
|
880
|
-
|
|
881
|
-
const [serverUser, serverSession] =
|
|
882
|
-
randomAnonymousAccountAndSessionID();
|
|
883
|
-
|
|
884
|
-
const server = new LocalNode(serverUser, serverSession, Crypto);
|
|
885
|
-
|
|
886
|
-
const [serverAsPeerForClient1, client1AsPeer] = yield* connectedPeers(
|
|
887
|
-
"serverFor1",
|
|
888
|
-
"client1",
|
|
889
|
-
{
|
|
890
|
-
peer1role: "server",
|
|
891
|
-
peer2role: "client",
|
|
892
|
-
trace: true,
|
|
893
|
-
},
|
|
894
|
-
);
|
|
895
|
-
|
|
896
|
-
client1.syncManager.addPeer(serverAsPeerForClient1);
|
|
897
|
-
server.syncManager.addPeer(client1AsPeer);
|
|
898
|
-
|
|
899
|
-
const client2 = new LocalNode(
|
|
900
|
-
admin,
|
|
901
|
-
newRandomSessionID(admin.id),
|
|
902
|
-
Crypto,
|
|
903
|
-
);
|
|
904
|
-
|
|
905
|
-
const [serverAsPeerForClient2, client2AsPeer] = yield* connectedPeers(
|
|
906
|
-
"serverFor2",
|
|
907
|
-
"client2",
|
|
908
|
-
{
|
|
909
|
-
peer1role: "server",
|
|
910
|
-
peer2role: "client",
|
|
911
|
-
trace: true,
|
|
912
|
-
},
|
|
913
|
-
);
|
|
914
|
-
|
|
915
|
-
client2.syncManager.addPeer(serverAsPeerForClient2);
|
|
916
|
-
server.syncManager.addPeer(client2AsPeer);
|
|
917
|
-
|
|
918
|
-
const mapOnClient2 = yield* Effect.promise(() =>
|
|
919
|
-
client2.loadCoValueCore(map.core.id),
|
|
920
|
-
);
|
|
921
|
-
if (mapOnClient2 === "unavailable") {
|
|
922
|
-
throw new Error("Map is unavailable");
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
expect(
|
|
926
|
-
expectMap(mapOnClient2.getCurrentContent()).get("hello"),
|
|
927
|
-
).toEqual("world");
|
|
928
|
-
}).pipe(Effect.scoped, Effect.runPromise));
|
|
929
|
-
|
|
930
|
-
test("Can sync a coValue with private transactions through a server to another client", () =>
|
|
931
|
-
Effect.gen(function* () {
|
|
932
|
-
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
933
|
-
|
|
934
|
-
const client1 = new LocalNode(admin, session, Crypto);
|
|
935
|
-
|
|
936
|
-
const group = client1.createGroup();
|
|
937
|
-
|
|
938
|
-
const map = group.createMap();
|
|
939
|
-
map.set("hello", "world", "private");
|
|
940
|
-
|
|
941
|
-
const [serverUser, serverSession] =
|
|
942
|
-
randomAnonymousAccountAndSessionID();
|
|
828
|
+
test("Can sync a coValue through a server to another client", async () => {
|
|
829
|
+
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
943
830
|
|
|
944
|
-
|
|
831
|
+
const client1 = new LocalNode(admin, session, Crypto);
|
|
945
832
|
|
|
946
|
-
|
|
947
|
-
"server",
|
|
948
|
-
"client1",
|
|
949
|
-
{
|
|
950
|
-
trace: true,
|
|
951
|
-
peer1role: "server",
|
|
952
|
-
peer2role: "client",
|
|
953
|
-
},
|
|
954
|
-
);
|
|
833
|
+
const group = client1.createGroup();
|
|
955
834
|
|
|
956
|
-
|
|
957
|
-
|
|
835
|
+
const map = group.createMap();
|
|
836
|
+
map.set("hello", "world", "trusting");
|
|
958
837
|
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
838
|
+
const [serverUser, serverSession] = randomAnonymousAccountAndSessionID();
|
|
839
|
+
|
|
840
|
+
const server = new LocalNode(serverUser, serverSession, Crypto);
|
|
841
|
+
|
|
842
|
+
const [serverAsPeerForClient1, client1AsPeer] = await connectedPeers(
|
|
843
|
+
"serverFor1",
|
|
844
|
+
"client1",
|
|
845
|
+
{
|
|
846
|
+
peer1role: "server",
|
|
847
|
+
peer2role: "client",
|
|
848
|
+
trace: true,
|
|
849
|
+
},
|
|
850
|
+
);
|
|
851
|
+
|
|
852
|
+
client1.syncManager.addPeer(serverAsPeerForClient1);
|
|
853
|
+
server.syncManager.addPeer(client1AsPeer);
|
|
854
|
+
|
|
855
|
+
const client2 = new LocalNode(admin, newRandomSessionID(admin.id), Crypto);
|
|
856
|
+
|
|
857
|
+
const [serverAsPeerForClient2, client2AsPeer] = connectedPeers(
|
|
858
|
+
"serverFor2",
|
|
859
|
+
"client2",
|
|
860
|
+
{
|
|
861
|
+
peer1role: "server",
|
|
862
|
+
peer2role: "client",
|
|
863
|
+
trace: true,
|
|
864
|
+
},
|
|
865
|
+
);
|
|
866
|
+
|
|
867
|
+
client2.syncManager.addPeer(serverAsPeerForClient2);
|
|
868
|
+
server.syncManager.addPeer(client2AsPeer);
|
|
869
|
+
|
|
870
|
+
const mapOnClient2 = await client2.loadCoValueCore(map.core.id);
|
|
871
|
+
if (mapOnClient2 === "unavailable") {
|
|
872
|
+
throw new Error("Map is unavailable");
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
expect(expectMap(mapOnClient2.getCurrentContent()).get("hello")).toEqual(
|
|
876
|
+
"world",
|
|
877
|
+
);
|
|
878
|
+
});
|
|
964
879
|
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
"client2",
|
|
968
|
-
{
|
|
969
|
-
trace: true,
|
|
970
|
-
peer1role: "server",
|
|
971
|
-
peer2role: "client",
|
|
972
|
-
},
|
|
973
|
-
);
|
|
880
|
+
test("Can sync a coValue with private transactions through a server to another client", async () => {
|
|
881
|
+
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
974
882
|
|
|
975
|
-
|
|
976
|
-
server.syncManager.addPeer(client2AsPeer);
|
|
883
|
+
const client1 = new LocalNode(admin, session, Crypto);
|
|
977
884
|
|
|
978
|
-
|
|
979
|
-
client2.loadCoValueCore(map.core.id),
|
|
980
|
-
);
|
|
981
|
-
if (mapOnClient2 === "unavailable") {
|
|
982
|
-
throw new Error("Map is unavailable");
|
|
983
|
-
}
|
|
885
|
+
const group = client1.createGroup();
|
|
984
886
|
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
887
|
+
const map = group.createMap();
|
|
888
|
+
map.set("hello", "world", "private");
|
|
889
|
+
|
|
890
|
+
const [serverUser, serverSession] = randomAnonymousAccountAndSessionID();
|
|
891
|
+
|
|
892
|
+
const server = new LocalNode(serverUser, serverSession, Crypto);
|
|
893
|
+
|
|
894
|
+
const [serverAsPeer, client1AsPeer] = await connectedPeers(
|
|
895
|
+
"server",
|
|
896
|
+
"client1",
|
|
897
|
+
{
|
|
898
|
+
trace: true,
|
|
899
|
+
peer1role: "server",
|
|
900
|
+
peer2role: "client",
|
|
901
|
+
},
|
|
902
|
+
);
|
|
903
|
+
|
|
904
|
+
client1.syncManager.addPeer(serverAsPeer);
|
|
905
|
+
server.syncManager.addPeer(client1AsPeer);
|
|
906
|
+
|
|
907
|
+
const client2 = new LocalNode(admin, newRandomSessionID(admin.id), Crypto);
|
|
908
|
+
|
|
909
|
+
const [serverAsOtherPeer, client2AsPeer] = await connectedPeers(
|
|
910
|
+
"server",
|
|
911
|
+
"client2",
|
|
912
|
+
{
|
|
913
|
+
trace: true,
|
|
914
|
+
peer1role: "server",
|
|
915
|
+
peer2role: "client",
|
|
916
|
+
},
|
|
917
|
+
);
|
|
918
|
+
|
|
919
|
+
client2.syncManager.addPeer(serverAsOtherPeer);
|
|
920
|
+
server.syncManager.addPeer(client2AsPeer);
|
|
921
|
+
|
|
922
|
+
const mapOnClient2 = await client2.loadCoValueCore(map.core.id);
|
|
923
|
+
if (mapOnClient2 === "unavailable") {
|
|
924
|
+
throw new Error("Map is unavailable");
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
expect(expectMap(mapOnClient2.getCurrentContent()).get("hello")).toEqual(
|
|
928
|
+
"world",
|
|
929
|
+
);
|
|
930
|
+
});
|
|
989
931
|
|
|
990
932
|
test.skip("When a peer's incoming/readable stream closes, we remove the peer", async () => {
|
|
991
933
|
/*
|
|
@@ -1100,50 +1042,41 @@ test.skip("When a peer's outgoing/writable stream closes, we remove the peer", a
|
|
|
1100
1042
|
*/
|
|
1101
1043
|
});
|
|
1102
1044
|
|
|
1103
|
-
test("If we start loading a coValue before connecting to a peer that has it, it will load it once we connect", () =>
|
|
1104
|
-
|
|
1105
|
-
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
1045
|
+
test("If we start loading a coValue before connecting to a peer that has it, it will load it once we connect", async () => {
|
|
1046
|
+
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
1106
1047
|
|
|
1107
|
-
|
|
1048
|
+
const node1 = new LocalNode(admin, session, Crypto);
|
|
1108
1049
|
|
|
1109
|
-
|
|
1050
|
+
const group = node1.createGroup();
|
|
1110
1051
|
|
|
1111
|
-
|
|
1112
|
-
|
|
1052
|
+
const map = group.createMap();
|
|
1053
|
+
map.set("hello", "world", "trusting");
|
|
1113
1054
|
|
|
1114
|
-
|
|
1115
|
-
admin,
|
|
1116
|
-
newRandomSessionID(admin.id),
|
|
1117
|
-
Crypto,
|
|
1118
|
-
);
|
|
1055
|
+
const node2 = new LocalNode(admin, newRandomSessionID(admin.id), Crypto);
|
|
1119
1056
|
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
peer2role: "client",
|
|
1126
|
-
trace: true,
|
|
1127
|
-
},
|
|
1128
|
-
);
|
|
1057
|
+
const [node1asPeer, node2asPeer] = await connectedPeers("peer1", "peer2", {
|
|
1058
|
+
peer1role: "server",
|
|
1059
|
+
peer2role: "client",
|
|
1060
|
+
trace: true,
|
|
1061
|
+
});
|
|
1129
1062
|
|
|
1130
|
-
|
|
1063
|
+
node1.syncManager.addPeer(node2asPeer);
|
|
1131
1064
|
|
|
1132
|
-
|
|
1065
|
+
const mapOnNode2Promise = node2.loadCoValueCore(map.core.id);
|
|
1133
1066
|
|
|
1134
|
-
|
|
1067
|
+
expect(node2.coValues[map.core.id]?.state).toEqual("loading");
|
|
1135
1068
|
|
|
1136
|
-
|
|
1069
|
+
node2.syncManager.addPeer(node1asPeer);
|
|
1137
1070
|
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1071
|
+
const mapOnNode2 = await mapOnNode2Promise;
|
|
1072
|
+
if (mapOnNode2 === "unavailable") {
|
|
1073
|
+
throw new Error("Map is unavailable");
|
|
1074
|
+
}
|
|
1142
1075
|
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1076
|
+
expect(expectMap(mapOnNode2.getCurrentContent()).get("hello")).toEqual(
|
|
1077
|
+
"world",
|
|
1078
|
+
);
|
|
1079
|
+
});
|
|
1147
1080
|
|
|
1148
1081
|
function groupContentEx(group: RawGroup) {
|
|
1149
1082
|
return {
|