@livestore/webmesh 0.3.0-dev.21 → 0.3.0-dev.23

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 (54) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/channel/message-channel-internal.d.ts +3 -3
  3. package/dist/channel/message-channel-internal.d.ts.map +1 -1
  4. package/dist/channel/message-channel-internal.js +8 -8
  5. package/dist/channel/message-channel-internal.js.map +1 -1
  6. package/dist/channel/message-channel.d.ts +5 -5
  7. package/dist/channel/message-channel.d.ts.map +1 -1
  8. package/dist/channel/message-channel.js +22 -22
  9. package/dist/channel/message-channel.js.map +1 -1
  10. package/dist/channel/proxy-channel.d.ts +2 -2
  11. package/dist/channel/proxy-channel.d.ts.map +1 -1
  12. package/dist/channel/proxy-channel.js +9 -9
  13. package/dist/channel/proxy-channel.js.map +1 -1
  14. package/dist/common.d.ts +7 -12
  15. package/dist/common.d.ts.map +1 -1
  16. package/dist/common.js +5 -3
  17. package/dist/common.js.map +1 -1
  18. package/dist/mesh-schema.d.ts +10 -10
  19. package/dist/mesh-schema.d.ts.map +1 -1
  20. package/dist/mesh-schema.js +7 -7
  21. package/dist/mesh-schema.js.map +1 -1
  22. package/dist/mod.d.ts +2 -2
  23. package/dist/mod.d.ts.map +1 -1
  24. package/dist/mod.js +2 -2
  25. package/dist/mod.js.map +1 -1
  26. package/dist/node.d.ts +14 -14
  27. package/dist/node.d.ts.map +1 -1
  28. package/dist/node.js +86 -86
  29. package/dist/node.js.map +1 -1
  30. package/dist/node.test.js +25 -25
  31. package/dist/node.test.js.map +1 -1
  32. package/dist/websocket-connection.d.ts +1 -1
  33. package/dist/websocket-connection.d.ts.map +1 -1
  34. package/dist/websocket-connection.js +5 -5
  35. package/dist/websocket-connection.js.map +1 -1
  36. package/dist/websocket-edge.d.ts +51 -0
  37. package/dist/websocket-edge.d.ts.map +1 -0
  38. package/dist/websocket-edge.js +69 -0
  39. package/dist/websocket-edge.js.map +1 -0
  40. package/dist/websocket-server.d.ts +2 -2
  41. package/dist/websocket-server.d.ts.map +1 -1
  42. package/dist/websocket-server.js +6 -6
  43. package/dist/websocket-server.js.map +1 -1
  44. package/package.json +3 -3
  45. package/src/channel/message-channel-internal.ts +10 -10
  46. package/src/channel/message-channel.ts +25 -25
  47. package/src/channel/proxy-channel.ts +11 -11
  48. package/src/common.ts +8 -11
  49. package/src/mesh-schema.ts +9 -9
  50. package/src/mod.ts +2 -2
  51. package/src/node.test.ts +25 -25
  52. package/src/node.ts +102 -113
  53. package/src/{websocket-connection.ts → websocket-edge.ts} +15 -14
  54. package/src/websocket-server.ts +9 -9
