cojson 0.0.11 → 0.0.12

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.
Files changed (75) hide show
  1. package/README.md +2 -2
  2. package/dist/account.d.ts +57 -0
  3. package/dist/account.js +76 -0
  4. package/dist/account.js.map +1 -0
  5. package/dist/account.test.d.ts +1 -0
  6. package/dist/account.test.js +40 -0
  7. package/dist/account.test.js.map +1 -0
  8. package/dist/coValue.d.ts +16 -35
  9. package/dist/coValue.js +49 -112
  10. package/dist/coValue.js.map +1 -1
  11. package/dist/coValue.test.js +16 -16
  12. package/dist/coValue.test.js.map +1 -1
  13. package/dist/contentType.d.ts +9 -9
  14. package/dist/contentType.js.map +1 -1
  15. package/dist/contentType.test.js +13 -17
  16. package/dist/contentType.test.js.map +1 -1
  17. package/dist/contentTypes/coList.d.ts +3 -3
  18. package/dist/contentTypes/coList.js.map +1 -1
  19. package/dist/contentTypes/coMap.d.ts +31 -21
  20. package/dist/contentTypes/coMap.js +28 -0
  21. package/dist/contentTypes/coMap.js.map +1 -1
  22. package/dist/contentTypes/coStream.d.ts +3 -3
  23. package/dist/contentTypes/coStream.js.map +1 -1
  24. package/dist/contentTypes/static.d.ts +4 -4
  25. package/dist/contentTypes/static.js.map +1 -1
  26. package/dist/crypto.d.ts +45 -39
  27. package/dist/crypto.js +68 -49
  28. package/dist/crypto.js.map +1 -1
  29. package/dist/crypto.test.js +45 -49
  30. package/dist/crypto.test.js.map +1 -1
  31. package/dist/ids.d.ts +5 -3
  32. package/dist/ids.js +3 -1
  33. package/dist/ids.js.map +1 -1
  34. package/dist/index.d.ts +12 -14
  35. package/dist/index.js +6 -8
  36. package/dist/index.js.map +1 -1
  37. package/dist/jsonValue.d.ts +2 -2
  38. package/dist/node.d.ts +25 -15
  39. package/dist/node.js +88 -33
  40. package/dist/node.js.map +1 -1
  41. package/dist/permissions.d.ts +27 -33
  42. package/dist/permissions.js +55 -47
  43. package/dist/permissions.js.map +1 -1
  44. package/dist/permissions.test.js +231 -314
  45. package/dist/permissions.test.js.map +1 -1
  46. package/dist/sync.d.ts +26 -28
  47. package/dist/sync.js +67 -63
  48. package/dist/sync.js.map +1 -1
  49. package/dist/sync.test.js +181 -298
  50. package/dist/sync.test.js.map +1 -1
  51. package/dist/testUtils.d.ts +37 -0
  52. package/dist/testUtils.js +157 -0
  53. package/dist/testUtils.js.map +1 -0
  54. package/package.json +1 -1
  55. package/src/account.test.ts +67 -0
  56. package/src/account.ts +152 -0
  57. package/src/coValue.test.ts +17 -31
  58. package/src/coValue.ts +93 -179
  59. package/src/contentType.test.ts +18 -45
  60. package/src/contentType.ts +15 -13
  61. package/src/contentTypes/coList.ts +4 -4
  62. package/src/contentTypes/coMap.ts +55 -29
  63. package/src/contentTypes/coStream.ts +4 -4
  64. package/src/contentTypes/static.ts +5 -5
  65. package/src/crypto.test.ts +53 -59
  66. package/src/crypto.ts +123 -95
  67. package/src/ids.ts +9 -3
  68. package/src/index.ts +14 -25
  69. package/src/jsonValue.ts +2 -2
  70. package/src/node.ts +189 -61
  71. package/src/permissions.test.ts +370 -404
  72. package/src/permissions.ts +126 -109
  73. package/src/sync.test.ts +258 -432
  74. package/src/sync.ts +95 -98
  75. package/src/testUtils.ts +229 -0
package/src/sync.ts CHANGED
@@ -8,43 +8,43 @@ import {
8
8
  WritableStream,
9
9
  WritableStreamDefaultWriter,
10
10
  } from "isomorphic-streams";
