@livestore/webmesh 0.4.0-dev.9 → 0.5.0-dev.0
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 +5 -3
- package/dist/.tsbuildinfo +1 -1
- package/dist/channel/direct-channel-internal.d.ts +2 -3
- package/dist/channel/direct-channel-internal.d.ts.map +1 -1
- package/dist/channel/direct-channel-internal.js +35 -38
- package/dist/channel/direct-channel-internal.js.map +1 -1
- package/dist/channel/direct-channel.d.ts.map +1 -1
- package/dist/channel/direct-channel.js +22 -23
- package/dist/channel/direct-channel.js.map +1 -1
- package/dist/channel/proxy-channel.d.ts +29 -5
- package/dist/channel/proxy-channel.d.ts.map +1 -1
- package/dist/channel/proxy-channel.js +87 -37
- package/dist/channel/proxy-channel.js.map +1 -1
- package/dist/common.d.ts +33 -35
- package/dist/common.d.ts.map +1 -1
- package/dist/common.js +7 -7
- package/dist/common.js.map +1 -1
- package/dist/mesh-schema.d.ts +262 -136
- package/dist/mesh-schema.d.ts.map +1 -1
- package/dist/mesh-schema.js +45 -44
- package/dist/mesh-schema.js.map +1 -1
- package/dist/node.d.ts +12 -6
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +47 -44
- package/dist/node.js.map +1 -1
- package/dist/node.test.js +253 -63
- package/dist/node.test.js.map +1 -1
- package/dist/utils.d.ts +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js.map +1 -1
- package/dist/websocket-edge.d.ts +20 -18
- package/dist/websocket-edge.d.ts.map +1 -1
- package/dist/websocket-edge.js +33 -41
- package/dist/websocket-edge.js.map +1 -1
- package/dist/websocket-edge.test.d.ts +7 -0
- package/dist/websocket-edge.test.d.ts.map +1 -0
- package/dist/websocket-edge.test.js +74 -0
- package/dist/websocket-edge.test.js.map +1 -0
- package/dist/worker/mod.d.ts +24 -0
- package/dist/worker/mod.d.ts.map +1 -0
- package/dist/worker/mod.js +44 -0
- package/dist/worker/mod.js.map +1 -0
- package/dist/worker/schema.d.ts +10 -0
- package/dist/worker/schema.d.ts.map +1 -0
- package/dist/worker/schema.js +7 -0
- package/dist/worker/schema.js.map +1 -0
- package/package.json +48 -12
- package/src/channel/direct-channel-internal.ts +41 -47
- package/src/channel/direct-channel.ts +28 -25
- package/src/channel/proxy-channel.ts +120 -57
- package/src/common.ts +9 -7
- package/src/mesh-schema.ts +34 -33
- package/src/node.test.ts +356 -80
- package/src/node.ts +63 -55
- package/src/utils.ts +3 -3
- package/src/websocket-edge.test.ts +98 -0
- package/src/websocket-edge.ts +52 -48
- package/src/worker/mod.ts +89 -0
- package/src/worker/schema.ts +8 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { casesHandled, shouldNeverHappen } from '@livestore/utils'
|
|
2
|
-
import type { PubSub } from '@livestore/utils/effect'
|
|
3
2
|
import {
|
|
3
|
+
type PubSub,
|
|
4
4
|
Deferred,
|
|
5
5
|
Effect,
|
|
6
|
-
|
|
6
|
+
Result,
|
|
7
7
|
Exit,
|
|
8
8
|
Fiber,
|
|
9
9
|
FiberHandle,
|
|
@@ -26,6 +26,38 @@ import {
|
|
|
26
26
|
} from '../common.ts'
|
|
27
27
|
import * as MeshSchema from '../mesh-schema.ts'
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Simulation parameters for proxy channel operations.
|
|
31
|
+
* Used for testing race conditions and timing-sensitive behavior.
|
|
32
|
+
*
|
|
33
|
+
* Each parameter represents a delay (in ms) injected at a specific point in the code.
|
|
34
|
+
* Values are bounded 0-500ms to prevent tests from running too long.
|
|
35
|
+
*/
|
|
36
|
+
export const ProxyChannelSimulationParams = Schema.Struct({
|
|
37
|
+
/**
|
|
38
|
+
* Delays related to receiving and processing payload messages
|
|
39
|
+
*/
|
|
40
|
+
onPayload: Schema.Struct({
|
|
41
|
+
/** Delay before sending the ACK response (simulates slow ACK send) */
|
|
42
|
+
beforeAckSend: Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 500 })),
|
|
43
|
+
/** Delay after forking the ACK send, before adding message to listen queue */
|
|
44
|
+
afterAckFork: Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 500 })),
|
|
45
|
+
/** Delay after adding message to listen queue */
|
|
46
|
+
afterListenQueueOffer: Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 500 })),
|
|
47
|
+
}),
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
export type ProxyChannelSimulationParams = typeof ProxyChannelSimulationParams.Type
|
|
51
|
+
|
|
52
|
+
/** Default simulation params with no delays */
|
|
53
|
+
export const defaultSimulationParams: ProxyChannelSimulationParams = {
|
|
54
|
+
onPayload: {
|
|
55
|
+
beforeAckSend: 0,
|
|
56
|
+
afterAckFork: 0,
|
|
57
|
+
afterListenQueueOffer: 0,
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
|
|
29
61
|
interface MakeProxyChannelArgs {
|
|
30
62
|
queue: Queue.Queue<ProxyQueueItem>
|
|
31
63
|
nodeName: MeshNodeName
|
|
@@ -34,9 +66,11 @@ interface MakeProxyChannelArgs {
|
|
|
34
66
|
channelName: ChannelName
|
|
35
67
|
target: MeshNodeName
|
|
36
68
|
schema: {
|
|
37
|
-
send: Schema.
|
|
38
|
-
listen: Schema.
|
|
69
|
+
send: Schema.Codec<any, any>
|
|
70
|
+
listen: Schema.Codec<any, any>
|
|
39
71
|
}
|
|
72
|
+
/** Optional simulation parameters for testing timing-sensitive behavior */
|
|
73
|
+
simulation?: ProxyChannelSimulationParams
|
|
40
74
|
}
|
|
41
75
|
|
|
42
76
|
export const makeProxyChannel = ({
|
|
@@ -47,9 +81,18 @@ export const makeProxyChannel = ({
|
|
|
47
81
|
target,
|
|
48
82
|
channelName,
|
|
49
83
|
schema,
|
|
84
|
+
simulation = defaultSimulationParams,
|
|
50
85
|
}: MakeProxyChannelArgs) =>
|
|
51
86
|
Effect.scopeWithCloseable((scope) =>
|
|
52
87
|
Effect.gen(function* () {
|
|
88
|
+
/** Helper to inject simulation delays at specific code points */
|
|
89
|
+
const simSleep = <TKey extends keyof ProxyChannelSimulationParams>(
|
|
90
|
+
key: TKey,
|
|
91
|
+
key2: keyof ProxyChannelSimulationParams[TKey],
|
|
92
|
+
) => {
|
|
93
|
+
const delay = (simulation[key]?.[key2] ?? 0) as number
|
|
94
|
+
return delay > 0 ? Effect.sleep(delay) : Effect.void
|
|
95
|
+
}
|
|
53
96
|
type ProxiedChannelState =
|
|
54
97
|
| {
|
|
55
98
|
_tag: 'Initial'
|
|
@@ -62,9 +105,9 @@ export const makeProxyChannel = ({
|
|
|
62
105
|
|
|
63
106
|
type ProxiedChannelStateEstablished = {
|
|
64
107
|
_tag: 'Established'
|
|
65
|
-
listenSchema: Schema.
|
|
108
|
+
listenSchema: Schema.Codec<any, any>
|
|
66
109
|
listenQueue: Queue.Queue<any>
|
|
67
|
-
ackMap: Map<string, Deferred.Deferred<void
|
|
110
|
+
ackMap: Map<string, Deferred.Deferred<void>>
|
|
68
111
|
combinedChannelId: string
|
|
69
112
|
}
|
|
70
113
|
|
|
@@ -88,6 +131,10 @@ export const makeProxyChannel = ({
|
|
|
88
131
|
const channelSpan = yield* Effect.currentSpan.pipe(Effect.orDie)
|
|
89
132
|
|
|
90
133
|
const connectedStateRef = yield* SubscriptionRef.make<ProxiedChannelStateEstablished | false>(false)
|
|
134
|
+
// Incoming packets can be processed as soon as the queue-draining fiber starts, so state
|
|
135
|
+
// referenced by established channels must exist before that processor is forked.
|
|
136
|
+
const listenQueue = yield* Queue.unbounded<any>()
|
|
137
|
+
const ackMap = new Map<string, Deferred.Deferred<void>>()
|
|
91
138
|
|
|
92
139
|
const waitForEstablished = Effect.gen(function* () {
|
|
93
140
|
const state = yield* SubscriptionRef.waitUntil(connectedStateRef, (state) => state !== false)
|
|
@@ -117,10 +164,11 @@ export const makeProxyChannel = ({
|
|
|
117
164
|
)
|
|
118
165
|
|
|
119
166
|
const getCombinedChannelId = (otherSideChannelIdCandidate: string) =>
|
|
120
|
-
[channelIdCandidate, otherSideChannelIdCandidate].
|
|
167
|
+
[channelIdCandidate, otherSideChannelIdCandidate].toSorted().join('_')
|
|
121
168
|
|
|
122
|
-
const earlyPayloadBuffer = yield*
|
|
123
|
-
|
|
169
|
+
const earlyPayloadBuffer = yield* Effect.acquireRelease(
|
|
170
|
+
Queue.unbounded<typeof MeshSchema.ProxyChannelPayload.Type>(),
|
|
171
|
+
Queue.shutdown,
|
|
124
172
|
)
|
|
125
173
|
|
|
126
174
|
const processProxyPacket = ({ packet, respondToSender }: ProxyQueueItem) =>
|
|
@@ -224,7 +272,7 @@ export const makeProxyChannel = ({
|
|
|
224
272
|
const establishedState = channelStateRef.current
|
|
225
273
|
if (establishedState._tag === 'Established') {
|
|
226
274
|
//
|
|
227
|
-
const bufferedPackets = yield* Queue.
|
|
275
|
+
const bufferedPackets = yield* Queue.clear(earlyPayloadBuffer)
|
|
228
276
|
// yield* Effect.logDebug(
|
|
229
277
|
// `[${nodeName}] Draining early payload buffer (${bufferedPackets.length}) after ResponseSuccess`,
|
|
230
278
|
// )
|
|
@@ -235,10 +283,10 @@ export const makeProxyChannel = ({
|
|
|
235
283
|
)
|
|
236
284
|
continue
|
|
237
285
|
}
|
|
238
|
-
const decodedMessage = yield* Schema.
|
|
286
|
+
const decodedMessage = yield* Schema.decodeUnknownEffect(establishedState.listenSchema)(
|
|
239
287
|
bufferedPacket.payload,
|
|
240
288
|
)
|
|
241
|
-
yield*
|
|
289
|
+
yield* Queue.offer(establishedState.listenQueue, decodedMessage)
|
|
242
290
|
}
|
|
243
291
|
} else {
|
|
244
292
|
yield* Effect.logError(
|
|
@@ -255,34 +303,46 @@ export const makeProxyChannel = ({
|
|
|
255
303
|
)
|
|
256
304
|
}
|
|
257
305
|
|
|
258
|
-
//
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
306
|
+
// Send ACK fire-and-forget to avoid blocking message processing
|
|
307
|
+
// This is critical because blocking ACK sends can prevent messages from reaching the listen queue
|
|
308
|
+
// See test: "ACK forkScoped regression tests" for documentation
|
|
309
|
+
|
|
310
|
+
// The ACK send effect with optional simulation delay INSIDE the fork
|
|
311
|
+
// This is key for testing: with forkScoped, the delay happens in background and doesn't block message processing
|
|
312
|
+
// Without forkScoped (blocking), the delay would block the message from being added to listen queue
|
|
313
|
+
const ackSendEffect = Effect.gen(function* () {
|
|
314
|
+
yield* simSleep('onPayload', 'beforeAckSend')
|
|
315
|
+
yield* respondToSender(
|
|
316
|
+
MeshSchema.ProxyChannelPayloadAck.make({
|
|
317
|
+
reqId: packet.id,
|
|
318
|
+
remainingHops: packet.hops,
|
|
319
|
+
hops: [],
|
|
320
|
+
target,
|
|
321
|
+
source: nodeName,
|
|
322
|
+
channelName,
|
|
323
|
+
combinedChannelId:
|
|
324
|
+
channelState._tag === 'Established' ? channelState.combinedChannelId : packet.combinedChannelId,
|
|
325
|
+
}),
|
|
326
|
+
)
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
yield* ackSendEffect.pipe(Effect.tapCauseLogPretty, Effect.forkScoped)
|
|
330
|
+
|
|
331
|
+
// Simulation point: delay after forking ACK (before processing message)
|
|
332
|
+
yield* simSleep('onPayload', 'afterAckFork')
|
|
271
333
|
|
|
272
334
|
if (channelState._tag === 'Established') {
|
|
273
|
-
const decodedMessage = yield* Schema.
|
|
274
|
-
yield*
|
|
335
|
+
const decodedMessage = yield* Schema.decodeUnknownEffect(channelState.listenSchema)(packet.payload)
|
|
336
|
+
yield* Queue.offer(channelState.listenQueue, decodedMessage)
|
|
337
|
+
|
|
338
|
+
// Simulation point: delay after adding to listen queue
|
|
339
|
+
yield* simSleep('onPayload', 'afterListenQueueOffer')
|
|
275
340
|
} else {
|
|
276
|
-
// yield* Effect.logDebug(
|
|
277
|
-
// `[${nodeName}] Buffering early payload reqId: ${packet.id} (state: ${channelState._tag})`,
|
|
278
|
-
// )
|
|
279
341
|
yield* Queue.offer(earlyPayloadBuffer, packet)
|
|
280
342
|
}
|
|
281
343
|
return
|
|
282
344
|
}
|
|
283
345
|
case 'ProxyChannelPayloadAck': {
|
|
284
|
-
// yield* Effect.logDebug(`[${nodeName}] Received Ack for reqId: ${packet.reqId}`)
|
|
285
|
-
|
|
286
346
|
if (channelState._tag !== 'Established') {
|
|
287
347
|
yield* Effect.spanEvent(`Not yet connected to ${target}. dropping message`)
|
|
288
348
|
yield* Effect.logWarning(
|
|
@@ -291,9 +351,14 @@ export const makeProxyChannel = ({
|
|
|
291
351
|
return
|
|
292
352
|
}
|
|
293
353
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
354
|
+
// Handle missing ACK gracefully - can happen with synthetic ACKs from relay or duplicate ACKs
|
|
355
|
+
const ack = channelState.ackMap.get(packet.reqId)
|
|
356
|
+
if (ack === undefined) {
|
|
357
|
+
yield* Effect.logDebug(
|
|
358
|
+
`Received ACK for unknown reqId: ${packet.reqId} (may be synthetic or duplicate)`,
|
|
359
|
+
)
|
|
360
|
+
return
|
|
361
|
+
}
|
|
297
362
|
yield* Deferred.succeed(ack, void 0)
|
|
298
363
|
|
|
299
364
|
channelState.ackMap.delete(packet.reqId)
|
|
@@ -310,19 +375,8 @@ export const makeProxyChannel = ({
|
|
|
310
375
|
}),
|
|
311
376
|
)
|
|
312
377
|
|
|
313
|
-
yield* Stream.fromQueue(queue).pipe(
|
|
314
|
-
Stream.tap(processProxyPacket),
|
|
315
|
-
Stream.runDrain,
|
|
316
|
-
Effect.tapCauseLogPretty,
|
|
317
|
-
Effect.forkScoped,
|
|
318
|
-
)
|
|
319
|
-
|
|
320
|
-
const listenQueue = yield* Queue.unbounded<any>()
|
|
321
|
-
|
|
322
378
|
yield* Effect.spanEvent(`Connecting`)
|
|
323
379
|
|
|
324
|
-
const ackMap = new Map<string, Deferred.Deferred<void, never>>()
|
|
325
|
-
|
|
326
380
|
// check if already established via incoming `ProxyChannelRequest` from other side
|
|
327
381
|
// which indicates we already have a edge to the target node
|
|
328
382
|
// const channelState = channelStateRef.current
|
|
@@ -341,6 +395,15 @@ export const makeProxyChannel = ({
|
|
|
341
395
|
Effect.forkScoped,
|
|
342
396
|
)
|
|
343
397
|
|
|
398
|
+
// Drain queued proxy packets only after the local side entered `Pending` and sent its
|
|
399
|
+
// request. Otherwise an already-buffered response can race initialization.
|
|
400
|
+
yield* Stream.fromQueue(queue).pipe(
|
|
401
|
+
Stream.tap(processProxyPacket),
|
|
402
|
+
Stream.runDrain,
|
|
403
|
+
Effect.tapCauseLogPretty,
|
|
404
|
+
Effect.forkScoped,
|
|
405
|
+
)
|
|
406
|
+
|
|
344
407
|
const { combinedChannelId: channelId } = yield* waitForEstablished
|
|
345
408
|
|
|
346
409
|
yield* Fiber.interrupt(retryOnNewEdgeFiber)
|
|
@@ -350,7 +413,7 @@ export const makeProxyChannel = ({
|
|
|
350
413
|
|
|
351
414
|
const send = (message: any) =>
|
|
352
415
|
Effect.gen(function* () {
|
|
353
|
-
const payload = yield* Schema.
|
|
416
|
+
const payload = yield* Schema.encodeUnknownEffect(schema.send)(message)
|
|
354
417
|
const sendFiberHandle = yield* FiberHandle.make<void, never>()
|
|
355
418
|
|
|
356
419
|
const sentDeferred = yield* Deferred.make<void>()
|
|
@@ -366,7 +429,7 @@ export const makeProxyChannel = ({
|
|
|
366
429
|
|
|
367
430
|
const innerSend = Effect.gen(function* () {
|
|
368
431
|
// Note we're re-creating new packets every time otherwise they will be skipped because of `handledIds`
|
|
369
|
-
const ack = yield* Deferred.make<void
|
|
432
|
+
const ack = yield* Deferred.make<void>()
|
|
370
433
|
const packet = MeshSchema.ProxyChannelPayload.make({
|
|
371
434
|
channelName,
|
|
372
435
|
payload,
|
|
@@ -381,7 +444,7 @@ export const makeProxyChannel = ({
|
|
|
381
444
|
|
|
382
445
|
yield* sendPacket(packet)
|
|
383
446
|
|
|
384
|
-
yield* ack
|
|
447
|
+
yield* Deferred.await(ack)
|
|
385
448
|
yield* Deferred.succeed(sentDeferred, void 0)
|
|
386
449
|
|
|
387
450
|
debugInfo.pendingSends--
|
|
@@ -390,18 +453,18 @@ export const makeProxyChannel = ({
|
|
|
390
453
|
// TODO make this configurable
|
|
391
454
|
// Schedule.exponential(10): 10, 20, 40, 80, 160, 320, ...
|
|
392
455
|
yield* innerSend.pipe(Effect.timeout(100), Effect.retry(Schedule.exponential(10)), Effect.orDie)
|
|
393
|
-
}).pipe(Effect.
|
|
456
|
+
}).pipe(Effect.tapCause(Effect.logError))
|
|
394
457
|
|
|
395
|
-
const rerunOnNewChannelFiber = yield*
|
|
458
|
+
const rerunOnNewChannelFiber = yield* SubscriptionRef.changes(connectedStateRef).pipe(
|
|
396
459
|
Stream.filter((_) => _ === false),
|
|
397
460
|
Stream.tap(() => FiberHandle.run(sendFiberHandle, trySend)),
|
|
398
461
|
Stream.runDrain,
|
|
399
|
-
Effect.
|
|
462
|
+
Effect.forkChild,
|
|
400
463
|
)
|
|
401
464
|
|
|
402
465
|
yield* FiberHandle.run(sendFiberHandle, trySend)
|
|
403
466
|
|
|
404
|
-
yield* sentDeferred
|
|
467
|
+
yield* Deferred.await(sentDeferred)
|
|
405
468
|
|
|
406
469
|
yield* Fiber.interrupt(rerunOnNewChannelFiber)
|
|
407
470
|
}).pipe(
|
|
@@ -410,11 +473,11 @@ export const makeProxyChannel = ({
|
|
|
410
473
|
Effect.withParentSpan(channelSpan),
|
|
411
474
|
)
|
|
412
475
|
|
|
413
|
-
const listen = Stream.fromQueue(listenQueue).pipe(Stream.map(
|
|
476
|
+
const listen = Stream.fromQueue(listenQueue).pipe(Stream.map(Result.succeed))
|
|
414
477
|
|
|
415
|
-
const closedDeferred = yield* Deferred.make<void>()
|
|
478
|
+
const closedDeferred = yield* Effect.acquireRelease(Deferred.make<void>(), Deferred.done(Exit.void))
|
|
416
479
|
|
|
417
|
-
const
|
|
480
|
+
const services = yield* Effect.context()
|
|
418
481
|
|
|
419
482
|
const webChannel = {
|
|
420
483
|
[WebChannel.WebChannelSymbol]: WebChannel.WebChannelSymbol,
|
|
@@ -429,7 +492,7 @@ export const makeProxyChannel = ({
|
|
|
429
492
|
debug: {
|
|
430
493
|
ping: (message = 'ping') =>
|
|
431
494
|
send(WebChannel.DebugPingMessage.make({ message })).pipe(
|
|
432
|
-
Effect.provide(
|
|
495
|
+
Effect.provide(services),
|
|
433
496
|
Effect.tapCauseLogPretty,
|
|
434
497
|
Effect.runFork,
|
|
435
498
|
),
|
package/src/common.ts
CHANGED
|
@@ -19,19 +19,21 @@ export type ChannelName = string
|
|
|
19
19
|
export type ChannelKey = `target:${MeshNodeName}, channelName:${ChannelName}`
|
|
20
20
|
|
|
21
21
|
// TODO actually use this to avoid timeouts in certain cases
|
|
22
|
-
// export class NoConnectionRouteSignal extends Schema.TaggedError<NoConnectionRouteSignal>(
|
|
23
|
-
// 'NoConnectionRouteSignal',
|
|
24
|
-
//
|
|
25
|
-
// ) {}
|
|
22
|
+
// export class NoConnectionRouteSignal extends Schema.TaggedError<NoConnectionRouteSignal>(
|
|
23
|
+
// '~@livestore/webmesh/NoConnectionRouteSignal',
|
|
24
|
+
// )('NoConnectionRouteSignal', {}) {}
|
|
26
25
|
|
|
27
|
-
export class EdgeAlreadyExistsError extends Schema.TaggedError<EdgeAlreadyExistsError>(
|
|
26
|
+
export class EdgeAlreadyExistsError extends Schema.TaggedError<EdgeAlreadyExistsError>(
|
|
27
|
+
'~@livestore/webmesh/EdgeAlreadyExistsError',
|
|
28
|
+
)('EdgeAlreadyExistsError', {
|
|
28
29
|
target: Schema.String,
|
|
29
30
|
}) {}
|
|
30
31
|
|
|
31
32
|
export const packetAsOtelAttributes = (packet: typeof Packet.Type) => ({
|
|
32
33
|
packetId: packet.id,
|
|
33
34
|
'span.label':
|
|
34
|
-
packet.id +
|
|
35
|
+
packet.id +
|
|
36
|
+
(Predicate.hasProperty(packet, 'reqId') === true && packet.reqId !== undefined ? ` for ${packet.reqId}` : ''),
|
|
35
37
|
...omitUndefineds({
|
|
36
38
|
packet:
|
|
37
39
|
packet._tag !== 'DirectChannelResponseSuccess' && packet._tag !== 'ProxyChannelPayload' ? packet : undefined,
|
|
@@ -41,7 +43,7 @@ export const packetAsOtelAttributes = (packet: typeof Packet.Type) => ({
|
|
|
41
43
|
export const ListenForChannelResult = Schema.Struct({
|
|
42
44
|
channelName: Schema.String,
|
|
43
45
|
source: Schema.String,
|
|
44
|
-
mode: Schema.
|
|
46
|
+
mode: Schema.Literals(['proxy', 'direct']),
|
|
45
47
|
})
|
|
46
48
|
|
|
47
49
|
export type ListenForChannelResult = typeof ListenForChannelResult.Type
|
package/src/mesh-schema.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { Schema, Transferable } from '@livestore/utils/effect'
|
|
1
|
+
import { Effect, Schema, Transferable } from '@livestore/utils/effect'
|
|
2
2
|
import { nanoid } from '@livestore/utils/nanoid'
|
|
3
3
|
|
|
4
4
|
const id = Schema.String.pipe(
|
|
5
|
-
Schema.
|
|
6
|
-
Schema.
|
|
5
|
+
Schema.withDecodingDefaultType(Effect.sync(() => nanoid(10))),
|
|
6
|
+
Schema.withConstructorDefault(Effect.sync(() => nanoid(10))),
|
|
7
7
|
)
|
|
8
8
|
|
|
9
9
|
const defaultPacketFields = {
|
|
@@ -23,10 +23,10 @@ const remainingHopsUndefined = Schema.Undefined.pipe(Schema.optional)
|
|
|
23
23
|
* We need a clear path back to the sender to avoid this, thus we respond with a separate
|
|
24
24
|
* `DirectChannelResponseSuccess` which contains the `port`.
|
|
25
25
|
*/
|
|
26
|
-
export
|
|
26
|
+
export const DirectChannelRequest = Schema.TaggedStruct('DirectChannelRequest', {
|
|
27
27
|
...defaultPacketFields,
|
|
28
28
|
remainingHops: Schema.Array(Schema.String).pipe(Schema.optional),
|
|
29
|
-
channelVersion: Schema.
|
|
29
|
+
channelVersion: Schema.Finite,
|
|
30
30
|
/** Only set if the request is in response to an incoming request */
|
|
31
31
|
reqId: Schema.UndefinedOr(Schema.String),
|
|
32
32
|
/**
|
|
@@ -34,70 +34,70 @@ export class DirectChannelRequest extends Schema.TaggedStruct('DirectChannelRequ
|
|
|
34
34
|
* source has changed.
|
|
35
35
|
*/
|
|
36
36
|
sourceId: Schema.String,
|
|
37
|
-
})
|
|
37
|
+
})
|
|
38
38
|
|
|
39
|
-
export
|
|
39
|
+
export const DirectChannelResponseSuccess = Schema.TaggedStruct('DirectChannelResponseSuccess', {
|
|
40
40
|
...defaultPacketFields,
|
|
41
41
|
reqId: Schema.String,
|
|
42
42
|
port: Transferable.MessagePort,
|
|
43
43
|
// Since we can't copy this message, we need to follow the exact route back to the sender
|
|
44
44
|
remainingHops: Schema.Array(Schema.String),
|
|
45
|
-
channelVersion: Schema.
|
|
46
|
-
})
|
|
45
|
+
channelVersion: Schema.Finite,
|
|
46
|
+
})
|
|
47
47
|
|
|
48
|
-
export
|
|
48
|
+
export const DirectChannelResponseNoTransferables = Schema.TaggedStruct('DirectChannelResponseNoTransferables', {
|
|
49
49
|
...defaultPacketFields,
|
|
50
50
|
reqId: Schema.String,
|
|
51
51
|
remainingHops: Schema.Array(Schema.String),
|
|
52
|
-
})
|
|
52
|
+
})
|
|
53
53
|
|
|
54
|
-
export
|
|
54
|
+
export const ProxyChannelRequest = Schema.TaggedStruct('ProxyChannelRequest', {
|
|
55
55
|
...defaultPacketFields,
|
|
56
56
|
remainingHops: remainingHopsUndefined,
|
|
57
57
|
channelIdCandidate: Schema.String,
|
|
58
|
-
})
|
|
58
|
+
})
|
|
59
59
|
|
|
60
|
-
export
|
|
60
|
+
export const ProxyChannelResponseSuccess = Schema.TaggedStruct('ProxyChannelResponseSuccess', {
|
|
61
61
|
...defaultPacketFields,
|
|
62
62
|
reqId: Schema.String,
|
|
63
63
|
remainingHops: Schema.Array(Schema.String),
|
|
64
64
|
combinedChannelId: Schema.String,
|
|
65
65
|
channelIdCandidate: Schema.String,
|
|
66
|
-
})
|
|
66
|
+
})
|
|
67
67
|
|
|
68
|
-
export
|
|
68
|
+
export const ProxyChannelPayload = Schema.TaggedStruct('ProxyChannelPayload', {
|
|
69
69
|
...defaultPacketFields,
|
|
70
70
|
remainingHops: remainingHopsUndefined,
|
|
71
71
|
payload: Schema.Any,
|
|
72
72
|
combinedChannelId: Schema.String,
|
|
73
|
-
})
|
|
73
|
+
})
|
|
74
74
|
|
|
75
|
-
export
|
|
75
|
+
export const ProxyChannelPayloadAck = Schema.TaggedStruct('ProxyChannelPayloadAck', {
|
|
76
76
|
...defaultPacketFields,
|
|
77
77
|
reqId: Schema.String,
|
|
78
78
|
remainingHops: Schema.Array(Schema.String),
|
|
79
79
|
combinedChannelId: Schema.String,
|
|
80
|
-
})
|
|
80
|
+
})
|
|
81
81
|
|
|
82
82
|
/**
|
|
83
83
|
* Broadcast to all nodes when a new edge is added.
|
|
84
84
|
* Mostly used for auto-reconnect purposes.
|
|
85
85
|
*/
|
|
86
|
-
export
|
|
86
|
+
export const NetworkEdgeAdded = Schema.TaggedStruct('NetworkEdgeAdded', {
|
|
87
87
|
id,
|
|
88
88
|
source: Schema.String,
|
|
89
89
|
target: Schema.String,
|
|
90
|
-
})
|
|
90
|
+
})
|
|
91
91
|
|
|
92
|
-
export
|
|
92
|
+
export const NetworkTopologyRequest = Schema.TaggedStruct('NetworkTopologyRequest', {
|
|
93
93
|
id,
|
|
94
94
|
hops: Schema.Array(Schema.String),
|
|
95
95
|
/** Always fixed to who requested the topology */
|
|
96
96
|
source: Schema.String,
|
|
97
97
|
target: Schema.Literal('-'),
|
|
98
|
-
})
|
|
98
|
+
})
|
|
99
99
|
|
|
100
|
-
export
|
|
100
|
+
export const NetworkTopologyResponse = Schema.TaggedStruct('NetworkTopologyResponse', {
|
|
101
101
|
id,
|
|
102
102
|
reqId: Schema.String,
|
|
103
103
|
remainingHops: Schema.Array(Schema.String),
|
|
@@ -106,7 +106,7 @@ export class NetworkTopologyResponse extends Schema.TaggedStruct('NetworkTopolog
|
|
|
106
106
|
/** Always fixed to who requested the topology */
|
|
107
107
|
source: Schema.String,
|
|
108
108
|
target: Schema.Literal('-'),
|
|
109
|
-
})
|
|
109
|
+
})
|
|
110
110
|
|
|
111
111
|
export const BroadcastChannelPacket = Schema.TaggedStruct('BroadcastChannelPacket', {
|
|
112
112
|
id,
|
|
@@ -121,27 +121,28 @@ export const BroadcastChannelPacket = Schema.TaggedStruct('BroadcastChannelPacke
|
|
|
121
121
|
target: Schema.Literal('-'),
|
|
122
122
|
})
|
|
123
123
|
|
|
124
|
-
export
|
|
124
|
+
export const DirectChannelPacket = Schema.Union([
|
|
125
125
|
DirectChannelRequest,
|
|
126
126
|
DirectChannelResponseSuccess,
|
|
127
127
|
DirectChannelResponseNoTransferables,
|
|
128
|
-
)
|
|
128
|
+
])
|
|
129
129
|
|
|
130
|
-
export
|
|
130
|
+
export const ProxyChannelPacket = Schema.Union([
|
|
131
131
|
ProxyChannelRequest,
|
|
132
132
|
ProxyChannelResponseSuccess,
|
|
133
133
|
ProxyChannelPayload,
|
|
134
134
|
ProxyChannelPayloadAck,
|
|
135
|
-
)
|
|
135
|
+
])
|
|
136
136
|
|
|
137
|
-
export
|
|
137
|
+
export const Packet = Schema.Union([
|
|
138
138
|
DirectChannelPacket,
|
|
139
139
|
ProxyChannelPacket,
|
|
140
140
|
NetworkEdgeAdded,
|
|
141
141
|
NetworkTopologyRequest,
|
|
142
142
|
NetworkTopologyResponse,
|
|
143
143
|
BroadcastChannelPacket,
|
|
144
|
-
)
|
|
144
|
+
])
|
|
145
|
+
|
|
146
|
+
export const DirectChannelPing = Schema.TaggedStruct('DirectChannelPing', {})
|
|
145
147
|
|
|
146
|
-
export
|
|
147
|
-
export class DirectChannelPong extends Schema.TaggedStruct('DirectChannelPong', {}) {}
|
|
148
|
+
export const DirectChannelPong = Schema.TaggedStruct('DirectChannelPong', {})
|