@livestore/webmesh 0.3.0-dev.11 → 0.3.0-dev.13
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 +20 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/channel/message-channel copy.d.ts +9 -0
- package/dist/channel/message-channel copy.d.ts.map +1 -0
- package/dist/channel/message-channel copy.js +137 -0
- package/dist/channel/message-channel copy.js.map +1 -0
- package/dist/channel/message-channel-internal copy.d.ts +42 -0
- package/dist/channel/message-channel-internal copy.d.ts.map +1 -0
- package/dist/channel/message-channel-internal copy.js +239 -0
- package/dist/channel/message-channel-internal copy.js.map +1 -0
- 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 +2 -2
- package/dist/channel/proxy-channel.d.ts.map +1 -1
- package/dist/channel/proxy-channel.js +7 -5
- package/dist/channel/proxy-channel.js.map +1 -1
- package/dist/common.d.ts +8 -4
- package/dist/common.d.ts.map +1 -1
- package/dist/common.js +2 -1
- package/dist/common.js.map +1 -1
- package/dist/mesh-schema.d.ts +23 -1
- package/dist/mesh-schema.d.ts.map +1 -1
- package/dist/mesh-schema.js +21 -2
- package/dist/mesh-schema.js.map +1 -1
- package/dist/node.d.ts +12 -1
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +40 -9
- 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 +315 -149
- package/dist/node.test.js.map +1 -1
- package/dist/websocket-connection.d.ts +1 -2
- package/dist/websocket-connection.d.ts.map +1 -1
- package/dist/websocket-connection.js +5 -4
- package/dist/websocket-connection.js.map +1 -1
- package/package.json +3 -3
- package/src/channel/message-channel-internal.ts +356 -0
- package/src/channel/message-channel.ts +190 -310
- package/src/channel/proxy-channel.ts +238 -230
- package/src/common.ts +3 -1
- package/src/mesh-schema.ts +20 -2
- package/src/node.test.ts +448 -179
- package/src/node.ts +70 -12
- package/src/websocket-connection.ts +83 -79
|
@@ -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,
|
|
@@ -46,287 +48,293 @@ export const makeProxyChannel = ({
|
|
|
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
|
+
/**
|
|
74
|
+
* We need to unique identify a channel as multiple channels might exist between the same two nodes.
|
|
75
|
+
* We do this by letting each channel end generate a unique id and then combining them in a deterministic way.
|
|
76
|
+
*/
|
|
77
|
+
const channelIdCandidate = nanoid(5)
|
|
78
|
+
yield* Effect.annotateCurrentSpan({ channelIdCandidate })
|
|
109
79
|
|
|
110
|
-
|
|
111
|
-
Effect.gen(function* () {
|
|
112
|
-
// yield* Effect.log(`${nodeName}:processing packet ${packet._tag} from ${packet.source}`)
|
|
80
|
+
const channelSpan = yield* Effect.currentSpan.pipe(Effect.orDie)
|
|
113
81
|
|
|
114
|
-
|
|
115
|
-
const channelKey = `${otherSideName}-${packet.channelName}` satisfies ChannelKey
|
|
116
|
-
const channelState = channelStateRef.current
|
|
82
|
+
const connectedStateRef = yield* SubscriptionRef.make<ProxiedChannelStateEstablished | false>(false)
|
|
117
83
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const combinedChannelId = getCombinedChannelId(packet.channelIdCandidate)
|
|
84
|
+
const waitForEstablished = Effect.gen(function* () {
|
|
85
|
+
const state = yield* SubscriptionRef.waitUntil(connectedStateRef, (state) => state !== false)
|
|
121
86
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
channelStateRef.current = { _tag: 'Pending', initiatedVia: 'incoming-request' }
|
|
125
|
-
yield* Effect.spanEvent(`Reconnecting`).pipe(Effect.withParentSpan(channelSpan))
|
|
87
|
+
return state as ProxiedChannelStateEstablished
|
|
88
|
+
})
|
|
126
89
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
90
|
+
const setStateToEstablished = (channelId: string) =>
|
|
91
|
+
Effect.gen(function* () {
|
|
92
|
+
// TODO avoid "double" `Connected` events (we might call `setStateToEstablished` twice during initial connection)
|
|
93
|
+
yield* Effect.spanEvent(`Connected (${channelId})`).pipe(Effect.withParentSpan(channelSpan))
|
|
94
|
+
channelStateRef.current = {
|
|
95
|
+
_tag: 'Established',
|
|
96
|
+
listenSchema: schema.listen,
|
|
97
|
+
listenQueue,
|
|
98
|
+
ackMap,
|
|
99
|
+
combinedChannelId: channelId,
|
|
100
|
+
}
|
|
101
|
+
yield* SubscriptionRef.set(connectedStateRef, channelStateRef.current)
|
|
102
|
+
})
|
|
132
103
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
target,
|
|
139
|
-
source: nodeName,
|
|
140
|
-
channelName,
|
|
141
|
-
combinedChannelId,
|
|
142
|
-
channelIdCandidate,
|
|
143
|
-
}),
|
|
144
|
-
)
|
|
104
|
+
const connectionRequest = Effect.suspend(() =>
|
|
105
|
+
sendPacket(
|
|
106
|
+
MeshSchema.ProxyChannelRequest.make({ channelName, hops: [], source: nodeName, target, channelIdCandidate }),
|
|
107
|
+
),
|
|
108
|
+
)
|
|
145
109
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
110
|
+
const getCombinedChannelId = (otherSideChannelIdCandidate: string) =>
|
|
111
|
+
[channelIdCandidate, otherSideChannelIdCandidate].sort().join('_')
|
|
112
|
+
|
|
113
|
+
const processProxyPacket = ({ packet, respondToSender }: ProxyQueueItem) =>
|
|
114
|
+
Effect.gen(function* () {
|
|
115
|
+
// yield* Effect.log(`${nodeName}:processing packet ${packet._tag} from ${packet.source}`)
|
|
116
|
+
|
|
117
|
+
const otherSideName = packet.source
|
|
118
|
+
const channelKey = `${otherSideName}-${packet.channelName}` satisfies ChannelKey
|
|
119
|
+
const channelState = channelStateRef.current
|
|
120
|
+
|
|
121
|
+
switch (packet._tag) {
|
|
122
|
+
case 'ProxyChannelRequest': {
|
|
123
|
+
const combinedChannelId = getCombinedChannelId(packet.channelIdCandidate)
|
|
124
|
+
|
|
125
|
+
if (channelState._tag === 'Initial' || channelState._tag === 'Established') {
|
|
126
|
+
yield* SubscriptionRef.set(connectedStateRef, false)
|
|
127
|
+
channelStateRef.current = { _tag: 'Pending', initiatedVia: 'incoming-request' }
|
|
128
|
+
yield* Effect.spanEvent(`Reconnecting`).pipe(Effect.withParentSpan(channelSpan))
|
|
129
|
+
|
|
130
|
+
// If we're already connected, we need to re-establish the connection
|
|
131
|
+
if (channelState._tag === 'Established' && channelState.combinedChannelId !== combinedChannelId) {
|
|
132
|
+
yield* connectionRequest
|
|
133
|
+
}
|
|
157
134
|
}
|
|
158
|
-
}
|
|
159
135
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
136
|
+
yield* respondToSender(
|
|
137
|
+
MeshSchema.ProxyChannelResponseSuccess.make({
|
|
138
|
+
reqId: packet.id,
|
|
139
|
+
remainingHops: packet.hops,
|
|
140
|
+
hops: [],
|
|
141
|
+
target,
|
|
142
|
+
source: nodeName,
|
|
143
|
+
channelName,
|
|
144
|
+
combinedChannelId,
|
|
145
|
+
channelIdCandidate,
|
|
146
|
+
}),
|
|
164
147
|
)
|
|
148
|
+
|
|
149
|
+
return
|
|
165
150
|
}
|
|
151
|
+
case 'ProxyChannelResponseSuccess': {
|
|
152
|
+
if (channelState._tag !== 'Pending') {
|
|
153
|
+
// return shouldNeverHappen(`Expected proxy channel to be pending but got ${channelState._tag}`)
|
|
154
|
+
if (
|
|
155
|
+
channelState._tag === 'Established' &&
|
|
156
|
+
channelState.combinedChannelId !== packet.combinedChannelId
|
|
157
|
+
) {
|
|
158
|
+
return shouldNeverHappen(
|
|
159
|
+
`Expected proxy channel to have the same combinedChannelId as the packet:\n${channelState.combinedChannelId} (channel) === ${packet.combinedChannelId} (packet)`,
|
|
160
|
+
)
|
|
161
|
+
} else {
|
|
162
|
+
// for now just ignore it but should be looked into (there seems to be some kind of race condition/inefficiency)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
166
165
|
|
|
167
|
-
|
|
166
|
+
const combinedChannelId = getCombinedChannelId(packet.channelIdCandidate)
|
|
167
|
+
if (combinedChannelId !== packet.combinedChannelId) {
|
|
168
|
+
return yield* Effect.die(
|
|
169
|
+
`Expected proxy channel to have the same combinedChannelId as the packet:\n${combinedChannelId} (channel) === ${packet.combinedChannelId} (packet)`,
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
yield* setStateToEstablished(packet.combinedChannelId)
|
|
168
174
|
|
|
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
175
|
return
|
|
176
176
|
}
|
|
177
|
+
case 'ProxyChannelPayload': {
|
|
178
|
+
if (channelState._tag !== 'Established') {
|
|
179
|
+
// return yield* Effect.die(`Not yet connected to ${target}. dropping message`)
|
|
180
|
+
yield* Effect.spanEvent(`Not yet connected to ${target}. dropping message`, { packet })
|
|
181
|
+
return
|
|
182
|
+
}
|
|
177
183
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
184
|
+
if (channelState.combinedChannelId !== packet.combinedChannelId) {
|
|
185
|
+
return yield* Effect.die(
|
|
186
|
+
`Expected proxy channel to have the same combinedChannelId as the packet:\n${channelState.combinedChannelId} (channel) === ${packet.combinedChannelId} (packet)`,
|
|
187
|
+
)
|
|
188
|
+
}
|
|
183
189
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
190
|
+
yield* respondToSender(
|
|
191
|
+
MeshSchema.ProxyChannelPayloadAck.make({
|
|
192
|
+
reqId: packet.id,
|
|
193
|
+
remainingHops: packet.hops,
|
|
194
|
+
hops: [],
|
|
195
|
+
target,
|
|
196
|
+
source: nodeName,
|
|
197
|
+
channelName,
|
|
198
|
+
combinedChannelId: channelState.combinedChannelId,
|
|
199
|
+
}),
|
|
200
|
+
)
|
|
195
201
|
|
|
196
|
-
|
|
197
|
-
|
|
202
|
+
const decodedMessage = yield* Schema.decodeUnknown(channelState.listenSchema)(packet.payload)
|
|
203
|
+
yield* channelState.listenQueue.pipe(Queue.offer(decodedMessage))
|
|
198
204
|
|
|
199
|
-
return
|
|
200
|
-
}
|
|
201
|
-
case 'ProxyChannelPayloadAck': {
|
|
202
|
-
if (channelState._tag !== 'Established') {
|
|
203
|
-
yield* Effect.spanEvent(`Not yet connected to ${target}. dropping message`)
|
|
204
205
|
return
|
|
205
206
|
}
|
|
207
|
+
case 'ProxyChannelPayloadAck': {
|
|
208
|
+
if (channelState._tag !== 'Established') {
|
|
209
|
+
yield* Effect.spanEvent(`Not yet connected to ${target}. dropping message`)
|
|
210
|
+
return
|
|
211
|
+
}
|
|
206
212
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
213
|
+
const ack =
|
|
214
|
+
channelState.ackMap.get(packet.reqId) ??
|
|
215
|
+
shouldNeverHappen(`Expected ack for ${packet.reqId} in proxy channel ${channelKey}`)
|
|
210
216
|
|
|
211
|
-
|
|
217
|
+
yield* Deferred.succeed(ack, void 0)
|
|
212
218
|
|
|
213
|
-
|
|
219
|
+
channelState.ackMap.delete(packet.reqId)
|
|
214
220
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
default: {
|
|
224
|
+
return casesHandled(packet)
|
|
225
|
+
}
|
|
219
226
|
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
)
|
|
227
|
+
}).pipe(
|
|
228
|
+
Effect.withSpan(`handleProxyPacket:${packet._tag}:${packet.source}->${packet.target}`, {
|
|
229
|
+
attributes: packetAsOtelAttributes(packet),
|
|
230
|
+
}),
|
|
231
|
+
)
|
|
226
232
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
+
yield* Stream.fromQueue(queue).pipe(
|
|
234
|
+
Stream.tap(processProxyPacket),
|
|
235
|
+
Stream.runDrain,
|
|
236
|
+
Effect.tapCauseLogPretty,
|
|
237
|
+
Effect.forkScoped,
|
|
238
|
+
)
|
|
233
239
|
|
|
234
|
-
|
|
240
|
+
const listenQueue = yield* Queue.unbounded<any>()
|
|
235
241
|
|
|
236
|
-
|
|
242
|
+
yield* Effect.spanEvent(`Connecting`)
|
|
237
243
|
|
|
238
|
-
|
|
244
|
+
const ackMap = new Map<string, Deferred.Deferred<void, never>>()
|
|
239
245
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
246
|
+
// check if already established via incoming `ProxyChannelRequest` from other side
|
|
247
|
+
// which indicates we already have a connection to the target node
|
|
248
|
+
// const channelState = channelStateRef.current
|
|
249
|
+
{
|
|
250
|
+
if (channelStateRef.current._tag !== 'Initial') {
|
|
251
|
+
return shouldNeverHappen('Expected proxy channel to be Initial')
|
|
252
|
+
}
|
|
247
253
|
|
|
248
|
-
|
|
254
|
+
channelStateRef.current = { _tag: 'Pending', initiatedVia: 'outgoing-request' }
|
|
249
255
|
|
|
250
|
-
|
|
256
|
+
yield* connectionRequest
|
|
251
257
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
258
|
+
const retryOnNewConnectionFiber = yield* Stream.fromPubSub(newConnectionAvailablePubSub).pipe(
|
|
259
|
+
Stream.tap(() => connectionRequest),
|
|
260
|
+
Stream.runDrain,
|
|
261
|
+
Effect.forkScoped,
|
|
262
|
+
)
|
|
257
263
|
|
|
258
|
-
|
|
264
|
+
const { combinedChannelId: channelId } = yield* waitForEstablished
|
|
259
265
|
|
|
260
|
-
|
|
266
|
+
yield* Fiber.interrupt(retryOnNewConnectionFiber)
|
|
261
267
|
|
|
262
|
-
|
|
263
|
-
|
|
268
|
+
yield* setStateToEstablished(channelId)
|
|
269
|
+
}
|
|
264
270
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
271
|
+
const send = (message: any) =>
|
|
272
|
+
Effect.gen(function* () {
|
|
273
|
+
const payload = yield* Schema.encodeUnknown(schema.send)(message)
|
|
274
|
+
const sendFiberHandle = yield* FiberHandle.make<void, never>()
|
|
269
275
|
|
|
270
|
-
|
|
276
|
+
const sentDeferred = yield* Deferred.make<void>()
|
|
271
277
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
278
|
+
const trySend = Effect.gen(function* () {
|
|
279
|
+
const { combinedChannelId } = (yield* SubscriptionRef.waitUntil(
|
|
280
|
+
connectedStateRef,
|
|
281
|
+
(channel) => channel !== false,
|
|
282
|
+
)) as ProxiedChannelStateEstablished
|
|
277
283
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
284
|
+
const innerSend = Effect.gen(function* () {
|
|
285
|
+
// Note we're re-creating new packets every time otherwise they will be skipped because of `handledIds`
|
|
286
|
+
const ack = yield* Deferred.make<void, never>()
|
|
287
|
+
const packet = MeshSchema.ProxyChannelPayload.make({
|
|
288
|
+
channelName,
|
|
289
|
+
payload,
|
|
290
|
+
hops: [],
|
|
291
|
+
source: nodeName,
|
|
292
|
+
target,
|
|
293
|
+
combinedChannelId,
|
|
294
|
+
})
|
|
295
|
+
ackMap.set(packet.id, ack)
|
|
290
296
|
|
|
291
|
-
|
|
297
|
+
yield* sendPacket(packet)
|
|
292
298
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
299
|
+
yield* ack
|
|
300
|
+
yield* Deferred.succeed(sentDeferred, void 0)
|
|
301
|
+
})
|
|
296
302
|
|
|
297
|
-
|
|
298
|
-
|
|
303
|
+
yield* innerSend.pipe(Effect.timeout(100), Effect.retry(Schedule.exponential(100)), Effect.orDie)
|
|
304
|
+
}).pipe(Effect.tapErrorCause(Effect.logError))
|
|
299
305
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
+
const rerunOnNewChannelFiber = yield* connectedStateRef.changes.pipe(
|
|
307
|
+
Stream.filter((_) => _ === false),
|
|
308
|
+
Stream.tap(() => FiberHandle.run(sendFiberHandle, trySend)),
|
|
309
|
+
Stream.runDrain,
|
|
310
|
+
Effect.fork,
|
|
311
|
+
)
|
|
306
312
|
|
|
307
|
-
|
|
313
|
+
yield* FiberHandle.run(sendFiberHandle, trySend)
|
|
308
314
|
|
|
309
|
-
|
|
315
|
+
yield* sentDeferred
|
|
310
316
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
+
yield* Fiber.interrupt(rerunOnNewChannelFiber)
|
|
318
|
+
}).pipe(
|
|
319
|
+
Effect.scoped,
|
|
320
|
+
Effect.withSpan(`sendAckWithRetry:ProxyChannelPayload`),
|
|
321
|
+
Effect.withParentSpan(channelSpan),
|
|
322
|
+
)
|
|
317
323
|
|
|
318
|
-
|
|
324
|
+
const listen = Stream.fromQueue(listenQueue).pipe(Stream.map(Either.right))
|
|
319
325
|
|
|
320
|
-
|
|
326
|
+
const closedDeferred = yield* Deferred.make<void>().pipe(Effect.acquireRelease(Deferred.done(Exit.void)))
|
|
321
327
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
328
|
+
const webChannel = {
|
|
329
|
+
[WebChannel.WebChannelSymbol]: WebChannel.WebChannelSymbol,
|
|
330
|
+
send,
|
|
331
|
+
listen,
|
|
332
|
+
closedDeferred,
|
|
333
|
+
supportsTransferables: true,
|
|
334
|
+
schema,
|
|
335
|
+
shutdown: Scope.close(scope, Exit.void),
|
|
336
|
+
} satisfies WebChannel.WebChannel<any, any>
|
|
330
337
|
|
|
331
|
-
|
|
332
|
-
|
|
338
|
+
return webChannel as WebChannel.WebChannel<any, any>
|
|
339
|
+
}).pipe(Effect.withSpanScoped('makeProxyChannel')),
|
|
340
|
+
)
|
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
|
|
|
@@ -32,5 +32,7 @@ export class ConnectionAlreadyExistsError extends Schema.TaggedError<ConnectionA
|
|
|
32
32
|
|
|
33
33
|
export const packetAsOtelAttributes = (packet: typeof Packet.Type) => ({
|
|
34
34
|
packetId: packet.id,
|
|
35
|
+
'span.label':
|
|
36
|
+
packet.id + (Predicate.hasProperty(packet, 'reqId') && packet.reqId !== undefined ? ` for ${packet.reqId}` : ''),
|
|
35
37
|
...(packet._tag !== 'MessageChannelResponseSuccess' && packet._tag !== 'ProxyChannelPayload' ? { packet } : {}),
|
|
36
38
|
})
|
package/src/mesh-schema.ts
CHANGED
|
@@ -16,10 +16,24 @@ const defaultPacketFields = {
|
|
|
16
16
|
|
|
17
17
|
const remainingHopsUndefined = Schema.Undefined.pipe(Schema.optional)
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Needs to go through already existing MessageChannel connections, times out otherwise
|
|
21
|
+
*
|
|
22
|
+
* Can't yet contain the `port` because the request might be duplicated while forwarding to multiple nodes.
|
|
23
|
+
* We need a clear path back to the sender to avoid this, thus we respond with a separate
|
|
24
|
+
* `MessageChannelResponseSuccess` which contains the `port`.
|
|
25
|
+
*/
|
|
20
26
|
export class MessageChannelRequest extends Schema.TaggedStruct('MessageChannelRequest', {
|
|
21
27
|
...defaultPacketFields,
|
|
22
|
-
remainingHops:
|
|
28
|
+
remainingHops: Schema.Array(Schema.String).pipe(Schema.optional),
|
|
29
|
+
channelVersion: Schema.Number,
|
|
30
|
+
/** Only set if the request is in response to an incoming request */
|
|
31
|
+
reqId: Schema.UndefinedOr(Schema.String),
|
|
32
|
+
/**
|
|
33
|
+
* Additionally to the `source` field, we use this field to track whether the instance of a
|
|
34
|
+
* source has changed.
|
|
35
|
+
*/
|
|
36
|
+
sourceId: Schema.String,
|
|
23
37
|
}) {}
|
|
24
38
|
|
|
25
39
|
export class MessageChannelResponseSuccess extends Schema.TaggedStruct('MessageChannelResponseSuccess', {
|
|
@@ -28,6 +42,7 @@ export class MessageChannelResponseSuccess extends Schema.TaggedStruct('MessageC
|
|
|
28
42
|
port: Transferable.MessagePort,
|
|
29
43
|
// Since we can't copy this message, we need to follow the exact route back to the sender
|
|
30
44
|
remainingHops: Schema.Array(Schema.String),
|
|
45
|
+
channelVersion: Schema.Number,
|
|
31
46
|
}) {}
|
|
32
47
|
|
|
33
48
|
export class MessageChannelResponseNoTransferables extends Schema.TaggedStruct(
|
|
@@ -92,3 +107,6 @@ export class ProxyChannelPacket extends Schema.Union(
|
|
|
92
107
|
) {}
|
|
93
108
|
|
|
94
109
|
export class Packet extends Schema.Union(MessageChannelPacket, ProxyChannelPacket, NetworkConnectionAdded) {}
|
|
110
|
+
|
|
111
|
+
export class MessageChannelPing extends Schema.TaggedStruct('MessageChannelPing', {}) {}
|
|
112
|
+
export class MessageChannelPong extends Schema.TaggedStruct('MessageChannelPong', {}) {}
|