@livestore/webmesh 0.0.0-snapshot-1d99fea7d2ce2c7a5d9ed0a3752f8a7bda6bc3db → 0.0.0-snapshot-aed277ba0960f72b8d464508961ab4aec1881230
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 +19 -1
- 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 +202 -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 +125 -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 +39 -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 +256 -124
- 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 +337 -0
- package/src/channel/message-channel.ts +177 -308
- 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 +367 -150
- package/src/node.ts +68 -12
- package/src/websocket-connection.ts +83 -79
package/src/node.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LS_DEV, shouldNeverHappen } from '@livestore/utils'
|
|
1
|
+
import { indent, LS_DEV, shouldNeverHappen } from '@livestore/utils'
|
|
2
2
|
import type { Scope } from '@livestore/utils/effect'
|
|
3
3
|
import {
|
|
4
4
|
Cause,
|
|
@@ -29,6 +29,8 @@ export interface MeshNode {
|
|
|
29
29
|
|
|
30
30
|
debug: {
|
|
31
31
|
print: () => void
|
|
32
|
+
/** Sends a ping message to all connected nodes and channels */
|
|
33
|
+
ping: (payload?: string) => void
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
/**
|
|
@@ -58,7 +60,16 @@ export interface MeshNode {
|
|
|
58
60
|
/**
|
|
59
61
|
* Tries to broker a MessageChannel connection between the nodes, otherwise will proxy messages via hop-nodes
|
|
60
62
|
*
|
|
61
|
-
* For a channel to successfully open, both sides need to have a connection and call `makeChannel
|
|
63
|
+
* For a channel to successfully open, both sides need to have a connection and call `makeChannel`.
|
|
64
|
+
*
|
|
65
|
+
* Example:
|
|
66
|
+
* ```ts
|
|
67
|
+
* // Code on node A
|
|
68
|
+
* const channel = nodeA.makeChannel({ target: 'B', channelName: 'my-channel', schema: ... })
|
|
69
|
+
*
|
|
70
|
+
* // Code on node B
|
|
71
|
+
* const channel = nodeB.makeChannel({ target: 'A', channelName: 'my-channel', schema: ... })
|
|
72
|
+
* ```
|
|
62
73
|
*/
|
|
63
74
|
makeChannel: <MsgListen, MsgSend>(args: {
|
|
64
75
|
target: MeshNodeName
|
|
@@ -330,7 +341,12 @@ export const makeMeshNode = (nodeName: MeshNodeName): Effect.Effect<MeshNode, ne
|
|
|
330
341
|
const schema = WebChannel.mapSchema(inputSchema)
|
|
331
342
|
const channelKey = `${target}-${channelName}` satisfies ChannelKey
|
|
332
343
|
|
|
333
|
-
if (
|
|
344
|
+
if (channelMap.has(channelKey)) {
|
|
345
|
+
const existingChannel = channelMap.get(channelKey)!.debugInfo?.channel
|
|
346
|
+
if (existingChannel) {
|
|
347
|
+
shouldNeverHappen(`Channel ${channelKey} already exists`, existingChannel)
|
|
348
|
+
}
|
|
349
|
+
} else {
|
|
334
350
|
const queue = yield* Queue.unbounded<MessageQueueItem | ProxyQueueItem>().pipe(
|
|
335
351
|
Effect.acquireRelease(Queue.shutdown),
|
|
336
352
|
)
|
|
@@ -342,12 +358,23 @@ export const makeMeshNode = (nodeName: MeshNodeName): Effect.Effect<MeshNode, ne
|
|
|
342
358
|
yield* Effect.addFinalizer(() => Effect.sync(() => channelMap.delete(channelKey)))
|
|
343
359
|
|
|
344
360
|
if (mode === 'messagechannel') {
|
|
345
|
-
|
|
361
|
+
const incomingPacketsQueue = yield* Queue.unbounded<any>()
|
|
362
|
+
|
|
363
|
+
// We're we're draining the queue into another new queue.
|
|
364
|
+
// It's a bit of a mystery why this is needed, since the unit tests also work without it.
|
|
365
|
+
// But for the LiveStore devtools to actually work, we need to do this.
|
|
366
|
+
// We should figure out some day why this is needed and further simplify if possible.
|
|
367
|
+
yield* Queue.takeBetween(queue, 1, 10).pipe(
|
|
368
|
+
Effect.tap((_) => Queue.offerAll(incomingPacketsQueue, _)),
|
|
369
|
+
Effect.forever,
|
|
370
|
+
Effect.tapCauseLogPretty,
|
|
371
|
+
Effect.forkScoped,
|
|
372
|
+
)
|
|
346
373
|
|
|
347
374
|
// NOTE already retries internally when transferables are required
|
|
348
|
-
const
|
|
375
|
+
const { webChannel, initialConnectionDeferred } = yield* makeMessageChannel({
|
|
349
376
|
nodeName,
|
|
350
|
-
|
|
377
|
+
incomingPacketsQueue,
|
|
351
378
|
newConnectionAvailablePubSub,
|
|
352
379
|
target,
|
|
353
380
|
channelName,
|
|
@@ -356,9 +383,11 @@ export const makeMeshNode = (nodeName: MeshNodeName): Effect.Effect<MeshNode, ne
|
|
|
356
383
|
checkTransferableConnections,
|
|
357
384
|
})
|
|
358
385
|
|
|
359
|
-
channelMap.set(channelKey, { queue, debugInfo: { channel, target } })
|
|
386
|
+
channelMap.set(channelKey, { queue, debugInfo: { channel: webChannel, target } })
|
|
360
387
|
|
|
361
|
-
|
|
388
|
+
yield* initialConnectionDeferred
|
|
389
|
+
|
|
390
|
+
return webChannel
|
|
362
391
|
} else {
|
|
363
392
|
const channel = yield* makeProxyChannel({
|
|
364
393
|
nodeName,
|
|
@@ -375,6 +404,7 @@ export const makeMeshNode = (nodeName: MeshNodeName): Effect.Effect<MeshNode, ne
|
|
|
375
404
|
return channel
|
|
376
405
|
}
|
|
377
406
|
}).pipe(
|
|
407
|
+
// Effect.timeout(timeout),
|
|
378
408
|
Effect.withSpanScoped(`makeChannel:${nodeName}→${target}(${channelName})`, {
|
|
379
409
|
attributes: { target, channelName, mode, timeout },
|
|
380
410
|
}),
|
|
@@ -393,15 +423,41 @@ export const makeMeshNode = (nodeName: MeshNodeName): Effect.Effect<MeshNode, ne
|
|
|
393
423
|
console.log('Channels:', channelMap.size)
|
|
394
424
|
for (const [key, value] of channelMap) {
|
|
395
425
|
console.log(
|
|
396
|
-
|
|
397
|
-
` Queue: ${value.queue.unsafeSize().pipe(Option.getOrUndefined)}`,
|
|
398
|
-
value.queue,
|
|
426
|
+
indent(key, 2),
|
|
399
427
|
'\n',
|
|
400
|
-
|
|
428
|
+
Object.entries({
|
|
429
|
+
target: value.debugInfo?.target,
|
|
430
|
+
supportsTransferables: value.debugInfo?.channel.supportsTransferables,
|
|
431
|
+
...value.debugInfo?.channel.debugInfo,
|
|
432
|
+
})
|
|
433
|
+
.map(([key, value]) => indent(`${key}=${value}`, 4))
|
|
434
|
+
.join('\n'),
|
|
435
|
+
' ',
|
|
401
436
|
value.debugInfo?.channel,
|
|
437
|
+
'\n',
|
|
438
|
+
indent(`Queue: ${value.queue.unsafeSize().pipe(Option.getOrUndefined)}`, 4),
|
|
439
|
+
value.queue,
|
|
402
440
|
)
|
|
403
441
|
}
|
|
404
442
|
},
|
|
443
|
+
ping: (payload) => {
|
|
444
|
+
Effect.gen(function* () {
|
|
445
|
+
const msg = (via: string) => {
|
|
446
|
+
console.log(`sending message to ${via}`)
|
|
447
|
+
return WebChannel.DebugPingMessage.make({ message: `ping from ${nodeName} via ${via}`, payload })
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
yield* Effect.forEach(connectionChannels, ([channelName, con]) =>
|
|
451
|
+
con.channel.send(msg(`connection ${channelName}`) as any),
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
yield* Effect.forEach(
|
|
455
|
+
channelMap,
|
|
456
|
+
([channelKey, channel]) =>
|
|
457
|
+
channel.debugInfo?.channel.send(msg(`channel ${channelKey}`) as any) ?? Effect.void,
|
|
458
|
+
)
|
|
459
|
+
}).pipe(Effect.runFork)
|
|
460
|
+
},
|
|
405
461
|
}
|
|
406
462
|
|
|
407
463
|
return { nodeName, addConnection, removeConnection, makeChannel, connectionKeys, debug } satisfies MeshNode
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import type { Scope } from '@livestore/utils/effect'
|
|
2
1
|
import {
|
|
3
2
|
Deferred,
|
|
4
3
|
Effect,
|
|
5
4
|
Either,
|
|
5
|
+
Exit,
|
|
6
6
|
FiberHandle,
|
|
7
7
|
Queue,
|
|
8
8
|
Schedule,
|
|
9
9
|
Schema,
|
|
10
|
+
Scope,
|
|
10
11
|
Stream,
|
|
11
12
|
WebChannel,
|
|
12
13
|
WebSocket,
|
|
@@ -77,82 +78,85 @@ export const makeWebSocketConnection = (
|
|
|
77
78
|
never,
|
|
78
79
|
Scope.Scope
|
|
79
80
|
> =>
|
|
80
|
-
Effect.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
Stream.
|
|
87
|
-
|
|
88
|
-
|
|
81
|
+
Effect.scopeWithCloseable((scope) =>
|
|
82
|
+
Effect.gen(function* () {
|
|
83
|
+
socket.binaryType = 'arraybuffer'
|
|
84
|
+
|
|
85
|
+
const fromDeferred = yield* Deferred.make<string>()
|
|
86
|
+
|
|
87
|
+
yield* Stream.fromEventListener<MessageEvent>(socket as any, 'message').pipe(
|
|
88
|
+
Stream.map((msg) => Schema.decodeUnknownEither(MessageMsgPack)(new Uint8Array(msg.data))),
|
|
89
|
+
Stream.flatten(),
|
|
90
|
+
Stream.tap((msg) =>
|
|
91
|
+
Effect.gen(function* () {
|
|
92
|
+
if (msg._tag === 'WSConnectionInit') {
|
|
93
|
+
yield* Deferred.succeed(fromDeferred, msg.from)
|
|
94
|
+
} else {
|
|
95
|
+
const decodedPayload = yield* Schema.decode(MeshSchema.Packet)(msg.payload)
|
|
96
|
+
yield* Queue.offer(listenQueue, decodedPayload)
|
|
97
|
+
}
|
|
98
|
+
}),
|
|
99
|
+
),
|
|
100
|
+
Stream.runDrain,
|
|
101
|
+
Effect.tapCauseLogPretty,
|
|
102
|
+
Effect.forkScoped,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
const listenQueue = yield* Queue.unbounded<typeof MeshSchema.Packet.Type>().pipe(
|
|
106
|
+
Effect.acquireRelease(Queue.shutdown),
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
const initHandshake = (from: string) =>
|
|
110
|
+
socket.send(Schema.encodeSync(MessageMsgPack)({ _tag: 'WSConnectionInit', from }))
|
|
111
|
+
|
|
112
|
+
if (socketType._tag === 'leaf') {
|
|
113
|
+
initHandshake(socketType.from)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const deferredResult = yield* fromDeferred
|
|
117
|
+
const from = socketType._tag === 'leaf' ? socketType.from : deferredResult
|
|
118
|
+
|
|
119
|
+
if (socketType._tag === 'relay') {
|
|
120
|
+
initHandshake(from)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const isConnectedLatch = yield* Effect.makeLatch(true)
|
|
124
|
+
|
|
125
|
+
const closedDeferred = yield* Deferred.make<void>().pipe(Effect.acquireRelease(Deferred.done(Exit.void)))
|
|
126
|
+
|
|
127
|
+
yield* Effect.eventListener<any>(
|
|
128
|
+
socket,
|
|
129
|
+
'close',
|
|
130
|
+
() =>
|
|
131
|
+
Effect.gen(function* () {
|
|
132
|
+
yield* isConnectedLatch.close
|
|
133
|
+
yield* Deferred.succeed(closedDeferred, undefined)
|
|
134
|
+
}),
|
|
135
|
+
{ once: true },
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
const send = (message: typeof MeshSchema.Packet.Type) =>
|
|
89
139
|
Effect.gen(function* () {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const deferredResult = yield* fromDeferred
|
|
115
|
-
const from = socketType._tag === 'leaf' ? socketType.from : deferredResult
|
|
116
|
-
|
|
117
|
-
if (socketType._tag === 'relay') {
|
|
118
|
-
initHandshake(from)
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const isConnectedLatch = yield* Effect.makeLatch(true)
|
|
122
|
-
|
|
123
|
-
const closedDeferred = yield* Deferred.make<void>()
|
|
124
|
-
|
|
125
|
-
yield* Effect.eventListener<any>(
|
|
126
|
-
socket,
|
|
127
|
-
'close',
|
|
128
|
-
() =>
|
|
129
|
-
Effect.gen(function* () {
|
|
130
|
-
yield* isConnectedLatch.close
|
|
131
|
-
yield* Deferred.succeed(closedDeferred, undefined)
|
|
132
|
-
}),
|
|
133
|
-
{ once: true },
|
|
134
|
-
)
|
|
135
|
-
|
|
136
|
-
const send = (message: typeof MeshSchema.Packet.Type) =>
|
|
137
|
-
Effect.gen(function* () {
|
|
138
|
-
yield* isConnectedLatch.await
|
|
139
|
-
const payload = yield* Schema.encode(MeshSchema.Packet)(message)
|
|
140
|
-
socket.send(Schema.encodeSync(MessageMsgPack)({ _tag: 'WSConnectionPayload', payload, from }))
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
const listen = Stream.fromQueue(listenQueue).pipe(Stream.map(Either.right))
|
|
144
|
-
|
|
145
|
-
const webChannel = {
|
|
146
|
-
[WebChannel.WebChannelSymbol]: WebChannel.WebChannelSymbol,
|
|
147
|
-
send,
|
|
148
|
-
listen,
|
|
149
|
-
closedDeferred,
|
|
150
|
-
schema: { listen: MeshSchema.Packet, send: MeshSchema.Packet },
|
|
151
|
-
supportsTransferables: false,
|
|
152
|
-
} satisfies WebChannel.WebChannel<typeof MeshSchema.Packet.Type, typeof MeshSchema.Packet.Type>
|
|
153
|
-
|
|
154
|
-
return {
|
|
155
|
-
webChannel: webChannel as WebChannel.WebChannel<typeof MeshSchema.Packet.Type, typeof MeshSchema.Packet.Type>,
|
|
156
|
-
from,
|
|
157
|
-
}
|
|
158
|
-
})
|
|
140
|
+
yield* isConnectedLatch.await
|
|
141
|
+
const payload = yield* Schema.encode(MeshSchema.Packet)(message)
|
|
142
|
+
socket.send(Schema.encodeSync(MessageMsgPack)({ _tag: 'WSConnectionPayload', payload, from }))
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
const listen = Stream.fromQueue(listenQueue).pipe(Stream.map(Either.right))
|
|
146
|
+
|
|
147
|
+
const webChannel = {
|
|
148
|
+
[WebChannel.WebChannelSymbol]: WebChannel.WebChannelSymbol,
|
|
149
|
+
send,
|
|
150
|
+
listen,
|
|
151
|
+
closedDeferred,
|
|
152
|
+
schema: { listen: MeshSchema.Packet, send: MeshSchema.Packet },
|
|
153
|
+
supportsTransferables: false,
|
|
154
|
+
shutdown: Scope.close(scope, Exit.void),
|
|
155
|
+
} satisfies WebChannel.WebChannel<typeof MeshSchema.Packet.Type, typeof MeshSchema.Packet.Type>
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
webChannel: webChannel as WebChannel.WebChannel<typeof MeshSchema.Packet.Type, typeof MeshSchema.Packet.Type>,
|
|
159
|
+
from,
|
|
160
|
+
}
|
|
161
|
+
}).pipe(Effect.withSpanScoped('makeWebSocketConnection')),
|
|
162
|
+
)
|