@livestore/webmesh 0.3.0-dev.2 → 0.3.0-dev.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/channel/message-channel-internal.d.ts +26 -0
- package/dist/channel/message-channel-internal.d.ts.map +1 -0
- package/dist/channel/message-channel-internal.js +217 -0
- package/dist/channel/message-channel-internal.js.map +1 -0
- package/dist/channel/message-channel.d.ts +21 -19
- package/dist/channel/message-channel.d.ts.map +1 -1
- package/dist/channel/message-channel.js +132 -162
- package/dist/channel/message-channel.js.map +1 -1
- package/dist/channel/proxy-channel.d.ts +3 -3
- package/dist/channel/proxy-channel.d.ts.map +1 -1
- package/dist/channel/proxy-channel.js +38 -19
- package/dist/channel/proxy-channel.js.map +1 -1
- package/dist/common.d.ts +36 -14
- package/dist/common.d.ts.map +1 -1
- package/dist/common.js +7 -4
- package/dist/common.js.map +1 -1
- package/dist/mesh-schema.d.ts +71 -5
- package/dist/mesh-schema.d.ts.map +1 -1
- package/dist/mesh-schema.js +55 -6
- package/dist/mesh-schema.js.map +1 -1
- package/dist/mod.d.ts +2 -2
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +2 -2
- package/dist/mod.js.map +1 -1
- package/dist/node.d.ts +43 -21
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +271 -95
- package/dist/node.js.map +1 -1
- package/dist/node.test.d.ts +1 -1
- package/dist/node.test.d.ts.map +1 -1
- package/dist/node.test.js +391 -156
- package/dist/node.test.js.map +1 -1
- package/dist/websocket-connection.d.ts +6 -7
- package/dist/websocket-connection.d.ts.map +1 -1
- package/dist/websocket-connection.js +21 -26
- package/dist/websocket-connection.js.map +1 -1
- package/dist/websocket-edge.d.ts +50 -0
- package/dist/websocket-edge.d.ts.map +1 -0
- package/dist/websocket-edge.js +69 -0
- package/dist/websocket-edge.js.map +1 -0
- package/dist/websocket-server.d.ts.map +1 -1
- package/dist/websocket-server.js +23 -9
- package/dist/websocket-server.js.map +1 -1
- package/package.json +7 -6
- package/src/channel/message-channel-internal.ts +356 -0
- package/src/channel/message-channel.ts +190 -310
- package/src/channel/proxy-channel.ts +259 -231
- package/src/common.ts +12 -13
- package/src/mesh-schema.ts +62 -6
- package/src/mod.ts +2 -2
- package/src/node.test.ts +554 -189
- package/src/node.ts +417 -134
- package/src/websocket-edge.ts +159 -0
- package/src/websocket-server.ts +26 -9
- package/tmp/pack.tgz +0 -0
- package/src/websocket-connection.ts +0 -158
|
@@ -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
|
-
|
|
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,319 @@ interface MakeProxyChannelArgs {
|
|
|
40
42
|
export const makeProxyChannel = ({
|
|
41
43
|
queue,
|
|
42
44
|
nodeName,
|
|
43
|
-
|
|
45
|
+
newEdgeAvailablePubSub,
|
|
44
46
|
sendPacket,
|
|
45
47
|
target,
|
|
46
48
|
channelName,
|
|
47
49
|
schema,
|
|
48
50
|
}: MakeProxyChannelArgs) =>
|
|
49
|
-
Effect.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
})
|
|
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
|
+
}
|
|
100
70
|
|
|
101
|
-
|
|
102
|
-
sendPacket(
|
|
103
|
-
MeshSchema.ProxyChannelRequest.make({ channelName, hops: [], source: nodeName, target, channelIdCandidate }),
|
|
104
|
-
),
|
|
105
|
-
)
|
|
71
|
+
const channelStateRef = { current: { _tag: 'Initial' } as ProxiedChannelState }
|
|
106
72
|
|
|
107
|
-
|
|
108
|
-
|
|
73
|
+
const debugInfo = {
|
|
74
|
+
pendingSends: 0,
|
|
75
|
+
totalSends: 0,
|
|
76
|
+
connectCounter: 0,
|
|
77
|
+
isConnected: false,
|
|
78
|
+
}
|
|
109
79
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
80
|
+
/**
|
|
81
|
+
* We need to unique identify a channel as multiple channels might exist between the same two nodes.
|
|
82
|
+
* We do this by letting each channel end generate a unique id and then combining them in a deterministic way.
|
|
83
|
+
*/
|
|
84
|
+
const channelIdCandidate = nanoid(5)
|
|
85
|
+
yield* Effect.annotateCurrentSpan({ channelIdCandidate })
|
|
113
86
|
|
|
114
|
-
|
|
115
|
-
const channelKey = `${otherSideName}-${packet.channelName}` satisfies ChannelKey
|
|
116
|
-
const channelState = channelStateRef.current
|
|
87
|
+
const channelSpan = yield* Effect.currentSpan.pipe(Effect.orDie)
|
|
117
88
|
|
|
118
|
-
|
|
119
|
-
case 'ProxyChannelRequest': {
|
|
120
|
-
const combinedChannelId = getCombinedChannelId(packet.channelIdCandidate)
|
|
89
|
+
const connectedStateRef = yield* SubscriptionRef.make<ProxiedChannelStateEstablished | false>(false)
|
|
121
90
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
channelStateRef.current = { _tag: 'Pending', initiatedVia: 'incoming-request' }
|
|
125
|
-
yield* Effect.spanEvent(`Reconnecting`).pipe(Effect.withParentSpan(channelSpan))
|
|
91
|
+
const waitForEstablished = Effect.gen(function* () {
|
|
92
|
+
const state = yield* SubscriptionRef.waitUntil(connectedStateRef, (state) => state !== false)
|
|
126
93
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
yield* connectionRequest
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
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
|
-
)
|
|
94
|
+
return state as ProxiedChannelStateEstablished
|
|
95
|
+
})
|
|
145
96
|
|
|
146
|
-
|
|
97
|
+
const setStateToEstablished = (channelId: string) =>
|
|
98
|
+
Effect.gen(function* () {
|
|
99
|
+
// TODO avoid "double" `Connected` events (we might call `setStateToEstablished` twice during initial edge)
|
|
100
|
+
yield* Effect.spanEvent(`Connected (${channelId})`).pipe(Effect.withParentSpan(channelSpan))
|
|
101
|
+
channelStateRef.current = {
|
|
102
|
+
_tag: 'Established',
|
|
103
|
+
listenSchema: schema.listen,
|
|
104
|
+
listenQueue,
|
|
105
|
+
ackMap,
|
|
106
|
+
combinedChannelId: channelId,
|
|
147
107
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
108
|
+
yield* SubscriptionRef.set(connectedStateRef, channelStateRef.current)
|
|
109
|
+
debugInfo.isConnected = true
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
const edgeRequest = Effect.suspend(() =>
|
|
113
|
+
sendPacket(
|
|
114
|
+
MeshSchema.ProxyChannelRequest.make({ channelName, hops: [], source: nodeName, target, channelIdCandidate }),
|
|
115
|
+
),
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
const getCombinedChannelId = (otherSideChannelIdCandidate: string) =>
|
|
119
|
+
[channelIdCandidate, otherSideChannelIdCandidate].sort().join('_')
|
|
120
|
+
|
|
121
|
+
const processProxyPacket = ({ packet, respondToSender }: ProxyQueueItem) =>
|
|
122
|
+
Effect.gen(function* () {
|
|
123
|
+
// yield* Effect.log(`${nodeName}:processing packet ${packet._tag} from ${packet.source}`)
|
|
124
|
+
|
|
125
|
+
const otherSideName = packet.source
|
|
126
|
+
const channelKey = `target:${otherSideName}, channelName:${packet.channelName}` satisfies ChannelKey
|
|
127
|
+
const channelState = channelStateRef.current
|
|
128
|
+
|
|
129
|
+
switch (packet._tag) {
|
|
130
|
+
case 'ProxyChannelRequest': {
|
|
131
|
+
const combinedChannelId = getCombinedChannelId(packet.channelIdCandidate)
|
|
132
|
+
|
|
133
|
+
if (channelState._tag === 'Initial' || channelState._tag === 'Established') {
|
|
134
|
+
yield* SubscriptionRef.set(connectedStateRef, false)
|
|
135
|
+
channelStateRef.current = { _tag: 'Pending', initiatedVia: 'incoming-request' }
|
|
136
|
+
yield* Effect.spanEvent(`Reconnecting`).pipe(Effect.withParentSpan(channelSpan))
|
|
137
|
+
debugInfo.isConnected = false
|
|
138
|
+
debugInfo.connectCounter++
|
|
139
|
+
|
|
140
|
+
// If we're already connected, we need to re-establish the edge
|
|
141
|
+
if (channelState._tag === 'Established' && channelState.combinedChannelId !== combinedChannelId) {
|
|
142
|
+
yield* edgeRequest
|
|
143
|
+
}
|
|
157
144
|
}
|
|
158
|
-
}
|
|
159
145
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
146
|
+
yield* respondToSender(
|
|
147
|
+
MeshSchema.ProxyChannelResponseSuccess.make({
|
|
148
|
+
reqId: packet.id,
|
|
149
|
+
remainingHops: packet.hops,
|
|
150
|
+
hops: [],
|
|
151
|
+
target,
|
|
152
|
+
source: nodeName,
|
|
153
|
+
channelName,
|
|
154
|
+
combinedChannelId,
|
|
155
|
+
channelIdCandidate,
|
|
156
|
+
}),
|
|
164
157
|
)
|
|
158
|
+
|
|
159
|
+
return
|
|
165
160
|
}
|
|
161
|
+
case 'ProxyChannelResponseSuccess': {
|
|
162
|
+
if (channelState._tag !== 'Pending') {
|
|
163
|
+
// return shouldNeverHappen(`Expected proxy channel to be pending but got ${channelState._tag}`)
|
|
164
|
+
if (
|
|
165
|
+
channelState._tag === 'Established' &&
|
|
166
|
+
channelState.combinedChannelId !== packet.combinedChannelId
|
|
167
|
+
) {
|
|
168
|
+
return shouldNeverHappen(
|
|
169
|
+
`ProxyChannel[${channelKey}]: Expected proxy channel to have the same combinedChannelId as the packet:\n${channelState.combinedChannelId} (channel) === ${packet.combinedChannelId} (packet)`,
|
|
170
|
+
)
|
|
171
|
+
} else {
|
|
172
|
+
// for now just ignore it but should be looked into (there seems to be some kind of race condition/inefficiency)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
166
175
|
|
|
167
|
-
|
|
176
|
+
const combinedChannelId = getCombinedChannelId(packet.channelIdCandidate)
|
|
177
|
+
if (combinedChannelId !== packet.combinedChannelId) {
|
|
178
|
+
return yield* Effect.die(
|
|
179
|
+
`ProxyChannel[${channelKey}]: Expected proxy channel to have the same combinedChannelId as the packet:\n${combinedChannelId} (channel) === ${packet.combinedChannelId} (packet)`,
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
yield* setStateToEstablished(packet.combinedChannelId)
|
|
168
184
|
|
|
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
185
|
return
|
|
176
186
|
}
|
|
187
|
+
case 'ProxyChannelPayload': {
|
|
188
|
+
if (channelState._tag !== 'Established') {
|
|
189
|
+
// return yield* Effect.die(`Not yet connected to ${target}. dropping message`)
|
|
190
|
+
yield* Effect.spanEvent(`Not yet connected to ${target}. dropping message`, { packet })
|
|
191
|
+
return
|
|
192
|
+
}
|
|
177
193
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
194
|
+
if (channelState.combinedChannelId !== packet.combinedChannelId) {
|
|
195
|
+
return yield* Effect.die(
|
|
196
|
+
`ProxyChannel[${channelKey}]: Expected proxy channel to have the same combinedChannelId as the packet:\n${channelState.combinedChannelId} (channel) === ${packet.combinedChannelId} (packet)`,
|
|
197
|
+
)
|
|
198
|
+
}
|
|
183
199
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
200
|
+
yield* respondToSender(
|
|
201
|
+
MeshSchema.ProxyChannelPayloadAck.make({
|
|
202
|
+
reqId: packet.id,
|
|
203
|
+
remainingHops: packet.hops,
|
|
204
|
+
hops: [],
|
|
205
|
+
target,
|
|
206
|
+
source: nodeName,
|
|
207
|
+
channelName,
|
|
208
|
+
combinedChannelId: channelState.combinedChannelId,
|
|
209
|
+
}),
|
|
210
|
+
)
|
|
195
211
|
|
|
196
|
-
|
|
197
|
-
|
|
212
|
+
const decodedMessage = yield* Schema.decodeUnknown(channelState.listenSchema)(packet.payload)
|
|
213
|
+
yield* channelState.listenQueue.pipe(Queue.offer(decodedMessage))
|
|
198
214
|
|
|
199
|
-
return
|
|
200
|
-
}
|
|
201
|
-
case 'ProxyChannelPayloadAck': {
|
|
202
|
-
if (channelState._tag !== 'Established') {
|
|
203
|
-
yield* Effect.spanEvent(`Not yet connected to ${target}. dropping message`)
|
|
204
215
|
return
|
|
205
216
|
}
|
|
217
|
+
case 'ProxyChannelPayloadAck': {
|
|
218
|
+
if (channelState._tag !== 'Established') {
|
|
219
|
+
yield* Effect.spanEvent(`Not yet connected to ${target}. dropping message`)
|
|
220
|
+
return
|
|
221
|
+
}
|
|
206
222
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
223
|
+
const ack =
|
|
224
|
+
channelState.ackMap.get(packet.reqId) ??
|
|
225
|
+
shouldNeverHappen(`[ProxyChannel[${channelKey}]] Expected ack for ${packet.reqId}`)
|
|
210
226
|
|
|
211
|
-
|
|
227
|
+
yield* Deferred.succeed(ack, void 0)
|
|
212
228
|
|
|
213
|
-
|
|
229
|
+
channelState.ackMap.delete(packet.reqId)
|
|
214
230
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
231
|
+
return
|
|
232
|
+
}
|
|
233
|
+
default: {
|
|
234
|
+
return casesHandled(packet)
|
|
235
|
+
}
|
|
219
236
|
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
237
|
+
}).pipe(
|
|
238
|
+
Effect.withSpan(`handleProxyPacket:${packet._tag}:${packet.source}->${packet.target}`, {
|
|
239
|
+
attributes: packetAsOtelAttributes(packet),
|
|
240
|
+
}),
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
yield* Stream.fromQueue(queue).pipe(
|
|
244
|
+
Stream.tap(processProxyPacket),
|
|
245
|
+
Stream.runDrain,
|
|
246
|
+
Effect.tapCauseLogPretty,
|
|
247
|
+
Effect.forkScoped,
|
|
225
248
|
)
|
|
226
249
|
|
|
227
|
-
|
|
228
|
-
Stream.tap(processProxyPacket),
|
|
229
|
-
Stream.runDrain,
|
|
230
|
-
Effect.tapCauseLogPretty,
|
|
231
|
-
Effect.forkScoped,
|
|
232
|
-
)
|
|
250
|
+
const listenQueue = yield* Queue.unbounded<any>()
|
|
233
251
|
|
|
234
|
-
|
|
252
|
+
yield* Effect.spanEvent(`Connecting`)
|
|
235
253
|
|
|
236
|
-
|
|
254
|
+
const ackMap = new Map<string, Deferred.Deferred<void, never>>()
|
|
237
255
|
|
|
238
|
-
|
|
256
|
+
// check if already established via incoming `ProxyChannelRequest` from other side
|
|
257
|
+
// which indicates we already have a edge to the target node
|
|
258
|
+
// const channelState = channelStateRef.current
|
|
259
|
+
{
|
|
260
|
+
if (channelStateRef.current._tag !== 'Initial') {
|
|
261
|
+
return shouldNeverHappen('Expected proxy channel to be Initial')
|
|
262
|
+
}
|
|
239
263
|
|
|
240
|
-
|
|
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
|
-
}
|
|
264
|
+
channelStateRef.current = { _tag: 'Pending', initiatedVia: 'outgoing-request' }
|
|
247
265
|
|
|
248
|
-
|
|
266
|
+
yield* edgeRequest
|
|
249
267
|
|
|
250
|
-
|
|
268
|
+
const retryOnNewEdgeFiber = yield* Stream.fromPubSub(newEdgeAvailablePubSub).pipe(
|
|
269
|
+
Stream.tap(() => edgeRequest),
|
|
270
|
+
Stream.runDrain,
|
|
271
|
+
Effect.forkScoped,
|
|
272
|
+
)
|
|
251
273
|
|
|
252
|
-
|
|
253
|
-
Stream.tap(() => connectionRequest),
|
|
254
|
-
Stream.runDrain,
|
|
255
|
-
Effect.forkScoped,
|
|
256
|
-
)
|
|
274
|
+
const { combinedChannelId: channelId } = yield* waitForEstablished
|
|
257
275
|
|
|
258
|
-
|
|
276
|
+
yield* Fiber.interrupt(retryOnNewEdgeFiber)
|
|
259
277
|
|
|
260
|
-
|
|
278
|
+
yield* setStateToEstablished(channelId)
|
|
279
|
+
}
|
|
261
280
|
|
|
262
|
-
|
|
263
|
-
|
|
281
|
+
const send = (message: any) =>
|
|
282
|
+
Effect.gen(function* () {
|
|
283
|
+
const payload = yield* Schema.encodeUnknown(schema.send)(message)
|
|
284
|
+
const sendFiberHandle = yield* FiberHandle.make<void, never>()
|
|
264
285
|
|
|
265
|
-
|
|
266
|
-
Effect.gen(function* () {
|
|
267
|
-
const payload = yield* Schema.encodeUnknown(schema.send)(message)
|
|
268
|
-
const sendFiberHandle = yield* FiberHandle.make<void, never>()
|
|
286
|
+
const sentDeferred = yield* Deferred.make<void>()
|
|
269
287
|
|
|
270
|
-
|
|
288
|
+
debugInfo.pendingSends++
|
|
289
|
+
debugInfo.totalSends++
|
|
271
290
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
291
|
+
const trySend = Effect.gen(function* () {
|
|
292
|
+
const { combinedChannelId } = (yield* SubscriptionRef.waitUntil(
|
|
293
|
+
connectedStateRef,
|
|
294
|
+
(channel) => channel !== false,
|
|
295
|
+
)) as ProxiedChannelStateEstablished
|
|
277
296
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
297
|
+
const innerSend = Effect.gen(function* () {
|
|
298
|
+
// Note we're re-creating new packets every time otherwise they will be skipped because of `handledIds`
|
|
299
|
+
const ack = yield* Deferred.make<void, never>()
|
|
300
|
+
const packet = MeshSchema.ProxyChannelPayload.make({
|
|
301
|
+
channelName,
|
|
302
|
+
payload,
|
|
303
|
+
hops: [],
|
|
304
|
+
source: nodeName,
|
|
305
|
+
target,
|
|
306
|
+
combinedChannelId,
|
|
307
|
+
})
|
|
308
|
+
// TODO consider handling previous ackMap entries which might leak/fill-up memory
|
|
309
|
+
// as only successful acks are removed from the map
|
|
310
|
+
ackMap.set(packet.id, ack)
|
|
290
311
|
|
|
291
|
-
|
|
312
|
+
yield* sendPacket(packet)
|
|
292
313
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
})
|
|
314
|
+
yield* ack
|
|
315
|
+
yield* Deferred.succeed(sentDeferred, void 0)
|
|
296
316
|
|
|
297
|
-
|
|
298
|
-
|
|
317
|
+
debugInfo.pendingSends--
|
|
318
|
+
})
|
|
299
319
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
Effect.fork,
|
|
305
|
-
)
|
|
320
|
+
// TODO make this configurable
|
|
321
|
+
// Schedule.exponential(10): 10, 20, 40, 80, 160, 320, ...
|
|
322
|
+
yield* innerSend.pipe(Effect.timeout(100), Effect.retry(Schedule.exponential(10)), Effect.orDie)
|
|
323
|
+
}).pipe(Effect.tapErrorCause(Effect.logError))
|
|
306
324
|
|
|
307
|
-
|
|
325
|
+
const rerunOnNewChannelFiber = yield* connectedStateRef.changes.pipe(
|
|
326
|
+
Stream.filter((_) => _ === false),
|
|
327
|
+
Stream.tap(() => FiberHandle.run(sendFiberHandle, trySend)),
|
|
328
|
+
Stream.runDrain,
|
|
329
|
+
Effect.fork,
|
|
330
|
+
)
|
|
308
331
|
|
|
309
|
-
|
|
332
|
+
yield* FiberHandle.run(sendFiberHandle, trySend)
|
|
310
333
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
334
|
+
yield* sentDeferred
|
|
335
|
+
|
|
336
|
+
yield* Fiber.interrupt(rerunOnNewChannelFiber)
|
|
337
|
+
}).pipe(
|
|
338
|
+
Effect.scoped,
|
|
339
|
+
Effect.withSpan(`sendAckWithRetry:ProxyChannelPayload`),
|
|
340
|
+
Effect.withParentSpan(channelSpan),
|
|
341
|
+
)
|
|
317
342
|
|
|
318
|
-
|
|
343
|
+
const listen = Stream.fromQueue(listenQueue).pipe(Stream.map(Either.right))
|
|
319
344
|
|
|
320
|
-
|
|
345
|
+
const closedDeferred = yield* Deferred.make<void>().pipe(Effect.acquireRelease(Deferred.done(Exit.void)))
|
|
321
346
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
347
|
+
const webChannel = {
|
|
348
|
+
[WebChannel.WebChannelSymbol]: WebChannel.WebChannelSymbol,
|
|
349
|
+
send,
|
|
350
|
+
listen,
|
|
351
|
+
closedDeferred,
|
|
352
|
+
supportsTransferables: true,
|
|
353
|
+
schema,
|
|
354
|
+
shutdown: Scope.close(scope, Exit.void),
|
|
355
|
+
debugInfo,
|
|
356
|
+
} satisfies WebChannel.WebChannel<any, any>
|
|
330
357
|
|
|
331
|
-
|
|
332
|
-
|
|
358
|
+
return webChannel as WebChannel.WebChannel<any, any>
|
|
359
|
+
}).pipe(Effect.withSpanScoped('makeProxyChannel')),
|
|
360
|
+
)
|
package/src/common.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Effect, Schema } from '@livestore/utils/effect'
|
|
1
|
+
import { type Effect, Predicate, Schema } from '@livestore/utils/effect'
|
|
2
2
|
|
|
3
3
|
import type { MessageChannelPacket, Packet, ProxyChannelPacket } from './mesh-schema.js'
|
|
4
4
|
|
|
@@ -15,22 +15,21 @@ export type MessageQueueItem = {
|
|
|
15
15
|
export type MeshNodeName = string
|
|
16
16
|
|
|
17
17
|
export type ChannelName = string
|
|
18
|
-
export type ChannelKey =
|
|
18
|
+
export type ChannelKey = `target:${MeshNodeName}, channelName:${ChannelName}`
|
|
19
19
|
|
|
20
20
|
// TODO actually use this to avoid timeouts in certain cases
|
|
21
|
-
export class NoConnectionRouteSignal extends Schema.TaggedError<NoConnectionRouteSignal>()(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
) {}
|
|
25
|
-
|
|
26
|
-
export class
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
target: Schema.String,
|
|
30
|
-
},
|
|
31
|
-
) {}
|
|
21
|
+
// export class NoConnectionRouteSignal extends Schema.TaggedError<NoConnectionRouteSignal>()(
|
|
22
|
+
// 'NoConnectionRouteSignal',
|
|
23
|
+
// {},
|
|
24
|
+
// ) {}
|
|
25
|
+
|
|
26
|
+
export class EdgeAlreadyExistsError extends Schema.TaggedError<EdgeAlreadyExistsError>()('EdgeAlreadyExistsError', {
|
|
27
|
+
target: Schema.String,
|
|
28
|
+
}) {}
|
|
32
29
|
|
|
33
30
|
export const packetAsOtelAttributes = (packet: typeof Packet.Type) => ({
|
|
34
31
|
packetId: packet.id,
|
|
32
|
+
'span.label':
|
|
33
|
+
packet.id + (Predicate.hasProperty(packet, 'reqId') && packet.reqId !== undefined ? ` for ${packet.reqId}` : ''),
|
|
35
34
|
...(packet._tag !== 'MessageChannelResponseSuccess' && packet._tag !== 'ProxyChannelPayload' ? { packet } : {}),
|
|
36
35
|
})
|