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.
@@ -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
- Effect.gen(function* () {
19
- const [admin, session] = randomAnonymousAccountAndSessionID();
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
- const group = node.createGroup();
20
+ const group = node.createGroup();
23
21
 
24
- const map = group.createMap();
22
+ const map = group.createMap();
25
23
 
26
- map.set("hello", "world", "trusting");
24
+ map.set("hello", "world", "trusting");
27
25
 
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)));
26
+ const [inRx, inTx] = newQueuePair();
27
+ const [outRx, outTx] = newQueuePair();
28
+ const outRxQ = outRx[Symbol.asyncIterator]();
32
29
 
33
- node.syncManager.addPeer({
34
- id: "test",
35
- incoming: inRx,
36
- outgoing: outTx,
37
- role: "peer",
38
- });
30
+ node.syncManager.addPeer({
31
+ id: "test",
32
+ incoming: inRx,
33
+ outgoing: outTx,
34
+ role: "peer",
35
+ });
39
36
 
40
- yield* Queue.offer(inTx, {
41
- action: "load",
42
- id: map.core.id,
43
- header: false,
44
- sessions: {},
45
- });
37
+ await inTx.push({
38
+ action: "load",
39
+ id: map.core.id,
40
+ header: false,
41
+ sessions: {},
42
+ });
46
43
 
47
- // expect((yield* Queue.take(outRxQ))).toMatchObject(admStateEx(admin.id));
48
- expect(yield* Queue.take(outRxQ)).toMatchObject(groupStateEx(group));
44
+ // expect((await outRxQ.next()).value).toMatchObject(admStateEx(admin.id));
45
+ expect((await outRxQ.next()).value).toMatchObject(groupStateEx(group));
49
46
 
50
- const mapTellKnownStateMsg = yield* Queue.take(outRxQ);
51
- expect(mapTellKnownStateMsg).toEqual({
52
- action: "known",
53
- ...map.core.knownState(),
54
- } satisfies SyncMessage);
47
+ const mapTellKnownStateMsg = (await outRxQ.next()).value;
48
+ expect(mapTellKnownStateMsg).toEqual({
49
+ action: "known",
50
+ ...map.core.knownState(),
51
+ } satisfies SyncMessage);
55
52
 
56
- // expect((yield * Queue.take(outRxQ))).toMatchObject(admContEx(admin.id));
57
- expect(yield* Queue.take(outRxQ)).toMatchObject(groupContentEx(group));
53
+ // expect((await outRxQ.next()).value).toMatchObject(admContEx(admin.id));
54
+ expect((await outRxQ.next()).value).toMatchObject(groupContentEx(group));
58
55
 
59
- const newContentMsg = yield* Queue.take(outRxQ);
56
+ const newContentMsg = (await outRxQ.next()).value;
60
57
 
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,
70
- },
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
- },
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
- } satisfies SyncMessage);
95
- }).pipe(Effect.scoped, Effect.runPromise));
88
+ },
89
+ } satisfies SyncMessage);
90
+ });
96
91
 
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);
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
- const group = node.createGroup();
96
+ const group = node.createGroup();
103
97
 
104
- const map = group.createMap();
98
+ const map = group.createMap();
105
99
 
106
- map.set("hello", "world", "trusting");
107
- map.set("goodbye", "world", "trusting");
100
+ map.set("hello", "world", "trusting");
101
+ map.set("goodbye", "world", "trusting");
108
102
 
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)));
103
+ const [inRx, inTx] = newQueuePair();
104
+ const [outRx, outTx] = newQueuePair();
105
+ const outRxQ = outRx[Symbol.asyncIterator]();
113
106
 
114
- node.syncManager.addPeer({
115
- id: "test",
116
- incoming: inRx,
117
- outgoing: outTx,
118
- role: "peer",
119
- });
107
+ node.syncManager.addPeer({
108
+ id: "test",
109
+ incoming: inRx,
110
+ outgoing: outTx,
111
+ role: "peer",
112
+ });
120
113
 
