@livestore/webmesh 0.3.0-dev.4 → 0.3.0-dev.41

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 (69) hide show
  1. package/README.md +42 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/channel/direct-channel-internal.d.ts +26 -0
  4. package/dist/channel/direct-channel-internal.d.ts.map +1 -0
  5. package/dist/channel/direct-channel-internal.js +217 -0
  6. package/dist/channel/direct-channel-internal.js.map +1 -0
  7. package/dist/channel/direct-channel.d.ts +22 -0
  8. package/dist/channel/direct-channel.d.ts.map +1 -0
  9. package/dist/channel/direct-channel.js +153 -0
  10. package/dist/channel/direct-channel.js.map +1 -0
  11. package/dist/channel/proxy-channel.d.ts +3 -3
  12. package/dist/channel/proxy-channel.d.ts.map +1 -1
  13. package/dist/channel/proxy-channel.js +119 -37
  14. package/dist/channel/proxy-channel.js.map +1 -1
  15. package/dist/common.d.ts +47 -19
  16. package/dist/common.d.ts.map +1 -1
  17. package/dist/common.js +13 -5
  18. package/dist/common.js.map +1 -1
  19. package/dist/mesh-schema.d.ts +79 -13
  20. package/dist/mesh-schema.d.ts.map +1 -1
  21. package/dist/mesh-schema.js +59 -10
  22. package/dist/mesh-schema.js.map +1 -1
  23. package/dist/mod.d.ts +2 -2
  24. package/dist/mod.d.ts.map +1 -1
  25. package/dist/mod.js +2 -2
  26. package/dist/mod.js.map +1 -1
  27. package/dist/node.d.ts +51 -24
  28. package/dist/node.d.ts.map +1 -1
  29. package/dist/node.js +322 -111
  30. package/dist/node.js.map +1 -1
  31. package/dist/node.test.d.ts +1 -1
  32. package/dist/node.test.d.ts.map +1 -1
  33. package/dist/node.test.js +489 -157
  34. package/dist/node.test.js.map +1 -1
  35. package/dist/utils.d.ts +4 -4
  36. package/dist/utils.d.ts.map +1 -1
  37. package/dist/utils.js +7 -1
  38. package/dist/utils.js.map +1 -1
  39. package/dist/websocket-edge.d.ts +53 -0
  40. package/dist/websocket-edge.d.ts.map +1 -0
  41. package/dist/websocket-edge.js +89 -0
  42. package/dist/websocket-edge.js.map +1 -0
  43. package/package.json +10 -6
  44. package/src/channel/direct-channel-internal.ts +356 -0
  45. package/src/channel/direct-channel.ts +234 -0
  46. package/src/channel/proxy-channel.ts +344 -234
  47. package/src/common.ts +24 -17
  48. package/src/mesh-schema.ts +73 -20
  49. package/src/mod.ts +2 -2
  50. package/src/node.test.ts +723 -190
  51. package/src/node.ts +497 -152
  52. package/src/utils.ts +13 -2
  53. package/src/websocket-edge.ts +183 -0
  54. package/dist/channel/message-channel.d.ts +0 -20
  55. package/dist/channel/message-channel.d.ts.map +0 -1
  56. package/dist/channel/message-channel.js +0 -183
  57. package/dist/channel/message-channel.js.map +0 -1
  58. package/dist/websocket-connection.d.ts +0 -51
  59. package/dist/websocket-connection.d.ts.map +0 -1
  60. package/dist/websocket-connection.js +0 -74
  61. package/dist/websocket-connection.js.map +0 -1
  62. package/dist/websocket-server.d.ts +0 -7
  63. package/dist/websocket-server.d.ts.map +0 -1
  64. package/dist/websocket-server.js +0 -24
  65. package/dist/websocket-server.js.map +0 -1
  66. package/src/channel/message-channel.ts +0 -354
  67. package/src/websocket-connection.ts +0 -158
  68. package/src/websocket-server.ts +0 -40
  69. package/tsconfig.json +0 -11