package/src/node.ts CHANGED
@@ -18,16 +18,16 @@ import {
18
18
  import { makeMessageChannel } from './channel/message-channel.js'
19
19
  import { makeProxyChannel } from './channel/proxy-channel.js'
20
20
  import type { ChannelKey, MeshNodeName, MessageQueueItem, ProxyQueueItem } from './common.js'
21
- import { ConnectionAlreadyExistsError, packetAsOtelAttributes } from './common.js'
21
+ import { EdgeAlreadyExistsError, packetAsOtelAttributes } from './common.js'
22
22
  import * as WebmeshSchema from './mesh-schema.js'
23
23
  import { TimeoutSet } from './utils.js'
24
24
 
25
- type ConnectionChannel = WebChannel.WebChannel<typeof WebmeshSchema.Packet.Type, typeof WebmeshSchema.Packet.Type>
25
+ type EdgeChannel = WebChannel.WebChannel<typeof WebmeshSchema.Packet.Type, typeof WebmeshSchema.Packet.Type>
26
26
 
27
27
  export interface MeshNode<TName extends MeshNodeName = MeshNodeName> {
28
28
  nodeName: TName
29
29
 
30
- connectionKeys: Effect.Effect<Set<MeshNodeName>>
30
+ edgeKeys: Effect.Effect<Set<MeshNodeName>>
31
31
 
32
32
  debug: {
33
33
  print: () => void
@@ -40,33 +40,33 @@ export interface MeshNode<TName extends MeshNodeName = MeshNodeName> {
40
40
  }
41
41
 
42
42
  /**
43
- * Manually adds a connection to get connected to the network of nodes with an existing WebChannel.
43
+ * Manually adds a edge to get connected to the network of nodes with an existing WebChannel.
44
44
  *
45
- * Assumptions about the WebChannel connection:
46
- * - 1:1 connection
45
+ * Assumptions about the WebChannel edge:
46
+ * - 1:1 edge
47
47
  * - Queues messages internally to never drop messages
48
48
  * - Automatically reconnects
49
49
  * - Ideally supports transferables
50
50
  */
51
- addConnection: {
51
+ addEdge: {
52
52
  (options: {
53
53
  target: MeshNodeName
54
- connectionChannel: ConnectionChannel
54
+ edgeChannel: EdgeChannel
55
55
  replaceIfExists: true
56
56
  }): Effect.Effect<void, never, Scope.Scope>
57
57
  (options: {
58
58
  target: MeshNodeName
59
- connectionChannel: ConnectionChannel
59
+ edgeChannel: EdgeChannel
60
60
  replaceIfExists?: boolean
61
- }): Effect.Effect<void, ConnectionAlreadyExistsError, Scope.Scope>
61
+ }): Effect.Effect<void, EdgeAlreadyExistsError, Scope.Scope>
62
62
  }
63
63
 
64
- removeConnection: (targetNodeName: MeshNodeName) => Effect.Effect<void, Cause.NoSuchElementException>
64
+ removeEdge: (targetNodeName: MeshNodeName) => Effect.Effect<void, Cause.NoSuchElementException>
65
65
 
66
66
  /**
67
- * Tries to broker a MessageChannel connection between the nodes, otherwise will proxy messages via hop-nodes
67
+ * Tries to broker a MessageChannel edge between the nodes, otherwise will proxy messages via hop-nodes
68
68
  *
69
- * For a channel to successfully open, both sides need to have a connection and call `makeChannel`.
69
+ * For a channel to successfully open, both sides need to have a edge and call `makeChannel`.
70
70
  *
71
71
  * Example:
72
72
  * ```ts
@@ -95,7 +95,7 @@ export interface MeshNode<TName extends MeshNodeName = MeshNodeName> {
95
95
  */
96
96
  mode: 'messagechannel' | 'proxy'
97
97
  /**
98
- * Amount of time before we consider a channel creation failed and retry when a new connection is available
98
+ * Amount of time before we consider a channel creation failed and retry when a new edge is available
99
99
  *
100
100
  * @default 1 second
101
101
  */
@@ -116,17 +116,12 @@ export const makeMeshNode = <TName extends MeshNodeName>(
116
116
  nodeName: TName,
117
117
  ): Effect.Effect<MeshNode<TName>, never, Scope.Scope> =>
118
118
  Effect.gen(function* () {
119
- const connectionChannels = new Map<
120
- MeshNodeName,
121
- { channel: ConnectionChannel; listenFiber: Fiber.RuntimeFiber<void> }
122
- >()
119
+ const edgeChannels = new Map<MeshNodeName, { channel: EdgeChannel; listenFiber: Fiber.RuntimeFiber<void> }>()
123
120
 
124
121
  // To avoid unbounded memory growth, we automatically forget about packet ids after a while
125
122
  const handledPacketIds = new TimeoutSet<string>({ timeout: Duration.minutes(1) })
126
123
 
127
- const newConnectionAvailablePubSub = yield* PubSub.unbounded<MeshNodeName>().pipe(
128
- Effect.acquireRelease(PubSub.shutdown),
129
- )
124
+ const newEdgeAvailablePubSub = yield* PubSub.unbounded<MeshNodeName>().pipe(Effect.acquireRelease(PubSub.shutdown))
130
125
 
131
126
  // const proxyPacketsToProcess = yield* Queue.unbounded<ProxyQueueItem>().pipe(Effect.acquireRelease(Queue.shutdown))
132
127
  // const messagePacketsToProcess = yield* Queue.unbounded<MessageQueueItem>().pipe(
@@ -153,14 +148,14 @@ export const makeMeshNode = <TName extends MeshNodeName>(
153
148
  type BroadcastChannelName = string
154
149
  const broadcastChannelListenQueueMap = new Map<BroadcastChannelName, Queue.Queue<any>>()
155
150
 
156
- const checkTransferableConnections = (packet: typeof WebmeshSchema.MessageChannelPacket.Type) => {
151
+ const checkTransferableEdges = (packet: typeof WebmeshSchema.MessageChannelPacket.Type) => {
157
152
  if (
158
153
  (packet._tag === 'MessageChannelRequest' &&
159
- (connectionChannels.size === 0 ||
160
- // Either if direct connection does not support transferables ...
161
- connectionChannels.get(packet.target)?.channel.supportsTransferables === false)) ||
162
- // ... or if no forward-connections support transferables
163
- ![...connectionChannels.values()].some((c) => c.channel.supportsTransferables === true)
154
+ (edgeChannels.size === 0 ||
155
+ // Either if direct edge does not support transferables ...
156
+ edgeChannels.get(packet.target)?.channel.supportsTransferables === false)) ||
157
+ // ... or if no forward-edges support transferables
158
+ ![...edgeChannels.values()].some((c) => c.channel.supportsTransferables === true)
164
159
  ) {
165
160
  return WebmeshSchema.MessageChannelResponseNoTransferables.make({
166
161
  reqId: packet.id,
@@ -181,20 +176,20 @@ export const makeMeshNode = <TName extends MeshNodeName>(
181
176
  Effect.gen(function* () {
182
177
  // yield* Effect.log(`${nodeName}: sendPacket:${packet._tag} [${packet.id}]`)
183
178
 
184
- if (Schema.is(WebmeshSchema.NetworkConnectionAdded)(packet)) {
185
- yield* Effect.spanEvent('NetworkConnectionAdded', { packet, nodeName })
186
- yield* PubSub.publish(newConnectionAvailablePubSub, packet.target)
179
+ if (Schema.is(WebmeshSchema.NetworkEdgeAdded)(packet)) {
180
+ yield* Effect.spanEvent('NetworkEdgeAdded', { packet, nodeName })
181
+ yield* PubSub.publish(newEdgeAvailablePubSub, packet.target)
187
182
 
188
- const connectionsToForwardTo = Array.from(connectionChannels)
183
+ const edgesToForwardTo = Array.from(edgeChannels)
189
184
  .filter(([name]) => name !== packet.source)
190
185
  .map(([_, con]) => con.channel)
191
186
 
192
- yield* Effect.forEach(connectionsToForwardTo, (con) => con.send(packet), { concurrency: 'unbounded' })
187
+ yield* Effect.forEach(edgesToForwardTo, (con) => con.send(packet), { concurrency: 'unbounded' })
193
188
  return
194
189
  }
195
190
 
196
191
  if (Schema.is(WebmeshSchema.BroadcastChannelPacket)(packet)) {
197
- const connectionsToForwardTo = Array.from(connectionChannels)
192
+ const edgesToForwardTo = Array.from(edgeChannels)
198
193
  .filter(([name]) => !packet.hops.includes(name))
199
194
  .map(([_, con]) => con.channel)
200
195
 
@@ -203,7 +198,7 @@ export const makeMeshNode = <TName extends MeshNodeName>(
203
198
  hops: [...packet.hops, nodeName],
204
199
  }
205
200
 
206
- yield* Effect.forEach(connectionsToForwardTo, (con) => con.send(adjustedPacket), { concurrency: 'unbounded' })
201
+ yield* Effect.forEach(edgesToForwardTo, (con) => con.send(adjustedPacket), { concurrency: 'unbounded' })
207
202
 
208
203
  // Don't emit the packet to the own node listen queue
209
204
  if (packet.source === nodeName) {
@@ -219,27 +214,27 @@ export const makeMeshNode = <TName extends MeshNodeName>(
219
214
  return
220
215
  }
221
216
 
222
- if (Schema.is(WebmeshSchema.NetworkConnectionTopologyRequest)(packet)) {
217
+ if (Schema.is(WebmeshSchema.NetworkTopologyRequest)(packet)) {
223
218
  if (packet.source !== nodeName) {
224
- const backConnectionName =
219
+ const backEdgeName =
225
220
  packet.hops.at(-1) ?? shouldNeverHappen(`${nodeName}: Expected hops for packet`, packet)
226
- const backConnectionChannel = connectionChannels.get(backConnectionName)!.channel
221
+ const backEdgeChannel = edgeChannels.get(backEdgeName)!.channel
227
222
 
228
- // Respond with own connection info
229
- const response = WebmeshSchema.NetworkConnectionTopologyResponse.make({
223
+ // Respond with own edge info
224
+ const response = WebmeshSchema.NetworkTopologyResponse.make({
230
225
  reqId: packet.id,
231
226
  source: packet.source,
232
227
  target: packet.target,
233
228
  remainingHops: packet.hops.slice(0, -1),
234
229
  nodeName,
235
- connections: Array.from(connectionChannels.keys()),
230
+ edges: Array.from(edgeChannels.keys()),
236
231
  })
237
232
 
238
- yield* backConnectionChannel.send(response)
233
+ yield* backEdgeChannel.send(response)
239
234
  }
240
235
 
241
- // Forward the packet to all connections except the already visited ones
242
- const connectionsToForwardTo = Array.from(connectionChannels)
236
+ // Forward the packet to all edges except the already visited ones
237
+ const edgesToForwardTo = Array.from(edgeChannels)
243
238
  .filter(([name]) => !packet.hops.includes(name))
244
239
  .map(([_, con]) => con.channel)
245
240
 
@@ -248,72 +243,72 @@ export const makeMeshNode = <TName extends MeshNodeName>(
248
243
  hops: [...packet.hops, nodeName],
249
244
  }
250
245
 
251
- yield* Effect.forEach(connectionsToForwardTo, (con) => con.send(adjustedPacket), { concurrency: 'unbounded' })
246
+ yield* Effect.forEach(edgesToForwardTo, (con) => con.send(adjustedPacket), { concurrency: 'unbounded' })
252
247
 
253
248
  return
254
249
  }
255
250
 
256
- if (Schema.is(WebmeshSchema.NetworkConnectionTopologyResponse)(packet)) {
251
+ if (Schema.is(WebmeshSchema.NetworkTopologyResponse)(packet)) {
257
252
  if (packet.source === nodeName) {
258
253
  const topologyRequestItem = topologyRequestsMap.get(packet.reqId)!
259
- topologyRequestItem.set(packet.nodeName, new Set(packet.connections))
254
+ topologyRequestItem.set(packet.nodeName, new Set(packet.edges))
260
255
  } else {
261
256
  const remainingHops = packet.remainingHops
262
257
  // Forwarding the response to the original sender via the route back
263
258
  const routeBack =
264
259
  remainingHops.at(-1) ?? shouldNeverHappen(`${nodeName}: Expected remaining hops for packet`, packet)
265
- const connectionChannel =
266
- connectionChannels.get(routeBack)?.channel ??
260
+ const edgeChannel =
261
+ edgeChannels.get(routeBack)?.channel ??
267
262
  shouldNeverHappen(
268
- `${nodeName}: Expected connection channel (${routeBack}) for packet`,
263
+ `${nodeName}: Expected edge channel (${routeBack}) for packet`,
269
264
  packet,
270
- 'Available connections:',
271
- Array.from(connectionChannels.keys()),
265
+ 'Available edges:',
266
+ Array.from(edgeChannels.keys()),
272
267
  )
273
268
 
274
- yield* connectionChannel.send({ ...packet, remainingHops: packet.remainingHops.slice(0, -1) })
269
+ yield* edgeChannel.send({ ...packet, remainingHops: packet.remainingHops.slice(0, -1) })
275
270
  }
276
271
  return
277
272
  }
278
273
 
279
- // We have a direct connection to the target node
280
- if (connectionChannels.has(packet.target)) {
281
- const connectionChannel = connectionChannels.get(packet.target)!.channel
274
+ // We have a direct edge to the target node
275
+ if (edgeChannels.has(packet.target)) {
276
+ const edgeChannel = edgeChannels.get(packet.target)!.channel
282
277
  const hops = packet.source === nodeName ? [] : [...packet.hops, nodeName]
283
- yield* connectionChannel.send({ ...packet, hops })
278
+ yield* edgeChannel.send({ ...packet, hops })
284
279
  }
285
280
  // In this case we have an expected route back we should follow
286
281
  // eslint-disable-next-line unicorn/no-negated-condition
287
282
  else if (packet.remainingHops !== undefined) {
288
283
  const hopTarget =
289
284
  packet.remainingHops.at(-1) ?? shouldNeverHappen(`${nodeName}: Expected remaining hops for packet`, packet)
290
- const connectionChannel = connectionChannels.get(hopTarget)?.channel
285
+ const edgeChannel = edgeChannels.get(hopTarget)?.channel
291
286
 
292
- if (connectionChannel === undefined) {
287
+ if (edgeChannel === undefined) {
293
288
  yield* Effect.logWarning(
294
- `${nodeName}: Expected to find hop target ${hopTarget} in connections. Dropping packet.`,
289
+ `${nodeName}: Expected to find hop target ${hopTarget} in edges. Dropping packet.`,
295
290
  packet,
296
291
  )
297
292
  return
298
293
  }
299
294
 
300
- yield* connectionChannel.send({
295
+ yield* edgeChannel.send({
301
296
  ...packet,
302
297
  remainingHops: packet.remainingHops.slice(0, -1),
303
298
  hops: [...packet.hops, nodeName],
304
299
  })
305
300
  }
306
- // No route found, forward to all connections
301
+ // No route found, forward to all edges
307
302
  else {
308
303
  const hops = packet.source === nodeName ? [] : [...packet.hops, nodeName]
309
304
 
310
- // Optimization: filter out connection where packet just came from
311
- const connectionsToForwardTo = Array.from(connectionChannels)
305
+ // Optimization: filter out edge where packet just came from
306
+ const edgesToForwardTo = Array.from(edgeChannels)
312
307
  .filter(([name]) => name !== packet.source)
313
308
  .map(([_, con]) => con.channel)
314
309
 
315
310
  // TODO if hops-depth=0, we should fail right away with no route found
316
- if (hops.length === 0 && connectionsToForwardTo.length === 0 && LS_DEV) {
311
+ if (hops.length === 0 && edgesToForwardTo.length === 0 && LS_DEV) {
317
312
  console.log(nodeName, 'no route found', packet._tag, 'TODO handle better')
318
313
  // TODO return a expected failure
319
314
  }
@@ -321,7 +316,7 @@ export const makeMeshNode = <TName extends MeshNodeName>(
321
316
  const packetToSend = { ...packet, hops }
322
317
  // console.debug(nodeName, 'sendPacket:forwarding', packetToSend)
323
318
 
324
- yield* Effect.forEach(connectionsToForwardTo, (con) => con.send(packetToSend), { concurrency: 'unbounded' })
319
+ yield* Effect.forEach(edgesToForwardTo, (con) => con.send(packetToSend), { concurrency: 'unbounded' })
325
320
  }
326
321
  }).pipe(
327
322
  Effect.withSpan(`sendPacket:${packet._tag}:${packet.source}→${packet.target}`, {
@@ -330,24 +325,20 @@ export const makeMeshNode = <TName extends MeshNodeName>(
330
325
  Effect.orDie,
331
326
  )
332
327
 
333
- const addConnection: MeshNode['addConnection'] = ({
334
- target: targetNodeName,
335
- connectionChannel,
336
- replaceIfExists = false,
337
- }) =>
328
+ const addEdge: MeshNode['addEdge'] = ({ target: targetNodeName, edgeChannel, replaceIfExists = false }) =>
338
329
  Effect.gen(function* () {
339
- if (connectionChannels.has(targetNodeName)) {
330
+ if (edgeChannels.has(targetNodeName)) {
340
331
  if (replaceIfExists) {
341
- yield* removeConnection(targetNodeName).pipe(Effect.orDie)
332
+ yield* removeEdge(targetNodeName).pipe(Effect.orDie)
342
333
  // console.log('interrupting', targetNodeName)
343
- // yield* Fiber.interrupt(connectionChannels.get(targetNodeName)!.listenFiber)
334
+ // yield* Fiber.interrupt(edgeChannels.get(targetNodeName)!.listenFiber)
344
335
  } else {
345
- return yield* new ConnectionAlreadyExistsError({ target: targetNodeName })
336
+ return yield* new EdgeAlreadyExistsError({ target: targetNodeName })
346
337
  }
347
338
  }
348
339
 
349
- // TODO use a priority queue instead to prioritize network-changes/connection-requests over payloads
350
- const listenFiber = yield* connectionChannel.listen.pipe(
340
+ // TODO use a priority queue instead to prioritize network-changes/edge-requests over payloads
341
+ const listenFiber = yield* edgeChannel.listen.pipe(
351
342
  Stream.flatten(),
352
343
  Stream.tap((message) =>
353
344
  Effect.gen(function* () {
@@ -359,9 +350,9 @@ export const makeMeshNode = <TName extends MeshNodeName>(
359
350
  handledPacketIds.add(packet.id)
360
351
 
361
352
  switch (packet._tag) {
362
- case 'NetworkConnectionAdded':
363
- case 'NetworkConnectionTopologyRequest':
364
- case 'NetworkConnectionTopologyResponse': {
353
+ case 'NetworkEdgeAdded':
354
+ case 'NetworkTopologyRequest':
355
+ case 'NetworkTopologyResponse': {
365
356
  yield* sendPacket(packet)
366
357
 
367
358
  break
@@ -380,7 +371,7 @@ export const makeMeshNode = <TName extends MeshNodeName>(
380
371
  const queue = channelMap.get(channelKey)!.queue
381
372
 
382
373
  const respondToSender = (outgoingPacket: typeof WebmeshSchema.Packet.Type) =>
383
- connectionChannel
374
+ edgeChannel
384
375
  .send(outgoingPacket)
385
376
  .pipe(
386
377
  Effect.withSpan(
@@ -397,12 +388,10 @@ export const makeMeshNode = <TName extends MeshNodeName>(
397
388
  }
398
389
  } else {
399
390
  if (Schema.is(WebmeshSchema.MessageChannelPacket)(packet)) {
400
- const noTransferableResponse = checkTransferableConnections(packet)
391
+ const noTransferableResponse = checkTransferableEdges(packet)
401
392
  if (noTransferableResponse !== undefined) {
402
- yield* Effect.spanEvent(
403
- `No transferable connections found for ${packet.source}→${packet.target}`,
404
- )
405
- return yield* connectionChannel.send(noTransferableResponse).pipe(
393
+ yield* Effect.spanEvent(`No transferable edges found for ${packet.source}→${packet.target}`)
394
+ return yield* edgeChannel.send(noTransferableResponse).pipe(
406
395
  Effect.withSpan(`sendNoTransferableResponse:${packet.source}→${packet.target}`, {
407
396
  attributes: packetAsOtelAttributes(noTransferableResponse),
408
397
  }),
@@ -422,31 +411,31 @@ export const makeMeshNode = <TName extends MeshNodeName>(
422
411
  Effect.forkScoped,
423
412
  )
424
413
 
425
- connectionChannels.set(targetNodeName, { channel: connectionChannel, listenFiber })
414
+ edgeChannels.set(targetNodeName, { channel: edgeChannel, listenFiber })
426
415
 
427
- const connectionAddedPacket = WebmeshSchema.NetworkConnectionAdded.make({
416
+ const edgeAddedPacket = WebmeshSchema.NetworkEdgeAdded.make({
428
417
  source: nodeName,
429
418
  target: targetNodeName,
430
419
  })
431
- yield* sendPacket(connectionAddedPacket).pipe(Effect.orDie)
420
+ yield* sendPacket(edgeAddedPacket).pipe(Effect.orDie)
432
421
  }).pipe(
433
- Effect.withSpan(`addConnection:${nodeName}→${targetNodeName}`, {
434
- attributes: { supportsTransferables: connectionChannel.supportsTransferables },
422
+ Effect.withSpan(`addEdge:${nodeName}→${targetNodeName}`, {
423
+ attributes: { supportsTransferables: edgeChannel.supportsTransferables },
435
424
  }),
436
425
  ) as any // any-cast needed for error/never overload
437
426
 
438
- const removeConnection: MeshNode['removeConnection'] = (targetNodeName) =>
427
+ const removeEdge: MeshNode['removeEdge'] = (targetNodeName) =>
439
428
  Effect.gen(function* () {
440
- if (!connectionChannels.has(targetNodeName)) {
441
- yield* new Cause.NoSuchElementException(`No connection found for ${targetNodeName}`)
429
+ if (!edgeChannels.has(targetNodeName)) {
430
+ yield* new Cause.NoSuchElementException(`No edge found for ${targetNodeName}`)
442
431
  }
443
432
 
444
- yield* Fiber.interrupt(connectionChannels.get(targetNodeName)!.listenFiber)
433
+ yield* Fiber.interrupt(edgeChannels.get(targetNodeName)!.listenFiber)
445
434
 
446
- connectionChannels.delete(targetNodeName)
435
+ edgeChannels.delete(targetNodeName)
447
436
  })
448
437
 
449
- // TODO add heartbeat to detect dead connections (for both e2e and proxying)
438
+ // TODO add heartbeat to detect dead edges (for both e2e and proxying)
450
439
  // TODO when a channel is established in the same origin, we can use a weblock to detect disconnects
451
440
  const makeChannel: MeshNode['makeChannel'] = ({
452
441
  target,
@@ -491,26 +480,26 @@ export const makeMeshNode = <TName extends MeshNodeName>(
491
480
  )
492
481
 
493
482
  // NOTE already retries internally when transferables are required
494
- const { webChannel, initialConnectionDeferred } = yield* makeMessageChannel({
483
+ const { webChannel, initialEdgeDeferred } = yield* makeMessageChannel({
495
484
  nodeName,
496
485
  incomingPacketsQueue,
497
- newConnectionAvailablePubSub,
486
+ newEdgeAvailablePubSub,
498
487
  target,
499
488
  channelName,
500
489
  schema,
501
490
  sendPacket,
502
- checkTransferableConnections,
491
+ checkTransferableEdges,
503
492
  })
504
493
 
505
494
  channelMap.set(channelKey, { queue, debugInfo: { channel: webChannel, target } })
506
495
 
507
- yield* initialConnectionDeferred
496
+ yield* initialEdgeDeferred
508
497
 
509
498
  return webChannel
510
499
  } else {
511
500
  const channel = yield* makeProxyChannel({
512
501
  nodeName,
513
- newConnectionAvailablePubSub,
502
+ newEdgeAvailablePubSub,
514
503
  target,
515
504
  channelName,
516
505
  schema,
@@ -579,7 +568,7 @@ export const makeMeshNode = <TName extends MeshNodeName>(
579
568
  }),
580
569
  )
581
570
 
582
- const connectionKeys: MeshNode['connectionKeys'] = Effect.sync(() => new Set(connectionChannels.keys()))
571
+ const edgeKeys: MeshNode['edgeKeys'] = Effect.sync(() => new Set(edgeChannels.keys()))
583
572
 
584
573
  const runtime = yield* Effect.runtime()
585
574
 
@@ -587,8 +576,8 @@ export const makeMeshNode = <TName extends MeshNodeName>(
587
576
  print: () => {
588
577
  console.log('Webmesh debug info for node:', nodeName)
589
578
 
590
- console.log('Connections:', connectionChannels.size)
591
- for (const [key, value] of connectionChannels) {
579
+ console.log('Edges:', edgeChannels.size)
580
+ for (const [key, value] of edgeChannels) {
592
581
  console.log(` ${key}: supportsTransferables=${value.channel.supportsTransferables}`)
593
582
  }
594
583
 
@@ -620,11 +609,11 @@ export const makeMeshNode = <TName extends MeshNodeName>(
620
609
  ping: (payload) => {
621
610
  Effect.gen(function* () {
622
611
  const msg = (via: string) =>
623
- WebChannel.DebugPingMessage.make({ message: `ping from ${nodeName} via connection ${via}`, payload })
612
+ WebChannel.DebugPingMessage.make({ message: `ping from ${nodeName} via edge ${via}`, payload })
624
613
 
625
- for (const [channelName, con] of connectionChannels) {
626
- yield* Effect.logDebug(`sending ping via connection ${channelName}`)
627
- yield* con.channel.send(msg(`connection ${channelName}`) as any)
614
+ for (const [channelName, con] of edgeChannels) {
615
+ yield* Effect.logDebug(`sending ping via edge ${channelName}`)
616
+ yield* con.channel.send(msg(`edge ${channelName}`) as any)
628
617
  }
629
618
 
630
619
  for (const [channelKey, channel] of channelMap) {
@@ -636,14 +625,14 @@ export const makeMeshNode = <TName extends MeshNodeName>(
636
625
  },
637
626
  requestTopology: (timeoutMs = 1000) =>
638
627
  Effect.gen(function* () {
639
- const packet = WebmeshSchema.NetworkConnectionTopologyRequest.make({
628
+ const packet = WebmeshSchema.NetworkTopologyRequest.make({
640
629
  source: nodeName,
641
630
  target: '-',
642
631
  hops: [],
643
632
  })
644
633
 
645
634
  const item = new Map<MeshNodeName, Set<MeshNodeName>>()
646
- item.set(nodeName, new Set(connectionChannels.keys()))
635
+ item.set(nodeName, new Set(edgeChannels.keys()))
647
636
  topologyRequestsMap.set(packet.id, item)
648
637
 
649
638
  yield* sendPacket(packet)
@@ -659,11 +648,11 @@ export const makeMeshNode = <TName extends MeshNodeName>(
659
648
 
660
649
  return {
661
650
  nodeName,
662
- addConnection,
663
- removeConnection,
651
+ addEdge,
652
+ removeEdge,
664
653
  makeChannel,
665
654
  makeBroadcastChannel,
666
- connectionKeys,
655
+ edgeKeys,
667
656
  debug,
668
657
  } satisfies MeshNode
669
658
  }).pipe(Effect.withSpan(`makeMeshNode:${nodeName}`))
@@ -1,3 +1,4 @@
1
+ import type { HttpClient } from '@livestore/utils/effect'
1
2
  import {
2
3
  Deferred,
3
4
  Effect,
@@ -16,18 +17,18 @@ import type * as NodeWebSocket from 'ws'
16
17
  import * as WebmeshSchema from './mesh-schema.js'
17
18
  import type { MeshNode } from './node.js'
18
19
 
19
- export class WSConnectionInit extends Schema.TaggedStruct('WSConnectionInit', {
20
+ export class WSEdgeInit extends Schema.TaggedStruct('WSEdgeInit', {
20
21
  from: Schema.String,
21
22
  }) {}
22
23
 
23
- export class WSConnectionPayload extends Schema.TaggedStruct('WSConnectionPayload', {
24
+ export class WSEdgePayload extends Schema.TaggedStruct('WSEdgePayload', {
24
25
  from: Schema.String,
25
26
  payload: Schema.Any,
26
27
  }) {}
27
28
 
28
- export class WSConnectionMessage extends Schema.Union(WSConnectionInit, WSConnectionPayload) {}
29
+ export class WSEdgeMessage extends Schema.Union(WSEdgeInit, WSEdgePayload) {}
29
30
 
30
- export const MessageMsgPack = Schema.MsgPack(WSConnectionMessage)
31
+ export const MessageMsgPack = Schema.MsgPack(WSEdgeMessage)
31
32
 
32
33
  export type SocketType =
33
34
  | {
@@ -46,7 +47,7 @@ export const connectViaWebSocket = ({
46
47
  node: MeshNode
47
48
  url: string
48
49
  reconnect?: Schedule.Schedule<unknown> | false
49
- }): Effect.Effect<void, never, Scope.Scope> =>
50
+ }): Effect.Effect<void, never, Scope.Scope | HttpClient.HttpClient> =>
50
51
  Effect.gen(function* () {
51
52
  const disconnected = yield* Deferred.make<void>()
52
53
 
@@ -54,14 +55,14 @@ export const connectViaWebSocket = ({
54
55
 
55
56
  socket.addEventListener('close', () => Deferred.unsafeDone(disconnected, Exit.void))
56
57
 
57
- const connection = yield* makeWebSocketConnection(socket, { _tag: 'leaf', from: node.nodeName })
58
+ const edgeChannel = yield* makeWebSocketEdge(socket, { _tag: 'leaf', from: node.nodeName })
58
59
 
59
- yield* node.addConnection({ target: 'ws', connectionChannel: connection.webChannel, replaceIfExists: true })
60
+ yield* node.addEdge({ target: 'ws', edgeChannel: edgeChannel.webChannel, replaceIfExists: true })
60
61
 
61
62
  yield* disconnected
62
63
  }).pipe(Effect.scoped, Effect.forever, Effect.catchTag('WebSocketError', Effect.orDie))
63
64
 
64
- export const makeWebSocketConnection = (
65
+ export const makeWebSocketEdge = (
65
66
  socket: globalThis.WebSocket | NodeWebSocket.WebSocket,
66
67
  socketType: SocketType,
67
68
  ): Effect.Effect<
@@ -70,7 +71,7 @@ export const makeWebSocketConnection = (
70
71
  from: string
71
72
  },
72
73
  never,
73
- Scope.Scope
74
+ Scope.Scope | HttpClient.HttpClient
74
75
  > =>
75
76
  Effect.scopeWithCloseable((scope) =>
76
77
  Effect.gen(function* () {
@@ -89,7 +90,7 @@ export const makeWebSocketConnection = (
89
90
  Stream.flatten(),
90
91
  Stream.tap((msg) =>
91
92
  Effect.gen(function* () {
92
- if (msg._tag === 'WSConnectionInit') {
93
+ if (msg._tag === 'WSEdgeInit') {
93
94
  yield* Deferred.succeed(fromDeferred, msg.from)
94
95
  } else {
95
96
  const decodedPayload = yield* Schema.decode(schema.listen)(msg.payload)
@@ -104,7 +105,7 @@ export const makeWebSocketConnection = (
104
105
  )
105
106
 
106
107
  const initHandshake = (from: string) =>
107
- socket.send(Schema.encodeSync(MessageMsgPack)({ _tag: 'WSConnectionInit', from }))
108
+ socket.send(Schema.encodeSync(MessageMsgPack)({ _tag: 'WSEdgeInit', from }))
108
109
 
109
110
  if (socketType._tag === 'leaf') {
110
111
  initHandshake(socketType.from)
@@ -136,12 +137,12 @@ export const makeWebSocketConnection = (
136
137
  Effect.gen(function* () {
137
138
  yield* isConnectedLatch.await
138
139
  const payload = yield* Schema.encode(schema.send)(message)
139
- socket.send(Schema.encodeSync(MessageMsgPack)({ _tag: 'WSConnectionPayload', payload, from }))
140
+ socket.send(Schema.encodeSync(MessageMsgPack)({ _tag: 'WSEdgePayload', payload, from }))
140
141
  })
141
142
 
142
143
  const listen = Stream.fromQueue(listenQueue).pipe(
143
144
  Stream.map(Either.right),
144
- WebChannel.listenToDebugPing('websocket-connection'),
145
+ WebChannel.listenToDebugPing('websocket-edge'),
145
146
  )
146
147
 
147
148
  const webChannel = {
@@ -155,5 +156,5 @@ export const makeWebSocketConnection = (
155
156
  } satisfies WebChannel.WebChannel<typeof WebmeshSchema.Packet.Type, typeof WebmeshSchema.Packet.Type>
156
157
 
157
158
  return { webChannel, from }
158
- }).pipe(Effect.withSpanScoped('makeWebSocketConnection')),
159
+ }).pipe(Effect.withSpanScoped('makeWebSocketEdge')),
159
160
  )
@@ -1,16 +1,16 @@
1
1
  import { UnexpectedError } from '@livestore/common'
2
- import type { Scope } from '@livestore/utils/effect'
2
+ import type { HttpClient, Scope } from '@livestore/utils/effect'
3
3
  import { Effect, FiberSet } from '@livestore/utils/effect'
4
4
  import * as WebSocket from 'ws'
5
5
 
6
6
  import { makeMeshNode } from './node.js'
7
- import { makeWebSocketConnection } from './websocket-connection.js'
7
+ import { makeWebSocketEdge } from './websocket-edge.js'
8
8
 
9
9
  export const makeWebSocketServer = ({
10
10
  relayNodeName,
11
11
  }: {
12
12
  relayNodeName: string
13
- }): Effect.Effect<WebSocket.WebSocketServer, never, Scope.Scope> =>
13
+ }): Effect.Effect<WebSocket.WebSocketServer, never, Scope.Scope | HttpClient.HttpClient> =>
14
14
  Effect.gen(function* () {
15
15
  const server = new WebSocket.WebSocketServer({ noServer: true })
16
16
 
@@ -30,22 +30,22 @@ export const makeWebSocketServer = ({
30
30
 
31
31
  const node = yield* makeMeshNode(relayNodeName)
32
32
 
33
- const runtime = yield* Effect.runtime<never>()
33
+ const runtime = yield* Effect.runtime<HttpClient.HttpClient>()
34
34
 
35
35
  const fiberSet = yield* FiberSet.make()
36
36
 
37
37
  // TODO handle node disconnects (i.e. remove respective connection)
38
38
  server.on('connection', (socket) => {
39
39
  Effect.gen(function* () {
40
- const { webChannel, from } = yield* makeWebSocketConnection(socket, { _tag: 'relay' })
40
+ const { webChannel, from } = yield* makeWebSocketEdge(socket, { _tag: 'relay' })
41
41
 
42
- yield* node.addConnection({ target: from, connectionChannel: webChannel, replaceIfExists: true })
43
- yield* Effect.log(`WS Relay ${relayNodeName}: added connection from '${from}'`)
42
+ yield* node.addEdge({ target: from, edgeChannel: webChannel, replaceIfExists: true })
43
+ yield* Effect.log(`WS Relay ${relayNodeName}: added edge from '${from}'`)
44
44
 
45
45
  socket.addEventListener('close', () =>
46
46
  Effect.gen(function* () {
47
- yield* node.removeConnection(from)
48
- yield* Effect.log(`WS Relay ${relayNodeName}: removed connection from '${from}'`)
47
+ yield* node.removeEdge(from)
48
+ yield* Effect.log(`WS Relay ${relayNodeName}: removed edge from '${from}'`)
49
49
  }).pipe(Effect.provide(runtime), Effect.tapCauseLogPretty, Effect.runFork),
50
50
  )
51
51