121
- yield* Queue.offer(inTx, {
122
- action: "load",
123
- id: map.core.id,
124
- header: true,
125
- sessions: {
126
- [node.currentSessionID]: 1,
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
- // expect(yield* Queue.take(outRxQ)).toMatchObject(admStateEx(admin.id));
131
- expect(yield* Queue.take(outRxQ)).toMatchObject(groupStateEx(group));
123
+ // expect((await outRxQ.next()).value).toMatchObject(admStateEx(admin.id));
124
+ expect((await outRxQ.next()).value).toMatchObject(groupStateEx(group));
132
125
 
133
- const mapTellKnownStateMsg = yield* Queue.take(outRxQ);
134
- expect(mapTellKnownStateMsg).toEqual({
135
- action: "known",
136
- ...map.core.knownState(),
137
- } satisfies SyncMessage);
126
+ const mapTellKnownStateMsg = (await outRxQ.next()).value;
127
+ expect(mapTellKnownStateMsg).toEqual({
128
+ action: "known",
129
+ ...map.core.knownState(),
130
+ } satisfies SyncMessage);
138
131
 
139
- // expect(yield* Queue.take(outRxQ))).toMatchObject(admContEx(admin.id));
140
- expect(yield* Queue.take(outRxQ)).toMatchObject(groupContentEx(group));
132
+ // expect((await outRxQ.next()).value).toMatchObject(admContEx(admin.id));
133
+ expect((await outRxQ.next()).value).toMatchObject(groupContentEx(group));
141
134
 
142
- const mapNewContentMsg = yield* Queue.take(outRxQ);
135
+ const mapNewContentMsg = (await outRxQ.next()).value;
143
136
 
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
- },
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
- } satisfies SyncMessage);
172
- }).pipe(Effect.scoped, Effect.runPromise));
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
- Effect.gen(function* () {
180
- const [admin, session] = randomAnonymousAccountAndSessionID();
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
- const group = node.createGroup();
172
+ const group = node.createGroup();
184
173
 
185
- const map = group.createMap();
174
+ const map = group.createMap();
186
175
 
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)));
176
+ const [inRx, inTx] = newQueuePair();
177
+ const [outRx, outTx] = newQueuePair();
178
+ const outRxQ = outRx[Symbol.asyncIterator]();
191
179
 
192
- node.syncManager.addPeer({
193
- id: "test",
194
- incoming: inRx,
195
- outgoing: outTx,
196
- role: "peer",
197
- });
180
+ node.syncManager.addPeer({
181
+ id: "test",
182
+ incoming: inRx,
183
+ outgoing: outTx,
184
+ role: "peer",
185
+ });
198
186
 
199
- yield* Queue.offer(inTx, {
200
- action: "load",
201
- id: map.core.id,
202
- header: false,
203
- sessions: {
204
- [node.currentSessionID]: 0,
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
- // expect(yield* Queue.take(outRxQ)).toMatchObject(admStateEx(admin.id));
209
- expect(yield* Queue.take(outRxQ)).toMatchObject(groupStateEx(group));
196
+ // expect((await outRxQ.next()).value).toMatchObject(admStateEx(admin.id));
197
+ expect((await outRxQ.next()).value).toMatchObject(groupStateEx(group));
210
198
 
211
- const mapTellKnownStateMsg = yield* Queue.take(outRxQ);
212
- expect(mapTellKnownStateMsg).toEqual({
213
- action: "known",
214
- ...map.core.knownState(),
215
- } satisfies SyncMessage);
199
+ const mapTellKnownStateMsg = (await outRxQ.next()).value;
200
+ expect(mapTellKnownStateMsg).toEqual({
201
+ action: "known",
202
+ ...map.core.knownState(),
203
+ } satisfies SyncMessage);
216
204
 
217
- // expect(yield* Queue.take(outRxQ)).toMatchObject(admContEx(admin.id));
218
- expect(yield* Queue.take(outRxQ)).toMatchObject(groupContentEx(group));
205
+ // expect((await outRxQ.next()).value).toMatchObject(admContEx(admin.id));
206
+ expect((await outRxQ.next()).value).toMatchObject(groupContentEx(group));
219
207
 
220
- const mapNewContentHeaderOnlyMsg = yield* Queue.take(outRxQ);
208
+ const mapNewContentHeaderOnlyMsg = (await outRxQ.next()).value;
221
209
 
222
- expect(mapNewContentHeaderOnlyMsg).toEqual({
223
- action: "content",
224
- id: map.core.id,
225
- header: map.core.header,
226
- new: {},
227
- } satisfies SyncMessage);
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
- map.set("hello", "world", "trusting");
217
+ map.set("hello", "world", "trusting");
230
218
 
231
- const mapEditMsg1 = yield* Queue.take(outRxQ);
219
+ const mapEditMsg1 = (await outRxQ.next()).value;
232
220
 
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
- },
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
- } satisfies SyncMessage);
244
+ },
245
+ } satisfies SyncMessage);
260
246
 