@@ -4,11 +4,13 @@ import {
4
4
  Deferred,
5
5
  Effect,
6
6
  Either,
7
+ Exit,
7
8
  Fiber,
8
9
  FiberHandle,
9
10
  Queue,
10
11
  Schedule,
11
12
  Schema,
13
+ Scope,
12
14
  Stream,
13
15
  SubscriptionRef,
14
16
  WebChannel,
@@ -27,7 +29,7 @@ import * as MeshSchema from '../mesh-schema.js'
27
29
  interface MakeProxyChannelArgs {
28
30
  queue: Queue.Queue<ProxyQueueItem>
29
31
  nodeName: MeshNodeName
30
- newConnectionAvailablePubSub: PubSub.PubSub<MeshNodeName>
32
+ newEdgeAvailablePubSub: PubSub.PubSub<MeshNodeName>
31
33
  sendPacket: (packet: typeof MeshSchema.ProxyChannelPacket.Type) => Effect.Effect<void>
32
34
  channelName: ChannelName
33
35
  target: MeshNodeName
@@ -40,293 +42,401 @@ interface MakeProxyChannelArgs {
40
42
  export const makeProxyChannel = ({
41
43
  queue,
42
44
  nodeName,
43
- newConnectionAvailablePubSub,
45
+ newEdgeAvailablePubSub,
44
46
  sendPacket,
45
47
  target,
46
48
  channelName,
47
49
  schema,
48
50
  }: MakeProxyChannelArgs) =>
49
- Effect.gen(function* () {
50
- type ProxiedChannelState =
51
- | {
52
- _tag: 'Initial'
53
- }
54
- | {
55
- _tag: 'Pending'
56
- initiatedVia: 'outgoing-request' | 'incoming-request'
57
- }
58
- | ProxiedChannelStateEstablished
59
-
60
- type ProxiedChannelStateEstablished = {
61
- _tag: 'Established'
62
- listenSchema: Schema.Schema<any, any>
63
- listenQueue: Queue.Queue<any>
64
- ackMap: Map<string, Deferred.Deferred<void, never>>
65
- combinedChannelId: string
66
- }
67
-
68
- const channelStateRef = { current: { _tag: 'Initial' } as ProxiedChannelState }
69
-
70
- /**
71
- * We need to unique identify a channel as multiple channels might exist between the same two nodes.
72
- * We do this by letting each channel end generate a unique id and then combining them in a deterministic way.
73
- */
74
- const channelIdCandidate = nanoid(5)
75
- yield* Effect.annotateCurrentSpan({ channelIdCandidate })
76
-
77
- const channelSpan = yield* Effect.currentSpan.pipe(Effect.orDie)
78
-
79
- const connectedStateRef = yield* SubscriptionRef.make<ProxiedChannelStateEstablished | false>(false)
80
-
81
- const waitForEstablished = Effect.gen(function* () {
82
- const state = yield* SubscriptionRef.waitUntil(connectedStateRef, (state) => state !== false)
83
-
84
- return state as ProxiedChannelStateEstablished
85
- })
86
-
87
- const setStateToEstablished = (channelId: string) =>
88
- Effect.gen(function* () {
89
- // TODO avoid "double" `Connected` events (we might call `setStateToEstablished` twice during initial connection)
90
- yield* Effect.spanEvent(`Connected (${channelId})`).pipe(Effect.withParentSpan(channelSpan))
91
- channelStateRef.current = {
92
- _tag: 'Established',
93
- listenSchema: schema.listen,
94
- listenQueue,
95
- ackMap,
96
- combinedChannelId: channelId,
97
- }
98
- yield* SubscriptionRef.set(connectedStateRef, channelStateRef.current)
99
- })
100
-
101
- const connectionRequest = Effect.suspend(() =>
102
- sendPacket(
103
- MeshSchema.ProxyChannelRequest.make({ channelName, hops: [], source: nodeName, target, channelIdCandidate }),
104
- ),
105
- )
51
+ Effect.scopeWithCloseable((scope) =>
52
+ Effect.gen(function* () {
53
+ type ProxiedChannelState =
54
+ | {
55
+ _tag: 'Initial'
56
+ }
57
+ | {
58
+ _tag: 'Pending'
59
+ initiatedVia: 'outgoing-request' | 'incoming-request'
60
+ }
61
+ | ProxiedChannelStateEstablished
62
+
63
+ type ProxiedChannelStateEstablished = {
64
+ _tag: 'Established'
65
+ listenSchema: Schema.Schema<any, any>
66
+ listenQueue: Queue.Queue<any>
67
+ ackMap: Map<string, Deferred.Deferred<void, never>>
68
+ combinedChannelId: string
69
+ }
106
70
 
107
- const getCombinedChannelId = (otherSideChannelIdCandidate: string) =>
108
- [channelIdCandidate, otherSideChannelIdCandidate].sort().join('_')
71
+ const channelStateRef = { current: { _tag: 'Initial' } as ProxiedChannelState }
109
72
 
110
- const processProxyPacket = ({ packet, respondToSender }: ProxyQueueItem) =>
111
- Effect.gen(function* () {
112
- // yield* Effect.log(`${nodeName}:processing packet ${packet._tag} from ${packet.source}`)
73
+ const debugInfo = {
74
+ kind: 'proxy-channel',
75
+ pendingSends: 0,
76
+ totalSends: 0,
77
+ connectCounter: 0,
78
+ isConnected: false,
79
+ }
113
80
 
114
- const otherSideName = packet.source
115
- const channelKey = `${otherSideName}-${packet.channelName}` satisfies ChannelKey
116
- const channelState = channelStateRef.current
81
+ /**
82
+ * We need to unique identify a channel as multiple channels might exist between the same two nodes.
83
+ * We do this by letting each channel end generate a unique id and then combining them in a deterministic way.
84
+ */
85
+ const channelIdCandidate = nanoid(5)
86
+ yield* Effect.annotateCurrentSpan({ channelIdCandidate })
117
87
 
118
- switch (packet._tag) {
119
- case 'ProxyChannelRequest': {
120
- const combinedChannelId = getCombinedChannelId(packet.channelIdCandidate)
88
+ const channelSpan = yield* Effect.currentSpan.pipe(Effect.orDie)
121
89
 
122
- if (channelState._tag === 'Initial' || channelState._tag === 'Established') {
123
- yield* SubscriptionRef.set(connectedStateRef, false)
124
- channelStateRef.current = { _tag: 'Pending', initiatedVia: 'incoming-request' }
125
- yield* Effect.spanEvent(`Reconnecting`).pipe(Effect.withParentSpan(channelSpan))
90
+ const connectedStateRef = yield* SubscriptionRef.make<ProxiedChannelStateEstablished | false>(false)
126
91
 
127
- // If we're already connected, we need to re-establish the connection
128
- if (channelState._tag === 'Established' && channelState.combinedChannelId !== combinedChannelId) {
129
- yield* connectionRequest
130
- }
131
- }
92
+ const waitForEstablished = Effect.gen(function* () {
93
+ const state = yield* SubscriptionRef.waitUntil(connectedStateRef, (state) => state !== false)
132
94
 
133
- yield* respondToSender(
134
- MeshSchema.ProxyChannelResponseSuccess.make({
135
- reqId: packet.id,
136
- remainingHops: packet.hops,
137
- hops: [],
138
- target,
139
- source: nodeName,
140
- channelName,
141
- combinedChannelId,
142
- channelIdCandidate,
143
- }),
144
- )
95
+ return state as ProxiedChannelStateEstablished
96
+ })
145
97
 
146
- return
98
+ const setStateToEstablished = (channelId: string) =>
99
+ Effect.gen(function* () {
100
+ // TODO avoid "double" `Connected` events (we might call `setStateToEstablished` twice during initial edge)
101
+ yield* Effect.spanEvent(`Connected (${channelId})`).pipe(Effect.withParentSpan(channelSpan))
102
+ channelStateRef.current = {
103
+ _tag: 'Established',
104
+ listenSchema: schema.listen,
105
+ listenQueue,
106
+ ackMap,
107
+ combinedChannelId: channelId,
147
108
  }
148
- case 'ProxyChannelResponseSuccess': {
149
- if (channelState._tag !== 'Pending') {
150
- // return shouldNeverHappen(`Expected proxy channel to be pending but got ${channelState._tag}`)
151
- if (channelState._tag === 'Established' && channelState.combinedChannelId !== packet.combinedChannelId) {
152
- return shouldNeverHappen(
153
- `Expected proxy channel to have the same combinedChannelId as the packet:\n${channelState.combinedChannelId} (channel) === ${packet.combinedChannelId} (packet)`,
109
+ yield* SubscriptionRef.set(connectedStateRef, channelStateRef.current)
110
+ debugInfo.isConnected = true
111
+ })
112
+
113
+ const edgeRequest = Effect.suspend(() =>
114
+ sendPacket(
115
+ MeshSchema.ProxyChannelRequest.make({ channelName, hops: [], source: nodeName, target, channelIdCandidate }),
116
+ ),
117
+ )
118
+
119
+ const getCombinedChannelId = (otherSideChannelIdCandidate: string) =>
120
+ [channelIdCandidate, otherSideChannelIdCandidate].sort().join('_')
121
+
122
+ const earlyPayloadBuffer = yield* Queue.unbounded<typeof MeshSchema.ProxyChannelPayload.Type>().pipe(
123
+ Effect.acquireRelease(Queue.shutdown),
124
+ )
125
+
126
+ const processProxyPacket = ({ packet, respondToSender }: ProxyQueueItem) =>
127
+ Effect.gen(function* () {
128
+ // yield* Effect.logDebug(
129
+ // `[${nodeName}] processProxyPacket received: ${packet._tag} from ${packet.source} (reqId: ${packet.id})`,
130
+ // )
131
+
132
+ const otherSideName = packet.source
133
+ const channelKey = `target:${otherSideName}, channelName:${packet.channelName}` satisfies ChannelKey
134
+ const channelState = channelStateRef.current
135
+
136
+ switch (packet._tag) {
137
+ case 'ProxyChannelRequest': {
138
+ const combinedChannelId = getCombinedChannelId(packet.channelIdCandidate)
139
+
140
+ // Handle Established state explicitly
141
+ if (channelState._tag === 'Established') {
142
+ // Check if the incoming request is for the *same* channel instance
143
+ if (channelState.combinedChannelId === combinedChannelId) {
144
+ // Already established with the same ID, likely a redundant request.
145
+ // Just respond and stay established.
146
+ // yield* Effect.logDebug(
147
+ // `[${nodeName}] Received redundant ProxyChannelRequest for already established channel instance ${combinedChannelId}. Responding.`,
148
+ // )
149
+ } else {
150
+ // Established, but the incoming request has a different ID.
151
+ // This implies a reconnect scenario where IDs don't match. Reset to Pending and re-initiate.
152
+ yield* Effect.logWarning(
153
+ `[${nodeName}] Received ProxyChannelRequest with different channel ID (${combinedChannelId}) while established with ${channelState.combinedChannelId}. Re-establishing.`,
154
+ )
155
+ yield* SubscriptionRef.set(connectedStateRef, false)
156
+ channelStateRef.current = { _tag: 'Pending', initiatedVia: 'incoming-request' }
157
+ yield* Effect.spanEvent(`Reconnecting (received conflicting ProxyChannelRequest)`).pipe(
158
+ Effect.withParentSpan(channelSpan),
159
+ )
160
+ debugInfo.isConnected = false
161
+ debugInfo.connectCounter++
162
+ // We need to send our own request as well to complete the handshake for the new ID
163
+ yield* edgeRequest
164
+ }
165
+ } else if (channelState._tag === 'Initial') {
166
+ // Standard initial connection: set to Pending
167
+ yield* SubscriptionRef.set(connectedStateRef, false) // Ensure connectedStateRef is false if we were somehow Initial but it wasn't false
168
+ channelStateRef.current = { _tag: 'Pending', initiatedVia: 'incoming-request' }
169
+ yield* Effect.spanEvent(`Connecting (received ProxyChannelRequest)`).pipe(
170
+ Effect.withParentSpan(channelSpan),
154
171
  )
155
- } else {
156
- // for now just ignore it but should be looked into (there seems to be some kind of race condition/inefficiency)
172
+ debugInfo.isConnected = false // Should be false already, but ensure consistency
173
+ debugInfo.connectCounter++
174
+ // No need to send edgeRequest here, the response acts as our part of the handshake for the incoming request's ID
157
175
  }
158
- }
159
-
160
- const combinedChannelId = getCombinedChannelId(packet.channelIdCandidate)
161
- if (combinedChannelId !== packet.combinedChannelId) {
162
- return yield* Effect.die(
163
- `Expected proxy channel to have the same combinedChannelId as the packet:\n${combinedChannelId} (channel) === ${packet.combinedChannelId} (packet)`,
176
+ // If state is 'Pending', we are already trying to connect.
177
+ // Just let the response go out, don't change state.
178
+
179
+ // Send the response regardless of the initial state (unless an error occurred)
180
+ yield* respondToSender(
181
+ MeshSchema.ProxyChannelResponseSuccess.make({
182
+ reqId: packet.id,
183
+ remainingHops: packet.hops,
184
+ hops: [],
185
+ target,
186
+ source: nodeName,
187
+ channelName,
188
+ combinedChannelId,
189
+ channelIdCandidate,
190
+ }),
164
191
  )
192
+
193
+ return
165
194
  }
195
+ case 'ProxyChannelResponseSuccess': {
196
+ if (channelState._tag !== 'Pending') {
197
+ if (
198
+ channelState._tag === 'Established' &&
199
+ channelState.combinedChannelId !== packet.combinedChannelId
200
+ ) {
201
+ return shouldNeverHappen(
202
+ `ProxyChannel[${channelKey}]: Expected proxy channel to have the same combinedChannelId as the packet:\n${channelState.combinedChannelId} (channel) === ${packet.combinedChannelId} (packet)`,
203
+ )
204
+ } else if (channelState._tag === 'Established') {
205
+ // yield* Effect.logDebug(`[${nodeName}] Ignoring redundant ResponseSuccess with same ID ${packet.id}`)
206
+ return
207
+ } else {
208
+ yield* Effect.logWarning(
209
+ `[${nodeName}] Ignoring ResponseSuccess ${packet.id} received in unexpected state ${channelState._tag}`,
210
+ )
211
+ return
212
+ }
213
+ }
214
+
215
+ const combinedChannelId = getCombinedChannelId(packet.channelIdCandidate)
216
+ if (combinedChannelId !== packet.combinedChannelId) {
217
+ return yield* Effect.die(
218
+ `ProxyChannel[${channelKey}]: Expected proxy channel to have the same combinedChannelId as the packet:\n${combinedChannelId} (channel) === ${packet.combinedChannelId} (packet)`,
219
+ )
220
+ }
166
221
 
167
- yield* setStateToEstablished(packet.combinedChannelId)
222
+ yield* setStateToEstablished(packet.combinedChannelId)
223
+
224
+ const establishedState = channelStateRef.current
225
+ if (establishedState._tag === 'Established') {
226
+ //
227
+ const bufferedPackets = yield* Queue.takeAll(earlyPayloadBuffer)
228
+ // yield* Effect.logDebug(
229
+ // `[${nodeName}] Draining early payload buffer (${bufferedPackets.length}) after ResponseSuccess`,
230
+ // )
231
+ for (const bufferedPacket of bufferedPackets) {
232
+ if (establishedState.combinedChannelId !== bufferedPacket.combinedChannelId) {
233
+ yield* Effect.logWarning(
234
+ `[${nodeName}] Discarding buffered payload ${bufferedPacket.id}: Combined channel ID mismatch during drain. Expected ${establishedState.combinedChannelId}, got ${bufferedPacket.combinedChannelId}`,
235
+ )
236
+ continue
237
+ }
238
+ const decodedMessage = yield* Schema.decodeUnknown(establishedState.listenSchema)(
239
+ bufferedPacket.payload,
240
+ )
241
+ yield* establishedState.listenQueue.pipe(Queue.offer(decodedMessage))
242
+ }
243
+ } else {
244
+ yield* Effect.logError(
245
+ `[${nodeName}] State is not Established immediately after setStateToEstablished was called. Cannot drain buffer. State: ${establishedState._tag}`,
246
+ )
247
+ }
168
248
 
169
- return
170
- }
171
- case 'ProxyChannelPayload': {
172
- if (channelState._tag !== 'Established') {
173
- // return yield* Effect.die(`Not yet connected to ${target}. dropping message`)
174
- yield* Effect.spanEvent(`Not yet connected to ${target}. dropping message`, { packet })
175
249
  return
176
250
  }
251
+ case 'ProxyChannelPayload': {
252
+ if (channelState._tag === 'Established' && channelState.combinedChannelId !== packet.combinedChannelId) {
253
+ return yield* Effect.die(
254
+ `ProxyChannel[${channelKey}]: Expected proxy channel to have the same combinedChannelId as the packet:\n${channelState.combinedChannelId} (channel) === ${packet.combinedChannelId} (packet)`,
255
+ )
256
+ }
177
257
 
178
- if (channelState.combinedChannelId !== packet.combinedChannelId) {
179
- return yield* Effect.die(
180
- `Expected proxy channel to have the same combinedChannelId as the packet:\n${channelState.combinedChannelId} (channel) === ${packet.combinedChannelId} (packet)`,
258
+ // yield* Effect.logDebug(`[${nodeName}] Received payload reqId: ${packet.id}. Sending Ack.`)
259
+ yield* respondToSender(
260
+ MeshSchema.ProxyChannelPayloadAck.make({
261
+ reqId: packet.id,
262
+ remainingHops: packet.hops,
263
+ hops: [],
264
+ target,
265
+ source: nodeName,
266
+ channelName,
267
+ combinedChannelId:
268
+ channelState._tag === 'Established' ? channelState.combinedChannelId : packet.combinedChannelId,
269
+ }),
181
270
  )
182
- }
183
271
 
184
- yield* respondToSender(
185
- MeshSchema.ProxyChannelPayloadAck.make({
186
- reqId: packet.id,
187
- remainingHops: packet.hops,
188
- hops: [],
189
- target,
190
- source: nodeName,
191
- channelName,
192
- combinedChannelId: channelState.combinedChannelId,
193
- }),
194
- )
195
-
196
- const decodedMessage = yield* Schema.decodeUnknown(channelState.listenSchema)(packet.payload)
197
- yield* channelState.listenQueue.pipe(Queue.offer(decodedMessage))
198
-
199
- return
200
- }
201
- case 'ProxyChannelPayloadAck': {
202
- if (channelState._tag !== 'Established') {
203
- yield* Effect.spanEvent(`Not yet connected to ${target}. dropping message`)
272
+ if (channelState._tag === 'Established') {
273
+ const decodedMessage = yield* Schema.decodeUnknown(channelState.listenSchema)(packet.payload)
274
+ yield* channelState.listenQueue.pipe(Queue.offer(decodedMessage))
275
+ } else {
276
+ // yield* Effect.logDebug(
277
+ // `[${nodeName}] Buffering early payload reqId: ${packet.id} (state: ${channelState._tag})`,
278
+ // )
279
+ yield* Queue.offer(earlyPayloadBuffer, packet)
280
+ }
204
281
  return
205
282
  }
283
+ case 'ProxyChannelPayloadAck': {
284
+ // yield* Effect.logDebug(`[${nodeName}] Received Ack for reqId: ${packet.reqId}`)
206
285
 
207
- const ack =
208
- channelState.ackMap.get(packet.reqId) ??
209
- shouldNeverHappen(`Expected ack for ${packet.reqId} in proxy channel ${channelKey}`)
286
+ if (channelState._tag !== 'Established') {
287
+ yield* Effect.spanEvent(`Not yet connected to ${target}. dropping message`)
288
+ yield* Effect.logWarning(
289
+ `[${nodeName}] Received Ack but not established (State: ${channelState._tag}). Dropping Ack for ${packet.reqId}`,
290
+ )
291
+ return
292
+ }
210
293
 
211
- yield* Deferred.succeed(ack, void 0)
294
+ const ack =
295
+ channelState.ackMap.get(packet.reqId) ??
296
+ shouldNeverHappen(`[ProxyChannel[${channelKey}]] Expected ack for ${packet.reqId}`)
297
+ yield* Deferred.succeed(ack, void 0)
212
298
 
213
- channelState.ackMap.delete(packet.reqId)
299
+ channelState.ackMap.delete(packet.reqId)
214
300
 
215
- return
216
- }
217
- default: {
218
- return casesHandled(packet)
301
+ return
302
+ }
303
+ default: {
304
+ return casesHandled(packet)
305
+ }
219
306
  }
220
- }
221
- }).pipe(
222
- Effect.withSpan(`handleProxyPacket:${packet._tag}:${packet.source}->${packet.target}`, {
223
- attributes: packetAsOtelAttributes(packet),
224
- }),
225
- )
226
-
227
- yield* Stream.fromQueue(queue).pipe(
228
- Stream.tap(processProxyPacket),
229
- Stream.runDrain,
230
- Effect.tapCauseLogPretty,
231
- Effect.forkScoped,
232
- )
307
+ }).pipe(
308
+ Effect.withSpan(`handleProxyPacket:${packet._tag}:${packet.source}->${packet.target}`, {
309
+ attributes: packetAsOtelAttributes(packet),
310
+ }),
311
+ )
233
312
 
234
- const listenQueue = yield* Queue.unbounded<any>()
313
+ yield* Stream.fromQueue(queue).pipe(
314
+ Stream.tap(processProxyPacket),
315
+ Stream.runDrain,
316
+ Effect.tapCauseLogPretty,
317
+ Effect.forkScoped,
318
+ )
235
319
 
236
- yield* Effect.spanEvent(`Connecting`)
320
+ const listenQueue = yield* Queue.unbounded<any>()
237
321
 
238
- const ackMap = new Map<string, Deferred.Deferred<void, never>>()
322
+ yield* Effect.spanEvent(`Connecting`)
239
323
 
240
- // check if already established via incoming `ProxyChannelRequest` from other side
241
- // which indicates we already have a connection to the target node
242
- // const channelState = channelStateRef.current
243
- {
244
- if (channelStateRef.current._tag !== 'Initial') {
245
- return shouldNeverHappen('Expected proxy channel to be Initial')
246
- }
324
+ const ackMap = new Map<string, Deferred.Deferred<void, never>>()
247
325
 
248
- channelStateRef.current = { _tag: 'Pending', initiatedVia: 'outgoing-request' }
326
+ // check if already established via incoming `ProxyChannelRequest` from other side
327
+ // which indicates we already have a edge to the target node
328
+ // const channelState = channelStateRef.current
329
+ {
330
+ if (channelStateRef.current._tag !== 'Initial') {
331
+ return shouldNeverHappen('Expected proxy channel to be Initial')
332
+ }
249
333
 
250
- yield* connectionRequest
334
+ channelStateRef.current = { _tag: 'Pending', initiatedVia: 'outgoing-request' }
251
335
 
252
- const retryOnNewConnectionFiber = yield* Stream.fromPubSub(newConnectionAvailablePubSub).pipe(
253
- Stream.tap(() => connectionRequest),
254
- Stream.runDrain,
255
- Effect.forkScoped,
256
- )
336
+ yield* edgeRequest
257
337
 
258
- const { combinedChannelId: channelId } = yield* waitForEstablished
338
+ const retryOnNewEdgeFiber = yield* Stream.fromPubSub(newEdgeAvailablePubSub).pipe(
339
+ Stream.tap(() => edgeRequest),
340
+ Stream.runDrain,
341
+ Effect.forkScoped,
342
+ )
259
343
 
260
- yield* Fiber.interrupt(retryOnNewConnectionFiber)
344
+ const { combinedChannelId: channelId } = yield* waitForEstablished
261
345
 
262
- yield* setStateToEstablished(channelId)
263
- }
346
+ yield* Fiber.interrupt(retryOnNewEdgeFiber)
264
347
 
265
- const send = (message: any) =>
266
- Effect.gen(function* () {
267
- const payload = yield* Schema.encodeUnknown(schema.send)(message)
268
- const sendFiberHandle = yield* FiberHandle.make<void, never>()
348
+ yield* setStateToEstablished(channelId)
349
+ }
269
350
 
270
- const sentDeferred = yield* Deferred.make<void>()
351
+ const send = (message: any) =>
352
+ Effect.gen(function* () {
353
+ const payload = yield* Schema.encodeUnknown(schema.send)(message)
354
+ const sendFiberHandle = yield* FiberHandle.make<void, never>()
271
355
 
272
- const trySend = Effect.gen(function* () {
273
- const { combinedChannelId } = (yield* SubscriptionRef.waitUntil(
274
- connectedStateRef,
275
- (channel) => channel !== false,
276
- )) as ProxiedChannelStateEstablished
356
+ const sentDeferred = yield* Deferred.make<void>()
277
357
 
278
- const innerSend = Effect.gen(function* () {
279
- // Note we're re-creating new packets every time otherwise they will be skipped because of `handledIds`
280
- const ack = yield* Deferred.make<void, never>()
281
- const packet = MeshSchema.ProxyChannelPayload.make({
282
- channelName,
283
- payload,
284
- hops: [],
285
- source: nodeName,
286
- target,
287
- combinedChannelId,
288
- })
289
- ackMap.set(packet.id, ack)
358
+ debugInfo.pendingSends++
359
+ debugInfo.totalSends++
290
360
 
291
- yield* sendPacket(packet)
361
+ const trySend = Effect.gen(function* () {
362
+ const { combinedChannelId } = (yield* SubscriptionRef.waitUntil(
363
+ connectedStateRef,
364
+ (channel) => channel !== false,
365
+ )) as ProxiedChannelStateEstablished
292
366
 
293
- yield* ack
294
- yield* Deferred.succeed(sentDeferred, void 0)
295
- })
367
+ const innerSend = Effect.gen(function* () {
368
+ // Note we're re-creating new packets every time otherwise they will be skipped because of `handledIds`
369
+ const ack = yield* Deferred.make<void, never>()
370
+ const packet = MeshSchema.ProxyChannelPayload.make({
371
+ channelName,
372
+ payload,
373
+ hops: [],
374
+ source: nodeName,
375
+ target,
376
+ combinedChannelId,
377
+ })
378
+ // TODO consider handling previous ackMap entries which might leak/fill-up memory
379
+ // as only successful acks are removed from the map
380
+ ackMap.set(packet.id, ack)
296
381
 
297
- yield* innerSend.pipe(Effect.timeout(100), Effect.retry(Schedule.exponential(100)), Effect.orDie)
298
- }).pipe(Effect.tapErrorCause(Effect.logError))
382
+ yield* sendPacket(packet)
299
383
 
300
- const rerunOnNewChannelFiber = yield* connectedStateRef.changes.pipe(
301
- Stream.filter((_) => _ === false),
302
- Stream.tap(() => FiberHandle.run(sendFiberHandle, trySend)),
303
- Stream.runDrain,
304
- Effect.fork,
305
- )
384
+ yield* ack
385
+ yield* Deferred.succeed(sentDeferred, void 0)
306
386
 
307
- yield* FiberHandle.run(sendFiberHandle, trySend)
387
+ debugInfo.pendingSends--
388
+ })
308
389
 
309
- yield* sentDeferred
390
+ // TODO make this configurable
391
+ // Schedule.exponential(10): 10, 20, 40, 80, 160, 320, ...
392
+ yield* innerSend.pipe(Effect.timeout(100), Effect.retry(Schedule.exponential(10)), Effect.orDie)
393
+ }).pipe(Effect.tapErrorCause(Effect.logError))
310
394
 
311
- yield* Fiber.interrupt(rerunOnNewChannelFiber)
312
- }).pipe(
313
- Effect.scoped,
314
- Effect.withSpan(`sendAckWithRetry:ProxyChannelPayload`),
315
- Effect.withParentSpan(channelSpan),
316
- )
395
+ const rerunOnNewChannelFiber = yield* connectedStateRef.changes.pipe(
396
+ Stream.filter((_) => _ === false),
397
+ Stream.tap(() => FiberHandle.run(sendFiberHandle, trySend)),
398
+ Stream.runDrain,
399
+ Effect.fork,
400
+ )
317
401
 
318
- const listen = Stream.fromQueue(listenQueue).pipe(Stream.map(Either.right))
402
+ yield* FiberHandle.run(sendFiberHandle, trySend)
319
403
 
320
- const closedDeferred = yield* Deferred.make<void>()
404
+ yield* sentDeferred
321
405
 
322
- const webChannel = {
323
- [WebChannel.WebChannelSymbol]: WebChannel.WebChannelSymbol,
324
- send,
325
- listen,
326
- closedDeferred,
327
- supportsTransferables: true,
328
- schema,
329
- } satisfies WebChannel.WebChannel<any, any>
406
+ yield* Fiber.interrupt(rerunOnNewChannelFiber)
407
+ }).pipe(
408
+ Effect.scoped,
409
+ Effect.withSpan(`sendAckWithRetry:ProxyChannelPayload`),
410
+ Effect.withParentSpan(channelSpan),
411
+ )
330
412
 
331
- return webChannel as WebChannel.WebChannel<any, any>
332
- }).pipe(Effect.withSpanScoped('makeProxyChannel'))
413
+ const listen = Stream.fromQueue(listenQueue).pipe(Stream.map(Either.right))
414
+
415
+ const closedDeferred = yield* Deferred.make<void>().pipe(Effect.acquireRelease(Deferred.done(Exit.void)))
416
+
417
+ const runtime = yield* Effect.runtime()
418
+
419
+ const webChannel = {
420
+ [WebChannel.WebChannelSymbol]: WebChannel.WebChannelSymbol,
421
+ send,
422
+ listen,
423
+ closedDeferred,
424
+ supportsTransferables: false,
425
+ schema,
426
+ shutdown: Scope.close(scope, Exit.void),
427
+ debugInfo,
428
+ ...({
429
+ debug: {
430
+ ping: (message: string = 'ping') =>
431
+ send(WebChannel.DebugPingMessage.make({ message })).pipe(
432
+ Effect.provide(runtime),
433
+ Effect.tapCauseLogPretty,
434
+ Effect.runFork,
435
+ ),
436
+ },
437
+ } as {}),
438
+ } satisfies WebChannel.WebChannel<any, any>
439
+
440
+ return webChannel as WebChannel.WebChannel<any, any>
441
+ }).pipe(Effect.withSpanScoped('makeProxyChannel')),
442
+ )