11
- import { RawCoValueID, SessionID } from "./ids.js";
11
+ import { RawCoID, SessionID } from "./ids.js";
12
12
 
13
13
  export type CoValueKnownState = {
14
- coValueID: RawCoValueID;
14
+ id: RawCoID;
15
15
  header: boolean;
16
16
  sessions: { [sessionID: SessionID]: number };
17
17
  };
18
18
 
19
- export function emptyKnownState(coValueID: RawCoValueID): CoValueKnownState {
19
+ export function emptyKnownState(id: RawCoID): CoValueKnownState {
20
20
  return {
21
- coValueID,
21
+ id,
22
22
  header: false,
23
23
  sessions: {},
24
24
  };
25
25
  }
26
26
 
27
27
  export type SyncMessage =
28
- | SubscribeMessage
29
- | TellKnownStateMessage
28
+ | LoadMessage
29
+ | KnownStateMessage
30
30
  | NewContentMessage
31
- | WrongAssumedKnownStateMessage
32
- | UnsubscribeMessage;
31
+ | DoneMessage;
33
32
 
34
- export type SubscribeMessage = {
35
- action: "subscribe";
33
+ export type LoadMessage = {
34
+ action: "load";
36
35
  } & CoValueKnownState;
37
36
 
38
- export type TellKnownStateMessage = {
39
- action: "tellKnownState";
40
- asDependencyOf?: RawCoValueID;
37
+ export type KnownStateMessage = {
38
+ action: "known";
39
+ asDependencyOf?: RawCoID;
40
+ isCorrection?: boolean;
41
41
  } & CoValueKnownState;
42
42
 
43
43
  export type NewContentMessage = {
44
- action: "newContent";
45
- coValueID: RawCoValueID;
44
+ action: "content";
45
+ id: RawCoID;
46
46
  header?: CoValueHeader;
47
- newContent: {
47
+ new: {
48
48
  [sessionID: SessionID]: SessionNewContent;
49
49
  };
50
50
  };
@@ -56,14 +56,9 @@ export type SessionNewContent = {
56
56
  lastHash: Hash;
57
57
  lastSignature: Signature;
58
58
  };
59
-
60
- export type WrongAssumedKnownStateMessage = {
61
- action: "wrongAssumedKnownState";
62
- } & CoValueKnownState;
63
-
64
- export type UnsubscribeMessage = {
65
- action: "unsubscribe";
66
- coValueID: RawCoValueID;
59
+ export type DoneMessage = {
60
+ action: "done";
61
+ id: RawCoID;
67
62
  };
68
63
 
69
64
  export type PeerID = string;
@@ -77,8 +72,8 @@ export interface Peer {
77
72
 
78
73
  export interface PeerState {
79
74
  id: PeerID;
80
- optimisticKnownStates: { [coValueID: RawCoValueID]: CoValueKnownState };
81
- toldKnownState: Set<RawCoValueID>;
75
+ optimisticKnownStates: { [id: RawCoID]: CoValueKnownState };
76
+ toldKnownState: Set<RawCoID>;
82
77
  incoming: ReadableStream<SyncMessage>;
83
78
  outgoing: WritableStreamDefaultWriter<SyncMessage>;
84
79
  role: "peer" | "server" | "client";
@@ -103,7 +98,7 @@ export function combinedKnownStates(
103
98
  }
104
99
 
105
100
  return {
106
- coValueID: stateA.coValueID,
101
+ id: stateA.id,
107
102
  header: stateA.header || stateB.header,
108
103
  sessions: sessionStates,
109
104
  };
@@ -117,12 +112,12 @@ export class SyncManager {
117
112
  this.local = local;
118
113
  }
119
114
 
120
- loadFromPeers(id: RawCoValueID) {
115
+ loadFromPeers(id: RawCoID) {
121
116
  for (const peer of Object.values(this.peers)) {
122
117
  peer.outgoing
123
118
  .write({
124
- action: "subscribe",
125
- coValueID: id,
119
+ action: "load",
120
+ id: id,
126
121
  header: false,
127
122
  sessions: {},
128
123
  })
@@ -135,15 +130,17 @@ export class SyncManager {
135
130
  async handleSyncMessage(msg: SyncMessage, peer: PeerState) {
136
131
  // TODO: validate
137
132
  switch (msg.action) {
138
- case "subscribe":
139
- return await this.handleSubscribe(msg, peer);
140
- case "tellKnownState":
141
- return await this.handleTellKnownState(msg, peer);
142
- case "newContent":
133
+ case "load":
134
+ return await this.handleLoad(msg, peer);
135
+ case "known":
136
+ if (msg.isCorrection) {
137
+ return await this.handleCorrection(msg, peer);
138
+ } else {
139
+ return await this.handleKnownState(msg, peer);
140
+ }
141
+ case "content":
143
142
  return await this.handleNewContent(msg, peer);
144
- case "wrongAssumedKnownState":
145
- return await this.handleWrongAssumedKnownState(msg, peer);
146
- case "unsubscribe":
143
+ case "done":
147
144
  return await this.handleUnsubscribe(msg);
148
145
  default:
149
146
  throw new Error(
@@ -155,10 +152,10 @@ export class SyncManager {
155
152
  }
156
153
 
157
154
  async subscribeToIncludingDependencies(
158
- coValueID: RawCoValueID,
155
+ id: RawCoID,
159
156
  peer: PeerState
160
157
  ) {
161
- const entry = this.local.coValues[coValueID];
158
+ const entry = this.local.coValues[id];
162
159
 
163
160
  if (!entry) {
164
161
  throw new Error(
@@ -168,8 +165,8 @@ export class SyncManager {
168
165
 
169
166
  if (entry.state === "loading") {
170
167
  await this.trySendToPeer(peer, {
171
- action: "subscribe",
172
- coValueID,
168
+ action: "load",
169
+ id,
173
170
  header: false,
174
171
  sessions: {},
175
172
  });
@@ -178,64 +175,64 @@ export class SyncManager {
178
175
 
179
176
  const coValue = entry.coValue;
180
177
 
181
- for (const coValueID of coValue.getDependedOnCoValues()) {
182
- await this.subscribeToIncludingDependencies(coValueID, peer);
178
+ for (const id of coValue.getDependedOnCoValues()) {
179
+ await this.subscribeToIncludingDependencies(id, peer);
183
180
  }
184
181
 
185
- if (!peer.toldKnownState.has(coValueID)) {
186
- peer.toldKnownState.add(coValueID);
182
+ if (!peer.toldKnownState.has(id)) {
183
+ peer.toldKnownState.add(id);
187
184
  await this.trySendToPeer(peer, {
188
- action: "subscribe",
185
+ action: "load",
189
186
  ...coValue.knownState(),
190
187
  });
191
188
  }
192
189
  }
193
190
 
194
191
  async tellUntoldKnownStateIncludingDependencies(
195
- coValueID: RawCoValueID,
192
+ id: RawCoID,
196
193
  peer: PeerState,
197
- asDependencyOf?: RawCoValueID
194
+ asDependencyOf?: RawCoID
198
195
  ) {
199
- const coValue = this.local.expectCoValueLoaded(coValueID);
196
+ const coValue = this.local.expectCoValueLoaded(id);
200
197
 
201
- for (const dependentCoValueID of coValue.getDependedOnCoValues()) {
198
+ for (const dependentCoID of coValue.getDependedOnCoValues()) {
202
199
  await this.tellUntoldKnownStateIncludingDependencies(
203
- dependentCoValueID,
200
+ dependentCoID,
204
201
  peer,
205
- asDependencyOf || coValueID
202
+ asDependencyOf || id
206
203
  );
207
204
  }
208
205
 
209
- if (!peer.toldKnownState.has(coValueID)) {
206
+ if (!peer.toldKnownState.has(id)) {
210
207
  await this.trySendToPeer(peer, {
211
- action: "tellKnownState",
208
+ action: "known",
212
209
  asDependencyOf,
213
210
  ...coValue.knownState(),
214
211
  });
215
212
 
216
- peer.toldKnownState.add(coValueID);
213
+ peer.toldKnownState.add(id);
217
214
  }
218
215
  }
219
216
 
220
217
  async sendNewContentIncludingDependencies(
221
- coValueID: RawCoValueID,
218
+ id: RawCoID,
222
219
  peer: PeerState
223
220
  ) {
224
- const coValue = this.local.expectCoValueLoaded(coValueID);
221
+ const coValue = this.local.expectCoValueLoaded(id);
225
222
 
226
- for (const coValueID of coValue.getDependedOnCoValues()) {
227
- await this.sendNewContentIncludingDependencies(coValueID, peer);
223
+ for (const id of coValue.getDependedOnCoValues()) {
224
+ await this.sendNewContentIncludingDependencies(id, peer);
228
225
  }
229
226
 
230
227
  const newContent = coValue.newContentSince(
231
- peer.optimisticKnownStates[coValueID]
228
+ peer.optimisticKnownStates[id]
232
229
  );
233
230
 
234
231
  if (newContent) {
235
232
  await this.trySendToPeer(peer, newContent);
236
- peer.optimisticKnownStates[coValueID] = combinedKnownStates(
237
- peer.optimisticKnownStates[coValueID] ||
238
- emptyKnownState(coValueID),
233
+ peer.optimisticKnownStates[id] = combinedKnownStates(
234
+ peer.optimisticKnownStates[id] ||
235
+ emptyKnownState(id),
239
236
  coValue.knownState()
240
237
  );
241
238
  }
@@ -256,11 +253,11 @@ export class SyncManager {
256
253
  const initialSync = async () => {
257
254
  for (const id of Object.keys(
258
255
  this.local.coValues
259
- ) as RawCoValueID[]) {
256
+ ) as RawCoID[]) {
260
257
  await this.subscribeToIncludingDependencies(id, peerState);
261
258
 
262
259
  peerState.optimisticKnownStates[id] = {
263
- coValueID: id,
260
+ id: id,
264
261
  header: false,
265
262
  sessions: {},
266
263
  };
@@ -295,20 +292,20 @@ export class SyncManager {
295
292
  });
296
293
  }
297
294
 
298
- async handleSubscribe(msg: SubscribeMessage, peer: PeerState) {
299
- const entry = this.local.coValues[msg.coValueID];
295
+ async handleLoad(msg: LoadMessage, peer: PeerState) {
296
+ const entry = this.local.coValues[msg.id];
300
297
 
301
298
  if (!entry || entry.state === "loading") {
302
299
  if (!entry) {
303
- this.local.coValues[msg.coValueID] = newLoadingState();
300
+ this.local.coValues[msg.id] = newLoadingState();
304
301
  }
305
302
 
306
- peer.optimisticKnownStates[msg.coValueID] = knownStateIn(msg);
307
- peer.toldKnownState.add(msg.coValueID);
303
+ peer.optimisticKnownStates[msg.id] = knownStateIn(msg);
304
+ peer.toldKnownState.add(msg.id);
308
305
 
309
306
  await this.trySendToPeer(peer, {
310
- action: "tellKnownState",
311
- coValueID: msg.coValueID,
307
+ action: "known",
308
+ id: msg.id,
312
309
  header: false,
313
310
  sessions: {},
314
311
  });
@@ -316,22 +313,22 @@ export class SyncManager {
316
313
  return;
317
314
  }
318
315
 
319
- peer.optimisticKnownStates[msg.coValueID] = knownStateIn(msg);
316
+ peer.optimisticKnownStates[msg.id] = knownStateIn(msg);
320
317
 
321
318
  await this.tellUntoldKnownStateIncludingDependencies(
322
- msg.coValueID,
319
+ msg.id,
323
320
  peer
324
321
  );
325
322
 
326
- await this.sendNewContentIncludingDependencies(msg.coValueID, peer);
323
+ await this.sendNewContentIncludingDependencies(msg.id, peer);
327
324
  }
328
325
 
329
- async handleTellKnownState(msg: TellKnownStateMessage, peer: PeerState) {
330
- let entry = this.local.coValues[msg.coValueID];
326
+ async handleKnownState(msg: KnownStateMessage, peer: PeerState) {
327
+ let entry = this.local.coValues[msg.id];
331
328
 
332
- peer.optimisticKnownStates[msg.coValueID] = combinedKnownStates(
333
- peer.optimisticKnownStates[msg.coValueID] ||
334
- emptyKnownState(msg.coValueID),
329
+ peer.optimisticKnownStates[msg.id] = combinedKnownStates(
330
+ peer.optimisticKnownStates[msg.id] ||
331
+ emptyKnownState(msg.id),
335
332
  knownStateIn(msg)
336
333
  );
337
334
 
@@ -340,7 +337,7 @@ export class SyncManager {
340
337
  if (this.local.coValues[msg.asDependencyOf]) {
341
338
  entry = newLoadingState();
342
339
 
343
- this.local.coValues[msg.coValueID] = entry;
340
+ this.local.coValues[msg.id] = entry;
344
341
  } else {
345
342
  throw new Error(
346
343
  "Expected coValue dependency entry to be created, missing subscribe?"
@@ -358,14 +355,14 @@ export class SyncManager {
358
355
  }
359
356
 
360
357
  await this.tellUntoldKnownStateIncludingDependencies(
361
- msg.coValueID,
358
+ msg.id,
362
359
  peer
363
360
  );
364
- await this.sendNewContentIncludingDependencies(msg.coValueID, peer);
361
+ await this.sendNewContentIncludingDependencies(msg.id, peer);
365
362
  }
366
363
 
367
364
  async handleNewContent(msg: NewContentMessage, peer: PeerState) {
368
- let entry = this.local.coValues[msg.coValueID];
365
+ let entry = this.local.coValues[msg.id];
369
366
 
370
367
  if (!entry) {
371
368
  throw new Error(
@@ -376,7 +373,7 @@ export class SyncManager {
376
373
  let resolveAfterDone: ((coValue: CoValue) => void) | undefined;
377
374
 
378
375
  const peerOptimisticKnownState =
379
- peer.optimisticKnownStates[msg.coValueID];
376
+ peer.optimisticKnownStates[msg.id];
380
377
 
381
378
  if (!peerOptimisticKnownState) {
382
379
  throw new Error(
@@ -400,7 +397,7 @@ export class SyncManager {
400
397
  coValue: coValue,
401
398
  };
402
399
 
403
- this.local.coValues[msg.coValueID] = entry;
400
+ this.local.coValues[msg.id] = entry;
404
401
  }
405
402
 
406
403
  const coValue = entry.coValue;
@@ -408,7 +405,7 @@ export class SyncManager {
408
405
  let invalidStateAssumed = false;
409
406
 
410
407
  for (const [sessionID, newContentForSession] of Object.entries(
411
- msg.newContent
408
+ msg.new
412
409
  ) as [SessionID, SessionNewContent][]) {
413
410
  const ourKnownTxIdx =
414
411
  coValue.sessions[sessionID]?.transactions.length;
@@ -451,19 +448,20 @@ export class SyncManager {
451
448
 
452
449
  if (invalidStateAssumed) {
453
450
  await this.trySendToPeer(peer, {
454
- action: "wrongAssumedKnownState",
451
+ action: "known",
452
+ isCorrection: true,
455
453
  ...coValue.knownState(),
456
454
  });
457
455
  }
458
456
  }
459
457
 
460
- async handleWrongAssumedKnownState(
461
- msg: WrongAssumedKnownStateMessage,
458
+ async handleCorrection(
459
+ msg: KnownStateMessage,
462
460
  peer: PeerState
463
461
  ) {
464
- const coValue = this.local.expectCoValueLoaded(msg.coValueID);
462
+ const coValue = this.local.expectCoValueLoaded(msg.id);
465
463
 
466
- peer.optimisticKnownStates[msg.coValueID] = combinedKnownStates(
464
+ peer.optimisticKnownStates[msg.id] = combinedKnownStates(
467
465
  msg,
468
466
  coValue.knownState()
469
467
  );
@@ -475,7 +473,7 @@ export class SyncManager {
475
473
  }
476
474
  }
477
475
 
478
- handleUnsubscribe(_msg: UnsubscribeMessage) {
476
+ handleUnsubscribe(_msg: DoneMessage) {
479
477
  throw new Error("Method not implemented.");
480
478
  }
481
479
 
@@ -505,12 +503,11 @@ export class SyncManager {
505
503
 
506
504
  function knownStateIn(
507
505
  msg:
508
- | SubscribeMessage
509
- | TellKnownStateMessage
510
- | WrongAssumedKnownStateMessage
506
+ | LoadMessage
507
+ | KnownStateMessage
511
508
  ) {
512
509
  return {
513
- coValueID: msg.coValueID,
510
+ id: msg.id,
514
511
  header: msg.header,
515
512
  sessions: msg.sessions,
516
513
  };
@@ -0,0 +1,229 @@
1
+ import { AgentSecret, createdNowUnique, getAgentID, newRandomAgentSecret } from "./crypto.js";
2
+ import { newRandomSessionID } from "./coValue.js";
3
+ import { LocalNode } from "./node.js";
4
+ import { expectTeamContent } from "./permissions.js";
5
+ import { AnonymousControlledAccount } from "./account.js";
6
+ import { SessionID } from "./ids.js";
7
+ import { ReadableStream, TransformStream, WritableStream } from "isomorphic-streams";
8
+ import { Peer, PeerID, SyncMessage } from "./sync.js";
9
+
10
+ export function randomAnonymousAccountAndSessionID(): [AnonymousControlledAccount, SessionID] {
11
+ const agentSecret = newRandomAgentSecret();
12
+
13
+ const sessionID = newRandomSessionID(getAgentID(agentSecret));
14
+
15
+ return [new AnonymousControlledAccount(agentSecret), sessionID];
16
+ }
17
+
18
+ export function newTeam() {
19
+ const [admin, sessionID] = randomAnonymousAccountAndSessionID();
20
+
21
+ const node = new LocalNode(admin, sessionID);
22
+
23
+ const team = node.createCoValue({
24
+ type: "comap",
25
+ ruleset: { type: "team", initialAdmin: admin.id },
26
+ meta: null,
27
+ ...createdNowUnique(),
28
+ });
29
+
30
+ const teamContent = expectTeamContent(team.getCurrentContent());
31
+
32
+ teamContent.edit((editable) => {
33
+ editable.set(admin.id, "admin", "trusting");
34
+ expect(editable.get(admin.id)).toEqual("admin");
35
+ });
36
+
37
+ return { node, team, admin };
38
+ }
39
+
40
+ export function teamWithTwoAdmins() {
41
+ const { team, admin, node } = newTeam();
42
+
43
+ const otherAdmin = node.createAccount("otherAdmin");
44
+
45
+ let content = expectTeamContent(team.getCurrentContent());
46
+
47
+ content.edit((editable) => {
48
+ editable.set(otherAdmin.id, "admin", "trusting");
49
+ expect(editable.get(otherAdmin.id)).toEqual("admin");
50
+ });
51
+
52
+ content = expectTeamContent(team.getCurrentContent());
53
+
54
+ if (content.type !== "comap") {
55
+ throw new Error("Expected map");
56
+ }
57
+
58
+ expect(content.get(otherAdmin.id)).toEqual("admin");
59
+ return { team, admin, otherAdmin, node };
60
+ }
61
+
62
+ export function newTeamHighLevel() {
63
+ const [admin, sessionID] = randomAnonymousAccountAndSessionID();
64
+
65
+
66
+ const node = new LocalNode(admin, sessionID);
67
+
68
+ const team = node.createTeam();
69
+
70
+ return { admin, node, team };
71
+ }
72
+
73
+ export function teamWithTwoAdminsHighLevel() {
74
+ const { admin, node, team } = newTeamHighLevel();
75
+
76
+ const otherAdmin = node.createAccount("otherAdmin");
77
+
78
+ team.addMember(otherAdmin.id, "admin");
79
+
80
+ return { admin, node, team, otherAdmin };
81
+ }
82
+
83
+ export function newStreamPair<T>(): [ReadableStream<T>, WritableStream<T>] {
84
+ const queue: T[] = [];
85
+ let resolveNextItemReady: () => void = () => {};
86
+ let nextItemReady: Promise<void> = new Promise((resolve) => {
87
+ resolveNextItemReady = resolve;
88
+ });
89
+
90
+ let writerClosed = false;
91
+ let readerClosed = false;
92
+
93
+ const readable = new ReadableStream<T>({
94
+ async pull(controller) {
95
+ let retriesLeft = 3;
96
+ while (retriesLeft > 0) {
97
+ if (writerClosed) {
98
+ controller.close();
99
+ return;
100
+ }
101
+ retriesLeft--;
102
+ if (queue.length > 0) {
103
+ controller.enqueue(queue.shift()!);
104
+ if (queue.length === 0) {
105
+ nextItemReady = new Promise((resolve) => {
106
+ resolveNextItemReady = resolve;
107
+ });
108
+ }
109
+ return;
110
+ } else {
111
+ await nextItemReady;
112
+ }
113
+ }
114
+ throw new Error(
115
+ "Should only use one retry to get next item in queue."
116
+ );
117
+ },
118
+
119
+ cancel(reason) {
120
+ console.log("Manually closing reader");
121
+ readerClosed = true;
122
+ },
123
+ });
124
+
125
+ const writable = new WritableStream<T>({
126
+ write(chunk, controller) {
127
+ if (readerClosed) {
128
+ console.log("Reader closed, not writing chunk", chunk);
129
+ throw new Error("Reader closed, not writing chunk");
130
+ }
131
+ queue.push(chunk);
132
+ if (queue.length === 1) {
133
+ // make sure that await write resolves before corresponding read
134
+ process.nextTick(() => resolveNextItemReady());
135
+ }
136
+ },
137
+ abort(reason) {
138
+ console.log("Manually closing writer");
139
+ writerClosed = true;
140
+ resolveNextItemReady();
141
+ return Promise.resolve();
142
+ },
143
+ });
144
+
145
+ return [readable, writable];
146
+ }
147
+
148
+ export function shouldNotResolve<T>(
149
+ promise: Promise<T>,
150
+ ops: { timeout: number }
151
+ ): Promise<void> {
152
+ return new Promise((resolve, reject) => {
153
+ promise
154
+ .then((v) =>
155
+ reject(
156
+ new Error(
157
+ "Should not have resolved, but resolved to " +
158
+ JSON.stringify(v)
159
+ )
160
+ )
161
+ )
162
+ .catch(reject);
163
+ setTimeout(resolve, ops.timeout);
164
+ });
165
+ }
166
+
167
+ export function connectedPeers(
168
+ peer1id: PeerID,
169
+ peer2id: PeerID,
170
+ {
171
+ trace = false,
172
+ peer1role = "peer",
173
+ peer2role = "peer",
174
+ }: {
175
+ trace?: boolean;
176
+ peer1role?: Peer["role"];
177
+ peer2role?: Peer["role"];
178
+ } = {}
179
+ ): [Peer, Peer] {
180
+ const [inRx1, inTx1] = newStreamPair<SyncMessage>();
181
+ const [outRx1, outTx1] = newStreamPair<SyncMessage>();
182
+
183
+ const [inRx2, inTx2] = newStreamPair<SyncMessage>();
184
+ const [outRx2, outTx2] = newStreamPair<SyncMessage>();
185
+
186
+ void outRx2
187
+ .pipeThrough(
188
+ new TransformStream({
189
+ transform(
190
+ chunk: SyncMessage,
191
+ controller: { enqueue: (msg: SyncMessage) => void }
192
+ ) {
193
+ trace && console.log(`${peer2id} -> ${peer1id}`, JSON.stringify(chunk, null, 2));
194
+ controller.enqueue(chunk);
195
+ },
196
+ })
197
+ )
198
+ .pipeTo(inTx1);
199
+
200
+ void outRx1
201
+ .pipeThrough(
202
+ new TransformStream({
203
+ transform(
204
+ chunk: SyncMessage,
205
+ controller: { enqueue: (msg: SyncMessage) => void }
206
+ ) {
207
+ trace && console.log(`${peer1id} -> ${peer2id}`, JSON.stringify(chunk, null, 2));
208
+ controller.enqueue(chunk);
209
+ },
210
+ })
211
+ )
212
+ .pipeTo(inTx2);
213
+
214
+ const peer2AsPeer: Peer = {
215
+ id: peer2id,
216
+ incoming: inRx1,
217
+ outgoing: outTx1,
218
+ role: peer2role,
219
+ };
220
+
221
+ const peer1AsPeer: Peer = {
222
+ id: peer1id,
223
+ incoming: inRx2,
224
+ outgoing: outTx2,
225
+ role: peer1role,
226
+ };
227
+
228
+ return [peer1AsPeer, peer2AsPeer];
229
+ }