261
- map.set("goodbye", "world", "trusting");
247
+ map.set("goodbye", "world", "trusting");
262
248
 
263
- const mapEditMsg2 = yield* Queue.take(outRxQ);
249
+ const mapEditMsg2 = (await outRxQ.next()).value;
264
250
 
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
- },
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
- } satisfies SyncMessage);
292
- }).pipe(Effect.scoped, Effect.runPromise));
274
+ },
275
+ } satisfies SyncMessage);
276
+ });
293
277
 
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);
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
- const group = node.createGroup();
282
+ const group = node.createGroup();
300
283
 
301
- const map = group.createMap();
284
+ const map = group.createMap();
302
285
 
303
- map.set("hello", "world", "trusting");
286
+ map.set("hello", "world", "trusting");
304
287
 
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)));
288
+ const [inRx, inTx] = newQueuePair();
289
+ const [outRx, outTx] = newQueuePair();
290
+ const outRxQ = outRx[Symbol.asyncIterator]();
309
291
 
310
- node.syncManager.addPeer({
311
- id: "test",
312
- incoming: inRx,
313
- outgoing: outTx,
314
- role: "peer",
315
- });
292
+ node.syncManager.addPeer({
293
+ id: "test",
294
+ incoming: inRx,
295
+ outgoing: outTx,
296
+ role: "peer",
297
+ });
316
298
 
317
- // expect(yield* Queue.take(outRxQ)).toMatchObject(groupStateEx(group));
299
+ // expect((await outRxQ.next()).value).toMatchObject(groupStateEx(group));
318
300
 
319
- yield* Queue.offer(inTx, {
320
- action: "known",
321
- id: map.core.id,
322
- header: false,
323
- sessions: {
324
- [node.currentSessionID]: 0,
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
- // expect(yield* Queue.take(outRxQ)).toMatchObject(admStateEx(admin.id));
329
- expect(yield* Queue.take(outRxQ)).toMatchObject(groupStateEx(group));
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
- const mapTellKnownStateMsg = yield* Queue.take(outRxQ);
332
- expect(mapTellKnownStateMsg).toEqual({
333
- action: "known",
334
- ...map.core.knownState(),
335
- } satisfies SyncMessage);
356
+ const group = node.createGroup();
336
357
 
337
- // expect(yield* Queue.take(outRxQ)).toMatchObject(admContEx(admin.id));
338
- expect(yield* Queue.take(outRxQ)).toMatchObject(groupContentEx(group));
358
+ const map = group.createMap();
339
359
 
340
- const mapNewContentMsg = yield* Queue.take(outRxQ);
360
+ const [inRx, inTx] = newQueuePair();
361
+ const [outRx, outTx] = newQueuePair();
362
+ const outRxQ = outRx[Symbol.asyncIterator]();
341
363
 
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
- },
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
- } satisfies SyncMessage);
370
- }).pipe(Effect.scoped, Effect.runPromise));
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
- test("No matter the optimistic known state, node respects invalid known state messages and resyncs", () =>
373
- Effect.gen(function* () {
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, 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)));
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: "peer",
500
+ role: "server",
391
501
  });
392
502
 
393
- yield* Queue.offer(inTx, {
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: map.core.id,
396
- header: false,
397
- sessions: {
398
- [node.currentSessionID]: 0,
399
- },
509
+ id: group.core.id,
400
510
  });
