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