401
511
 
402
- // expect(yield* Queue.take(outRxQ)).toMatchObject(admStateEx(admin.id));
403
- expect(yield* Queue.take(outRxQ)).toMatchObject(groupStateEx(group));
512
+ const mapSubscribeMsg = (await outRxQ.next()).value;
404
513
 
405
- const mapTellKnownStateMsg = yield* Queue.take(outRxQ);
406
- expect(mapTellKnownStateMsg).toEqual({
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
- const newContentAfterWrongAssumedState = yield* Queue.take(outRxQ);
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(newContentAfterWrongAssumedState).toEqual({
530
+ expect(mapNewContentMsg).toEqual({
444
531
  action: "content",
445
532
  id: map.core.id,
446
- header: undefined,
533
+ header: map.core.header,
447
534
  new: {
448
535
  [node.currentSessionID]: {
449
- after: 1,
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[1]!.madeAt,
542
+ )!.transactions[0]!.madeAt,
456
543
  changes: stableStringify([
457
544
  {
458
545
  op: "set",
459
- key: "goodbye",
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
- }).pipe(Effect.scoped, Effect.runPromise));
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
- Effect.gen(function* () {
590
- const [admin, session] = randomAnonymousAccountAndSessionID();
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
- const group = node.createGroup();
565
+ const group = node.createGroup();
594
566
 
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)));
567
+ const [inRx, _inTx] = newQueuePair();
568
+ const [outRx, outTx] = newQueuePair();
569
+ const outRxQ = outRx[Symbol.asyncIterator]();
599
570
 
600
- node.syncManager.addPeer({
601
- id: "test",
602
- incoming: inRx,
603
- outgoing: outTx,
604
- role: "server",
605
- });
571
+ node.syncManager.addPeer({
572
+ id: "test",
573
+ incoming: inRx,
574
+ outgoing: outTx,
575
+ role: "server",
576
+ });
606
577
 
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
- });
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
- const map = group.createMap();
587
+ const map = group.createMap();
617
588
 
618
- const mapSubscribeMsg = Queue.take(outRxQ);
589
+ const mapSubscribeMsg = (await outRxQ.next()).value;
619
590
 
620
- expect(mapSubscribeMsg).toEqual({
621
- action: "load",
622
- ...map.core.knownState(),
623
- } satisfies SyncMessage);
591
+ expect(mapSubscribeMsg).toEqual({
592
+ action: "load",
593
+ ...map.core.knownState(),
594
+ } satisfies SyncMessage);
624
595
 
625
- // expect(yield* Queue.take(outRxQ)).toMatchObject(admContEx(adminID));
626
- expect(yield* Queue.take(outRxQ)).toMatchObject(groupContentEx(group));
596
+ // expect((await outRxQ.next()).value).toMatchObject(admContEx(adminID));
597
+ expect((await outRxQ.next()).value).toMatchObject(groupContentEx(group));
627
598
 
628
- const mapContentMsg = Queue.take(outRxQ);
599
+ const mapContentMsg = (await outRxQ.next()).value;
629
600
 
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));
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
- Effect.gen(function* () {
644
- const [admin, session] = randomAnonymousAccountAndSessionID();
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
- const group = node.createGroup();
617
+ const group = node.createGroup();
648
618
 
649
- const map = group.createMap();
619
+ const map = group.createMap();
650
620
 
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)));
621
+ const [inRx, _inTx] = newQueuePair();
622
+ const [outRx, outTx] = newQueuePair();
623
+ const outRxQ = outRx[Symbol.asyncIterator]();
655
624
 
656
- node.syncManager.addPeer({
657
- id: "test",
658
- incoming: inRx,
659
- outgoing: outTx,
660
- role: "server",
661
- });
625
+ node.syncManager.addPeer({
626
+ id: "test",
627
+ incoming: inRx,
628
+ outgoing: outTx,
629
+ role: "server",
630
+ });
662
631
 
663
- // const _adminSubscribeMessage = yield* Queue.take(outRxQ);
664
- const groupSubscribeMessage = yield* Queue.take(outRxQ);
632
+ // const _adminSubscribeMessage = await outRxQ.next();
633
+ const groupSubscribeMessage = (await outRxQ.next()).value;
665
634
 
666
- expect(groupSubscribeMessage).toEqual({
667
- action: "load",
668
- ...group.core.knownState(),
669
- } satisfies SyncMessage);
635
+ expect(groupSubscribeMessage).toEqual({
636
+ action: "load",
637
+ ...group.core.knownState(),
638
+ } satisfies SyncMessage);
670
639
 
671
- const secondMessage = yield* Queue.take(outRxQ);
640
+ const secondMessage = (await outRxQ.next()).value;
672
641
 
673
- expect(secondMessage).toEqual({
674
- action: "load",
675
- ...map.core.knownState(),
676
- } satisfies SyncMessage);
677
- }).pipe(Effect.scoped, Effect.runPromise));
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
- Effect.gen(function* () {
681
- const [admin, session] = randomAnonymousAccountAndSessionID();
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
- const group = node.createGroup();
652
+ const group = node.createGroup();
685
653
 
686
- const map = group.createMap();
654
+ const map = group.createMap();
687
655
 
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)));
656
+ const [inRx, inTx] = newQueuePair();
657
+ const [outRx, outTx] = newQueuePair();
658
+ const outRxQ = outRx[Symbol.asyncIterator]();
692
659
 
693
- node.syncManager.addPeer({
694
- id: "test",
695
- incoming: inRx,
696
- outgoing: outTx,
697
- role: "peer",
698
- });
660
+ node.syncManager.addPeer({
661
+ id: "test",
662
+ incoming: inRx,
663
+ outgoing: outTx,
664
+ role: "peer",
665
+ });
699
666
 
700
- yield* Queue.offer(inTx, {
701
- action: "load",
702
- id: map.core.id,
703
- header: true,
704
- sessions: {
705
- [node.currentSessionID]: 1,
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
- // 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);
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
- expect(mapTellKnownState).toEqual({
714
- action: "known",
715
- ...map.core.knownState(),
716
- } satisfies SyncMessage);
717
- }).pipe(Effect.scoped, Effect.runPromise));
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
- 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();
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
- const node1 = new LocalNode(admin, session, Crypto);
690
+ const node1 = new LocalNode(admin, session, Crypto);
725
691
 
726
- const group = node1.createGroup();
692
+ const group = node1.createGroup();
727
693
 
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)));
694
+ const [inRx1, inTx1] = newQueuePair();
695
+ const [outRx1, outTx1] = newQueuePair();
696
+ const outRxQ1 = outRx1[Symbol.asyncIterator]();
732
697
 
733
- node1.syncManager.addPeer({
734
- id: "test2",
735
- incoming: inRx1,
736
- outgoing: outTx1,
737
- role: "server",
738
- });
698
+ node1.syncManager.addPeer({
699
+ id: "test2",
700
+ incoming: inRx1,
701
+ outgoing: outTx1,
702
+ role: "server",
703
+ });
739
704
 
740
- const node2 = new LocalNode(
741
- admin,
742
- newRandomSessionID(admin.id),
743
- Crypto,
744
- );
705
+ const node2 = new LocalNode(admin, newRandomSessionID(admin.id), Crypto);
745
706
 
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)));
707
+ const [inRx2, inTx2] = newQueuePair();
708
+ const [outRx2, outTx2] = newQueuePair();
709
+ const outRxQ2 = outRx2[Symbol.asyncIterator]();
750
710
 
751
- node2.syncManager.addPeer({
752
- id: "test1",
753
- incoming: inRx2,
754
- outgoing: outTx2,
755
- role: "client",
756
- });
711
+ node2.syncManager.addPeer({
712
+ id: "test1",
713
+ incoming: inRx2,
714
+ outgoing: outTx2,
715
+ role: "client",
716
+ });
757
717
 
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
- });
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
- yield* Queue.offer(inTx2, adminSubscribeMessage);
770
- yield* Queue.offer(inTx2, groupSubscribeMsg);
729
+ await inTx2.push(adminSubscribeMessage);
730
+ await inTx2.push(groupSubscribeMsg);
771
731
 
772
- // const adminTellKnownStateMsg = yield* Queue.take(outRxQ2);
773
- // expect(adminTellKnownStateMsg).toMatchObject(admStateEx(admin.id));
732
+ // const adminTellKnownStateMsg = (await outRxQ2.next()).value;
733
+ // expect(adminTellKnownStateMsg).toMatchObject(admStateEx(admin.id));
774
734
 
775
- const groupTellKnownStateMsg = yield* Queue.take(outRxQ2);
776
- expect(groupTellKnownStateMsg).toMatchObject(groupStateEx(group));
735
+ const groupTellKnownStateMsg = (await outRxQ2.next()).value;
736
+ expect(groupTellKnownStateMsg).toMatchObject(groupStateEx(group));
777
737
 
778
- expect(
779
- node2.syncManager.peers["test1"]!.optimisticKnownStates[
780
- group.core.id
781
- ],
782
- ).toBeDefined();
738
+ expect(
739
+ node2.syncManager.peers["test1"]!.optimisticKnownStates[group.core.id],
740
+ ).toBeDefined();
783
741
 
784
- // yield* Queue.offer(inTx1, adminTellKnownStateMsg);
785
- yield* Queue.offer(inTx1, groupTellKnownStateMsg);
742
+ // await inTx1.push(adminTellKnownStateMsg);
743
+ await inTx1.push(groupTellKnownStateMsg);
786
744
 
787
- // const adminContentMsg = yield* Queue.take(outRxQ1);
788
- // expect(adminContentMsg).toMatchObject(admContEx(admin.id));
745
+ // const adminContentMsg = (await outRxQ1.next()).value;
746
+ // expect(adminContentMsg).toMatchObject(admContEx(admin.id));
789
747
 
790
- const groupContentMsg = yield* Queue.take(outRxQ1);
791
- expect(groupContentMsg).toMatchObject(groupContentEx(group));
748
+ const groupContentMsg = (await outRxQ1.next()).value;
749
+ expect(groupContentMsg).toMatchObject(groupContentEx(group));
792
750
 
793
- // yield* Queue.offer(inTx2, adminContentMsg);
794
- yield* Queue.offer(inTx2, groupContentMsg);
751
+ // await inTx2.push(adminContentMsg);
752
+ await inTx2.push(groupContentMsg);
795
753
 
796
- const map = group.createMap();
754
+ const map = group.createMap();
797
755
 
798
- const mapSubscriptionMsg = yield* Queue.take(outRxQ1);
799
- expect(mapSubscriptionMsg).toMatchObject({
800
- action: "load",
801
- id: map.core.id,
802
- });
756
+ const mapSubscriptionMsg = (await outRxQ1.next()).value;
757
+ expect(mapSubscriptionMsg).toMatchObject({
758
+ action: "load",
759
+ id: map.core.id,
760
+ });
803
761
 
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);
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
- yield* Queue.offer(inTx2, mapSubscriptionMsg);
770
+ await inTx2.push(mapSubscriptionMsg);
813
771
 
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);
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
- expect(node2.coValues[map.core.id]?.state).toEqual("loading");
780
+ expect(node2.coValues[map.core.id]?.state).toEqual("loading");
823
781
 
824
- yield* Queue.offer(inTx2, mapNewContentMsg);
782
+ await inTx2.push(mapNewContentMsg);
825
783
 
826
- map.set("hello", "world", "trusting");
784
+ map.set("hello", "world", "trusting");
827
785
 
828
- const mapEditMsg = yield* Queue.take(outRxQ1);
786
+ const mapEditMsg = (await outRxQ1.next()).value;
829
787
 
830
- yield* Queue.offer(inTx2, mapEditMsg);
788
+ await inTx2.push(mapEditMsg);
831
789
 
832
- yield* Effect.sleep(100);
790
+ await new Promise((resolve) => setTimeout(resolve, 100));
833
791
 
834
- expect(
835
- expectMap(
836
- node2.expectCoValueLoaded(map.core.id).getCurrentContent(),
837
- ).get("hello"),
838
- ).toEqual("world");
839
- }).pipe(Effect.scoped, Effect.runPromise));
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
- Effect.gen(function* () {
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
- const server = new LocalNode(serverUser, serverSession, Crypto);
831
+ const client1 = new LocalNode(admin, session, Crypto);
945
832
 
946
- const [serverAsPeer, client1AsPeer] = yield* connectedPeers(
947
- "server",
948
- "client1",
949
- {
950
- trace: true,
951
- peer1role: "server",
952
- peer2role: "client",
953
- },
954
- );
833
+ const group = client1.createGroup();
955
834
 
956
- client1.syncManager.addPeer(serverAsPeer);
957
- server.syncManager.addPeer(client1AsPeer);
835
+ const map = group.createMap();
836
+ map.set("hello", "world", "trusting");
958
837
 
959
- const client2 = new LocalNode(
960
- admin,
961
- newRandomSessionID(admin.id),
962
- Crypto,
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
- const [serverAsOtherPeer, client2AsPeer] = yield* connectedPeers(
966
- "server",
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
- client2.syncManager.addPeer(serverAsOtherPeer);
976
- server.syncManager.addPeer(client2AsPeer);
883
+ const client1 = new LocalNode(admin, session, Crypto);
977
884
 
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
- }
885
+ const group = client1.createGroup();
984
886
 
985
- expect(
986
- expectMap(mapOnClient2.getCurrentContent()).get("hello"),
987
- ).toEqual("world");
988
- }).pipe(Effect.scoped, Effect.runPromise));
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
- Effect.gen(function* () {
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
- const node1 = new LocalNode(admin, session, Crypto);
1048
+ const node1 = new LocalNode(admin, session, Crypto);
1108
1049
 
1109
- const group = node1.createGroup();
1050
+ const group = node1.createGroup();
1110
1051
 
1111
- const map = group.createMap();
1112
- map.set("hello", "world", "trusting");
1052
+ const map = group.createMap();
1053
+ map.set("hello", "world", "trusting");
1113
1054
 
1114
- const node2 = new LocalNode(
1115
- admin,
1116
- newRandomSessionID(admin.id),
1117
- Crypto,
1118
- );
1055
+ const node2 = new LocalNode(admin, newRandomSessionID(admin.id), Crypto);
1119
1056
 
1120
- const [node1asPeer, node2asPeer] = yield* connectedPeers(
1121
- "peer1",
1122
- "peer2",
1123
- {
1124
- peer1role: "server",
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
- node1.syncManager.addPeer(node2asPeer);
1063
+ node1.syncManager.addPeer(node2asPeer);
1131
1064
 
1132
- const mapOnNode2Promise = node2.loadCoValueCore(map.core.id);
1065
+ const mapOnNode2Promise = node2.loadCoValueCore(map.core.id);
1133
1066
 
1134
- expect(node2.coValues[map.core.id]?.state).toEqual("loading");
1067
+ expect(node2.coValues[map.core.id]?.state).toEqual("loading");
1135
1068
 
1136
- node2.syncManager.addPeer(node1asPeer);
1069
+ node2.syncManager.addPeer(node1asPeer);
1137
1070
 
1138
- const mapOnNode2 = yield* Effect.promise(() => mapOnNode2Promise);
1139
- if (mapOnNode2 === "unavailable") {
1140
- throw new Error("Map is unavailable");
1141
- }
1071
+ const mapOnNode2 = await mapOnNode2Promise;
1072
+ if (mapOnNode2 === "unavailable") {
1073
+ throw new Error("Map is unavailable");
1074
+ }
1142
1075
 
1143
- expect(expectMap(mapOnNode2.getCurrentContent()).get("hello")).toEqual(
1144
- "world",
1145
- );
1146
- }).pipe(Effect.scoped, Effect.runPromise));
1076
+ expect(expectMap(mapOnNode2.getCurrentContent()).get("hello")).toEqual(
1077
+ "world",
1078
+ );
1079
+ });
1147
1080
 
1148
1081
  function groupContentEx(group: RawGroup) {
1149
1082
  return {