@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
package/src/node.test.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { expect } from 'vitest'
|
|
2
|
+
|
|
1
3
|
import { IS_CI, omitUndefineds } from '@livestore/utils'
|
|
2
|
-
import { Chunk, Deferred, Effect, Exit, Schema, Scope, Stream, WebChannel } from '@livestore/utils/effect'
|
|
3
4
|
import { Vitest } from '@livestore/utils-dev/node-vitest'
|
|
4
|
-
import {
|
|
5
|
+
import { Deferred, Effect, Exit, Fiber, Schema, Scope, Stream, WebChannel } from '@livestore/utils/effect'
|
|
5
6
|
|
|
6
7
|
import { Packet } from './mesh-schema.ts'
|
|
7
8
|
import type { MeshNode } from './node.ts'
|
|
@@ -71,7 +72,7 @@ const createChannel = (source: MeshNode, target: string, options?: Partial<Param
|
|
|
71
72
|
|
|
72
73
|
const getFirstMessage = <T1, T2>(channel: WebChannel.WebChannel<T1, T2>) =>
|
|
73
74
|
channel.listen.pipe(
|
|
74
|
-
Stream.
|
|
75
|
+
Stream.mapEffect(Effect.fromResult),
|
|
75
76
|
Stream.take(1),
|
|
76
77
|
Stream.runCollect,
|
|
77
78
|
Effect.map(([message]) => message),
|
|
@@ -85,19 +86,19 @@ const maybeDelay =
|
|
|
85
86
|
? effect
|
|
86
87
|
: Effect.sleep(delay).pipe(Effect.withSpan(`${label}:delay(${delay})`), Effect.andThen(effect))
|
|
87
88
|
|
|
88
|
-
const testTimeout = IS_CI ? 30_000 : 1000
|
|
89
|
-
const propTestTimeout = IS_CI ? 60_000 : 20_000
|
|
89
|
+
const testTimeout = IS_CI === true ? 30_000 : 1000
|
|
90
|
+
const propTestTimeout = IS_CI === true ? 60_000 : 20_000
|
|
90
91
|
|
|
91
|
-
// TODO also make work without `Vitest.
|
|
92
|
+
// TODO also make work without `Vitest.live` (i.e. with `Vitest.effect`)
|
|
92
93
|
// probably requires controlling the clocks
|
|
93
94
|
Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
94
|
-
const Delay = Schema.UndefinedOr(Schema.
|
|
95
|
+
const Delay = Schema.UndefinedOr(Schema.Literals([0, 1, 10, 50]))
|
|
95
96
|
// NOTE for message channels, we test both with and without transferables (i.e. proxying)
|
|
96
|
-
const ChannelType = Schema.
|
|
97
|
-
const NodeNames = Schema.Union(
|
|
98
|
-
Schema.Tuple(Schema.Literal('A'), Schema.Literal('B')),
|
|
99
|
-
Schema.Tuple(Schema.Literal('B'), Schema.Literal('A')),
|
|
100
|
-
)
|
|
97
|
+
const ChannelType = Schema.Literals(['direct', 'proxy(via-messagechannel-edge)', 'proxy'])
|
|
98
|
+
const NodeNames = Schema.Union([
|
|
99
|
+
Schema.Tuple([Schema.Literal('A'), Schema.Literal('B')]),
|
|
100
|
+
Schema.Tuple([Schema.Literal('B'), Schema.Literal('A')]),
|
|
101
|
+
])
|
|
101
102
|
|
|
102
103
|
const fromChannelType = (
|
|
103
104
|
channelType: typeof ChannelType.Type,
|
|
@@ -170,10 +171,10 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
170
171
|
// const connectDelay = undefined
|
|
171
172
|
// const channelType = 'direct'
|
|
172
173
|
// const nodeNames = ['B', 'A'] as const
|
|
173
|
-
// Vitest.
|
|
174
|
+
// Vitest.live(
|
|
174
175
|
// 'a / b connect at different times with different channel types',
|
|
175
176
|
// (test) =>
|
|
176
|
-
Vitest.
|
|
177
|
+
Vitest.live.prop(
|
|
177
178
|
'a / b connect at different times with different channel types',
|
|
178
179
|
[Delay, Delay, Delay, ChannelType, NodeNames],
|
|
179
180
|
([delayX, delayY, connectDelay, channelType, nodeNames], test) =>
|
|
@@ -200,7 +201,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
200
201
|
yield* Effect.promise(() => nodeX.debug.requestTopology(100))
|
|
201
202
|
}).pipe(
|
|
202
203
|
Vitest.withTestCtx(test, {
|
|
203
|
-
suffix: `delayX=${delayX} delayY=${delayY} connectDelay=${connectDelay} channelType=${channelType} nodeNames=${nodeNames}`,
|
|
204
|
+
suffix: `delayX=${delayX} delayY=${delayY} connectDelay=${connectDelay} channelType=${channelType} nodeNames=${nodeNames.join(',')}`,
|
|
204
205
|
}),
|
|
205
206
|
),
|
|
206
207
|
// { fastCheck: { numRuns: 20 } },
|
|
@@ -208,10 +209,10 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
208
209
|
// const waitForOfflineDelay = undefined
|
|
209
210
|
// const sleepDelay = 0
|
|
210
211
|
// const channelType = 'direct'
|
|
211
|
-
// Vitest.
|
|
212
|
+
// Vitest.live(
|
|
212
213
|
// 'b reconnects',
|
|
213
214
|
// (test) =>
|
|
214
|
-
Vitest.
|
|
215
|
+
Vitest.live.prop(
|
|
215
216
|
'b reconnects',
|
|
216
217
|
[Delay, Delay, ChannelType],
|
|
217
218
|
([waitForOfflineDelay, sleepDelay, channelType], test) =>
|
|
@@ -231,8 +232,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
231
232
|
// TODO also optionally delay the edge
|
|
232
233
|
yield* connectNodes(nodeA, nodeB)
|
|
233
234
|
|
|
234
|
-
const waitForBToBeOffline =
|
|
235
|
-
waitForOfflineDelay === undefined ? undefined : yield* Deferred.make<void, never>()
|
|
235
|
+
const waitForBToBeOffline = waitForOfflineDelay === undefined ? undefined : yield* Deferred.make<void>()
|
|
236
236
|
|
|
237
237
|
const nodeACode = Effect.gen(function* () {
|
|
238
238
|
const channelAToB = yield* createChannel(nodeA, 'B', { mode })
|
|
@@ -241,7 +241,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
241
241
|
|
|
242
242
|
console.log('nodeACode:waiting for B to be offline')
|
|
243
243
|
if (waitForBToBeOffline !== undefined) {
|
|
244
|
-
yield* waitForBToBeOffline
|
|
244
|
+
yield* Deferred.await(waitForBToBeOffline)
|
|
245
245
|
}
|
|
246
246
|
|
|
247
247
|
yield* channelAToB.send({ message: 'A2' })
|
|
@@ -286,14 +286,14 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
286
286
|
{ fastCheck: { numRuns: 20 } },
|
|
287
287
|
)
|
|
288
288
|
|
|
289
|
-
Vitest.
|
|
289
|
+
Vitest.live('reconnect with re-created node', (test) =>
|
|
290
290
|
Effect.gen(function* () {
|
|
291
291
|
const nodeBgen1Scope = yield* Scope.make()
|
|
292
292
|
|
|
293
293
|
const nodeA = yield* makeMeshNode('A')
|
|
294
|
-
const nodeBgen1 = yield* makeMeshNode('B').pipe(Scope.
|
|
294
|
+
const nodeBgen1 = yield* makeMeshNode('B').pipe(Scope.provide(nodeBgen1Scope))
|
|
295
295
|
|
|
296
|
-
yield* connectNodesViaMessageChannel(nodeA, nodeBgen1).pipe(Scope.
|
|
296
|
+
yield* connectNodesViaMessageChannel(nodeA, nodeBgen1).pipe(Scope.provide(nodeBgen1Scope))
|
|
297
297
|
|
|
298
298
|
// yield* Effect.sleep(100)
|
|
299
299
|
|
|
@@ -314,7 +314,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
314
314
|
// expect(channelBToA.debugInfo.connectCounter).toBe(1)
|
|
315
315
|
})
|
|
316
316
|
|
|
317
|
-
yield* Effect.all([nodeACode, nodeBCode(nodeBgen1).pipe(Scope.
|
|
317
|
+
yield* Effect.all([nodeACode, nodeBCode(nodeBgen1).pipe(Scope.provide(nodeBgen1Scope))], {
|
|
318
318
|
concurrency: 'unbounded',
|
|
319
319
|
}).pipe(Effect.withSpan('test1'))
|
|
320
320
|
|
|
@@ -329,7 +329,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
329
329
|
}).pipe(Vitest.withTestCtx(test)),
|
|
330
330
|
)
|
|
331
331
|
|
|
332
|
-
const ChannelTypeWithoutMessageChannelProxy = Schema.
|
|
332
|
+
const ChannelTypeWithoutMessageChannelProxy = Schema.Literals(['proxy', 'direct'])
|
|
333
333
|
// TODO there seems to be a flaky case here which gets hit sometimes (e.g. 2025-02-28-17:11)
|
|
334
334
|
// Log output:
|
|
335
335
|
// test: { seed: -964670352, path: "1", endOnFailure: true }
|
|
@@ -345,7 +345,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
345
345
|
// test: Error: Property failed after 2 tests
|
|
346
346
|
// test: { seed: -964670352, path: "1", endOnFailure: true }
|
|
347
347
|
// test: Counterexample: ["direct",["A","B"]]
|
|
348
|
-
Vitest.
|
|
348
|
+
Vitest.live.prop(
|
|
349
349
|
'replace edge while keeping the channel',
|
|
350
350
|
[ChannelTypeWithoutMessageChannelProxy, NodeNames],
|
|
351
351
|
([channelType, nodeNames], test) =>
|
|
@@ -367,7 +367,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
367
367
|
yield* channelXToY.send({ message: `${nodeLabel.x}1` })
|
|
368
368
|
expect(yield* getFirstMessage(channelXToY)).toEqual({ message: `${nodeLabel.y}1` })
|
|
369
369
|
|
|
370
|
-
yield* waitForEdgeReplacement
|
|
370
|
+
yield* Deferred.await(waitForEdgeReplacement)
|
|
371
371
|
|
|
372
372
|
yield* channelXToY.send({ message: `${nodeLabel.x}2` })
|
|
373
373
|
expect(yield* getFirstMessage(channelXToY)).toEqual({ message: `${nodeLabel.y}2` })
|
|
@@ -392,7 +392,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
392
392
|
yield* Effect.all([nodeXCode, nodeYCode], { concurrency: 'unbounded' })
|
|
393
393
|
}).pipe(
|
|
394
394
|
Vitest.withTestCtx(test, {
|
|
395
|
-
suffix: `channelType=${channelType} nodeNames=${nodeNames}`,
|
|
395
|
+
suffix: `channelType=${channelType} nodeNames=${nodeNames.join(',')}`,
|
|
396
396
|
}),
|
|
397
397
|
),
|
|
398
398
|
{ fastCheck: { numRuns: 10 } },
|
|
@@ -400,9 +400,9 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
400
400
|
|
|
401
401
|
Vitest.describe('TODO improve latency', () => {
|
|
402
402
|
// TODO we need to improve latency when sending messages concurrently
|
|
403
|
-
Vitest.
|
|
403
|
+
Vitest.live.prop(
|
|
404
404
|
'concurrent messages',
|
|
405
|
-
[ChannelType, Schema.Int.
|
|
405
|
+
[ChannelType, Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 50 }))],
|
|
406
406
|
([channelType, count], test) =>
|
|
407
407
|
Effect.gen(function* () {
|
|
408
408
|
const nodeA = yield* makeMeshNode('A')
|
|
@@ -416,14 +416,18 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
416
416
|
|
|
417
417
|
// send 10 times A1
|
|
418
418
|
yield* Effect.forEach(
|
|
419
|
-
|
|
419
|
+
Array.from({ length: count }, (_, i) => ({ message: `A${i}` })),
|
|
420
420
|
channelAToB.send,
|
|
421
421
|
{ concurrency: 'unbounded' },
|
|
422
422
|
)
|
|
423
423
|
|
|
424
|
-
expect(
|
|
425
|
-
|
|
426
|
-
|
|
424
|
+
expect(
|
|
425
|
+
yield* channelAToB.listen.pipe(
|
|
426
|
+
Stream.mapEffect(Effect.fromResult),
|
|
427
|
+
Stream.take(count),
|
|
428
|
+
Stream.runCollect,
|
|
429
|
+
),
|
|
430
|
+
).toEqual(Array.from({ length: count }, (_, i) => ({ message: `B${i}` })))
|
|
427
431
|
// expect(yield* getFirstMessage(channelAToB)).toEqual({ message: 'A2' })
|
|
428
432
|
})
|
|
429
433
|
|
|
@@ -432,14 +436,18 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
432
436
|
|
|
433
437
|
// send 10 times B1
|
|
434
438
|
yield* Effect.forEach(
|
|
435
|
-
|
|
439
|
+
Array.from({ length: count }, (_, i) => ({ message: `B${i}` })),
|
|
436
440
|
channelBToA.send,
|
|
437
441
|
{ concurrency: 'unbounded' },
|
|
438
442
|
)
|
|
439
443
|
|
|
440
|
-
expect(
|
|
441
|
-
|
|
442
|
-
|
|
444
|
+
expect(
|
|
445
|
+
yield* channelBToA.listen.pipe(
|
|
446
|
+
Stream.mapEffect(Effect.fromResult),
|
|
447
|
+
Stream.take(count),
|
|
448
|
+
Stream.runCollect,
|
|
449
|
+
),
|
|
450
|
+
).toEqual(Array.from({ length: count }, (_, i) => ({ message: `A${i}` })))
|
|
443
451
|
})
|
|
444
452
|
|
|
445
453
|
yield* Effect.all([nodeACode, nodeBCode, connectNodes(nodeA, nodeB).pipe(Effect.delay(100))], {
|
|
@@ -457,7 +465,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
457
465
|
})
|
|
458
466
|
|
|
459
467
|
Vitest.describe('message channel specific tests', () => {
|
|
460
|
-
Vitest.
|
|
468
|
+
Vitest.live('differing initial edge counter', (test) =>
|
|
461
469
|
Effect.gen(function* () {
|
|
462
470
|
const nodeA = yield* makeMeshNode('A')
|
|
463
471
|
const nodeB = yield* makeMeshNode('B')
|
|
@@ -469,28 +477,32 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
469
477
|
const bFiber = yield* Effect.gen(function* () {
|
|
470
478
|
const channelBToA = yield* createChannel(nodeB, 'A')
|
|
471
479
|
yield* channelBToA.listen.pipe(
|
|
472
|
-
Stream.
|
|
480
|
+
Stream.mapEffect(Effect.fromResult),
|
|
473
481
|
Stream.tap((msg) => channelBToA.send({ message: `resp:${msg.message}` })),
|
|
474
482
|
Stream.take(messageCount),
|
|
475
483
|
Stream.runDrain,
|
|
476
484
|
)
|
|
477
|
-
}).pipe(Effect.scoped, Effect.
|
|
485
|
+
}).pipe(Effect.scoped, Effect.forkChild)
|
|
478
486
|
|
|
479
487
|
// yield* createChannel(nodeA, 'B').pipe(Effect.andThen(WebChannel.shutdown))
|
|
480
488
|
// // yield* createChannel(nodeA, 'B').pipe(Effect.andThen(WebChannel.shutdown))
|
|
481
489
|
// // yield* createChannel(nodeA, 'B').pipe(Effect.andThen(WebChannel.shutdown))
|
|
482
|
-
yield* Effect.
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
490
|
+
yield* Effect.replicateEffect(
|
|
491
|
+
Effect.gen(function* () {
|
|
492
|
+
const channelAToB = yield* createChannel(nodeA, 'B')
|
|
493
|
+
yield* channelAToB.send({ message: 'A' })
|
|
494
|
+
expect(yield* getFirstMessage(channelAToB)).toEqual({ message: 'resp:A' })
|
|
495
|
+
}).pipe(Effect.scoped),
|
|
496
|
+
messageCount,
|
|
497
|
+
{ discard: true },
|
|
498
|
+
)
|
|
487
499
|
|
|
488
|
-
yield* bFiber
|
|
500
|
+
yield* Fiber.join(bFiber)
|
|
489
501
|
}).pipe(Vitest.withTestCtx(test)),
|
|
490
502
|
)
|
|
491
503
|
})
|
|
492
504
|
|
|
493
|
-
Vitest.
|
|
505
|
+
Vitest.live('manual debug test', (test) =>
|
|
494
506
|
Effect.gen(function* () {
|
|
495
507
|
const nodeA = yield* makeMeshNode('A')
|
|
496
508
|
const nodeB = yield* makeMeshNode('B')
|
|
@@ -518,7 +530,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
518
530
|
}).pipe(Vitest.withTestCtx(test)),
|
|
519
531
|
)
|
|
520
532
|
|
|
521
|
-
Vitest.
|
|
533
|
+
Vitest.live('broadcast edge with message channel', (test) =>
|
|
522
534
|
Effect.gen(function* () {
|
|
523
535
|
const nodeA = yield* makeMeshNode('A')
|
|
524
536
|
const nodeB = yield* makeMeshNode('B')
|
|
@@ -526,13 +538,13 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
526
538
|
yield* connectNodesViaBroadcastChannel(nodeA, nodeB)
|
|
527
539
|
|
|
528
540
|
const err = yield* createChannel(nodeA, 'B', { mode: 'direct' }).pipe(Effect.timeout(200), Effect.flip)
|
|
529
|
-
expect(err._tag).toBe('
|
|
541
|
+
expect(err._tag).toBe('TimeoutError')
|
|
530
542
|
}).pipe(Vitest.withTestCtx(test)),
|
|
531
543
|
)
|
|
532
544
|
})
|
|
533
545
|
|
|
534
546
|
Vitest.describe('A <> B <> C', () => {
|
|
535
|
-
Vitest.
|
|
547
|
+
Vitest.live('should work', (test) =>
|
|
536
548
|
Effect.gen(function* () {
|
|
537
549
|
const nodeA = yield* makeMeshNode('A')
|
|
538
550
|
const nodeB = yield* makeMeshNode('B')
|
|
@@ -562,7 +574,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
562
574
|
}).pipe(Vitest.withTestCtx(test)),
|
|
563
575
|
)
|
|
564
576
|
|
|
565
|
-
Vitest.
|
|
577
|
+
Vitest.live('should work - delayed edge', (test) =>
|
|
566
578
|
Effect.gen(function* () {
|
|
567
579
|
const nodeA = yield* makeMeshNode('A')
|
|
568
580
|
const nodeB = yield* makeMeshNode('B')
|
|
@@ -597,7 +609,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
597
609
|
}).pipe(Vitest.withTestCtx(test)),
|
|
598
610
|
)
|
|
599
611
|
|
|
600
|
-
Vitest.
|
|
612
|
+
Vitest.live('proxy channel', (test) =>
|
|
601
613
|
Effect.gen(function* () {
|
|
602
614
|
const nodeA = yield* makeMeshNode('A')
|
|
603
615
|
const nodeB = yield* makeMeshNode('B')
|
|
@@ -622,7 +634,271 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
622
634
|
}).pipe(Vitest.withTestCtx(test)),
|
|
623
635
|
)
|
|
624
636
|
|
|
625
|
-
|
|
637
|
+
/**
|
|
638
|
+
* Pattern test: ACK sending must not block message processing.
|
|
639
|
+
*
|
|
640
|
+
* This test documents the pattern where ACKs are sent fire-and-forget using `Effect.forkScoped`
|
|
641
|
+
* to avoid blocking the message processing loop.
|
|
642
|
+
*
|
|
643
|
+
* Background: Before the fix, ACKs were sent with `await` which blocked message processing.
|
|
644
|
+
* This caused heartbeat timeouts in devtools after ~30 seconds ("Connection to app lost").
|
|
645
|
+
*
|
|
646
|
+
* Note: This unit test passes both with and without the fix because in-memory channels
|
|
647
|
+
* complete ACK sends instantly. The actual regression test is the Playwright test
|
|
648
|
+
* `node-adapter-timeout.play.ts` which uses real network conditions.
|
|
649
|
+
*
|
|
650
|
+
* This test exists to:
|
|
651
|
+
* 1. Document the expected pattern (high message throughput via proxy channels)
|
|
652
|
+
* 2. Verify the pattern works correctly with forked ACKs
|
|
653
|
+
* 3. Serve as a reference for the fix in proxy-channel.ts
|
|
654
|
+
*/
|
|
655
|
+
Vitest.live('ACK sending should not block message processing', (test) =>
|
|
656
|
+
Effect.gen(function* () {
|
|
657
|
+
const nodeA = yield* makeMeshNode('A')
|
|
658
|
+
const nodeB = yield* makeMeshNode('B')
|
|
659
|
+
const nodeC = yield* makeMeshNode('C')
|
|
660
|
+
|
|
661
|
+
yield* connectNodesViaBroadcastChannel(nodeA, nodeB)
|
|
662
|
+
yield* connectNodesViaBroadcastChannel(nodeB, nodeC)
|
|
663
|
+
|
|
664
|
+
// Send many messages to stress the ACK handling
|
|
665
|
+
const messageCount = 10
|
|
666
|
+
|
|
667
|
+
const nodeACode = Effect.gen(function* () {
|
|
668
|
+
const channelAToC = yield* createChannel(nodeA, 'C', { mode: 'proxy' })
|
|
669
|
+
// Send multiple messages concurrently
|
|
670
|
+
yield* Effect.forEach(
|
|
671
|
+
Array.from({ length: messageCount }, (_, i) => ({ message: `A${i}` })),
|
|
672
|
+
channelAToC.send,
|
|
673
|
+
{ concurrency: 'unbounded' },
|
|
674
|
+
)
|
|
675
|
+
// Receive responses
|
|
676
|
+
const responses = yield* channelAToC.listen.pipe(
|
|
677
|
+
Stream.mapEffect(Effect.fromResult),
|
|
678
|
+
Stream.take(messageCount),
|
|
679
|
+
Stream.runCollect,
|
|
680
|
+
)
|
|
681
|
+
expect(responses.length).toBe(messageCount)
|
|
682
|
+
})
|
|
683
|
+
|
|
684
|
+
const nodeCCode = Effect.gen(function* () {
|
|
685
|
+
const channelCToA = yield* createChannel(nodeC, 'A', { mode: 'proxy' })
|
|
686
|
+
// Send multiple messages concurrently
|
|
687
|
+
yield* Effect.forEach(
|
|
688
|
+
Array.from({ length: messageCount }, (_, i) => ({ message: `C${i}` })),
|
|
689
|
+
channelCToA.send,
|
|
690
|
+
{ concurrency: 'unbounded' },
|
|
691
|
+
)
|
|
692
|
+
// Receive responses
|
|
693
|
+
const responses = yield* channelCToA.listen.pipe(
|
|
694
|
+
Stream.mapEffect(Effect.fromResult),
|
|
695
|
+
Stream.take(messageCount),
|
|
696
|
+
Stream.runCollect,
|
|
697
|
+
)
|
|
698
|
+
expect(responses.length).toBe(messageCount)
|
|
699
|
+
})
|
|
700
|
+
|
|
701
|
+
yield* Effect.all([nodeACode, nodeCCode], { concurrency: 'unbounded' })
|
|
702
|
+
}).pipe(Vitest.withTestCtx(test)),
|
|
703
|
+
)
|
|
704
|
+
|
|
705
|
+
Vitest.describe('ACK forkScoped regression tests', { timeout: 10_000 }, () => {
|
|
706
|
+
/**
|
|
707
|
+
* REGRESSION TEST: ACK sends must be forked (non-blocking) to allow message processing to continue.
|
|
708
|
+
*
|
|
709
|
+
* This test uses simulation parameters to inject a delay BEFORE the ACK send.
|
|
710
|
+
* It then measures when messages arrive at the receiver's listen queue.
|
|
711
|
+
*
|
|
712
|
+
* With the fix (Effect.forkScoped):
|
|
713
|
+
* - ACK send is forked in the background
|
|
714
|
+
* - Message is added to listen queue IMMEDIATELY (before ACK send completes)
|
|
715
|
+
* - Multiple messages arrive close together regardless of ACK delay
|
|
716
|
+
*
|
|
717
|
+
* Without the fix (blocking yield*):
|
|
718
|
+
* - ACK send blocks the processing loop
|
|
719
|
+
* - Message is added to listen queue AFTER ACK send completes
|
|
720
|
+
* - Messages arrive spread out by the ACK delay
|
|
721
|
+
*
|
|
722
|
+
* To verify this test catches the regression:
|
|
723
|
+
* 1. Temporarily change `Effect.forkScoped` to `yield*` in proxy-channel.ts:319
|
|
724
|
+
* 2. Run this test - it should FAIL because messages arrive too slowly
|
|
725
|
+
* 3. Revert the change - test should PASS
|
|
726
|
+
*/
|
|
727
|
+
/**
|
|
728
|
+
* This test verifies the fix works: messages should arrive at the listen queue
|
|
729
|
+
* without being blocked by slow ACK sends. We measure how long it takes to receive
|
|
730
|
+
* all messages - with forked ACKs it should be fast, with blocking ACKs it would be slow.
|
|
731
|
+
*/
|
|
732
|
+
Vitest.live('messages arrive in listen queue without waiting for ACK send', (test) =>
|
|
733
|
+
Effect.gen(function* () {
|
|
734
|
+
const ACK_DELAY_MS = 50
|
|
735
|
+
const MESSAGE_COUNT = 5
|
|
736
|
+
|
|
737
|
+
const nodeA = yield* makeMeshNode('A')
|
|
738
|
+
const nodeB = yield* makeMeshNode('B')
|
|
739
|
+
|
|
740
|
+
yield* connectNodesViaMessageChannel(nodeA, nodeB)
|
|
741
|
+
|
|
742
|
+
const receivedMessages: string[] = []
|
|
743
|
+
|
|
744
|
+
const senderCode = Effect.gen(function* () {
|
|
745
|
+
const channelAToB = yield* nodeA.makeChannel({
|
|
746
|
+
target: 'B',
|
|
747
|
+
channelName: 'test-ack-timing',
|
|
748
|
+
schema: ExampleSchema,
|
|
749
|
+
mode: 'proxy',
|
|
750
|
+
timeout: 3000,
|
|
751
|
+
})
|
|
752
|
+
|
|
753
|
+
// Send all messages concurrently - this is key!
|
|
754
|
+
// With forked ACKs, all messages get processed immediately at receiver
|
|
755
|
+
// With blocking ACKs, messages would be processed one at a time with delays
|
|
756
|
+
yield* Effect.forEach(
|
|
757
|
+
Array.from({ length: MESSAGE_COUNT }, (_, i) => ({ message: `msg${i}` })),
|
|
758
|
+
channelAToB.send,
|
|
759
|
+
{ concurrency: 'unbounded' },
|
|
760
|
+
)
|
|
761
|
+
})
|
|
762
|
+
|
|
763
|
+
const receiverCode = Effect.gen(function* () {
|
|
764
|
+
const channelBToA = yield* nodeB.makeChannel({
|
|
765
|
+
target: 'A',
|
|
766
|
+
channelName: 'test-ack-timing',
|
|
767
|
+
schema: ExampleSchema,
|
|
768
|
+
mode: 'proxy',
|
|
769
|
+
timeout: 3000,
|
|
770
|
+
// KEY: Inject delay BEFORE ACK send
|
|
771
|
+
// With forkScoped: message arrives in queue immediately, then ACK is sent in background
|
|
772
|
+
// Without forkScoped: message waits for ACK delay before being added to queue
|
|
773
|
+
simulation: {
|
|
774
|
+
onPayload: {
|
|
775
|
+
beforeAckSend: ACK_DELAY_MS,
|
|
776
|
+
afterAckFork: 0,
|
|
777
|
+
afterListenQueueOffer: 0,
|
|
778
|
+
},
|
|
779
|
+
},
|
|
780
|
+
})
|
|
781
|
+
|
|
782
|
+
yield* channelBToA.listen.pipe(
|
|
783
|
+
Stream.mapEffect(Effect.fromResult),
|
|
784
|
+
Stream.tap((msg) =>
|
|
785
|
+
Effect.sync(() => {
|
|
786
|
+
receivedMessages.push(msg.message)
|
|
787
|
+
}),
|
|
788
|
+
),
|
|
789
|
+
Stream.take(MESSAGE_COUNT),
|
|
790
|
+
Stream.runDrain,
|
|
791
|
+
)
|
|
792
|
+
})
|
|
793
|
+
|
|
794
|
+
const startTime = Date.now()
|
|
795
|
+
yield* Effect.all([senderCode, receiverCode], { concurrency: 'unbounded' })
|
|
796
|
+
const elapsed = Date.now() - startTime
|
|
797
|
+
|
|
798
|
+
console.log(`[regression-test-1] Received ${receivedMessages.length} messages in ${elapsed}ms`)
|
|
799
|
+
console.log(`[regression-test-1] Messages: ${receivedMessages.join(', ')}`)
|
|
800
|
+
|
|
801
|
+
expect(receivedMessages.length).toBe(MESSAGE_COUNT)
|
|
802
|
+
|
|
803
|
+
// With forked ACKs, elapsed time should be much less than MESSAGE_COUNT * ACK_DELAY_MS
|
|
804
|
+
// because ACKs don't block message processing
|
|
805
|
+
const blockingTime = MESSAGE_COUNT * ACK_DELAY_MS
|
|
806
|
+
console.log(`[regression-test-1] Elapsed: ${elapsed}ms, Blocking estimate: ${blockingTime}ms`)
|
|
807
|
+
|
|
808
|
+
// The test passes if messages arrive faster than they would with blocking ACKs
|
|
809
|
+
// Allow some margin for test overhead (2x faster than blocking)
|
|
810
|
+
if (elapsed > blockingTime / 2) {
|
|
811
|
+
throw new Error(
|
|
812
|
+
`REGRESSION DETECTED: Processing took ${elapsed}ms, blocking estimate: ${blockingTime}ms. ` +
|
|
813
|
+
`With forked ACKs, processing should be much faster. ` +
|
|
814
|
+
`Check that proxy-channel.ts uses Effect.forkScoped for ACK sends.`,
|
|
815
|
+
)
|
|
816
|
+
}
|
|
817
|
+
}).pipe(Vitest.withTestCtx(test)),
|
|
818
|
+
)
|
|
819
|
+
|
|
820
|
+
/**
|
|
821
|
+
* Additional test: Verify message processing continues during slow ACK sends.
|
|
822
|
+
*
|
|
823
|
+
* This test sends multiple messages concurrently and verifies they're all
|
|
824
|
+
* processed even when ACK sends are slow.
|
|
825
|
+
*/
|
|
826
|
+
Vitest.live('concurrent messages processed despite slow ACK sends', (test) =>
|
|
827
|
+
Effect.gen(function* () {
|
|
828
|
+
const ACK_DELAY_MS = 50
|
|
829
|
+
const MESSAGE_COUNT = 5
|
|
830
|
+
|
|
831
|
+
const nodeA = yield* makeMeshNode('A')
|
|
832
|
+
const nodeB = yield* makeMeshNode('B')
|
|
833
|
+
|
|
834
|
+
yield* connectNodesViaMessageChannel(nodeA, nodeB)
|
|
835
|
+
|
|
836
|
+
const receivedMessages: string[] = []
|
|
837
|
+
|
|
838
|
+
const senderCode = Effect.gen(function* () {
|
|
839
|
+
const channelAToB = yield* nodeA.makeChannel({
|
|
840
|
+
target: 'B',
|
|
841
|
+
channelName: 'test-concurrent',
|
|
842
|
+
schema: ExampleSchema,
|
|
843
|
+
mode: 'proxy',
|
|
844
|
+
timeout: 3000,
|
|
845
|
+
})
|
|
846
|
+
|
|
847
|
+
// Send all messages concurrently
|
|
848
|
+
yield* Effect.forEach(
|
|
849
|
+
Array.from({ length: MESSAGE_COUNT }, (_, i) => ({ message: `msg${i}` })),
|
|
850
|
+
channelAToB.send,
|
|
851
|
+
{ concurrency: 'unbounded' },
|
|
852
|
+
)
|
|
853
|
+
})
|
|
854
|
+
|
|
855
|
+
const receiverCode = Effect.gen(function* () {
|
|
856
|
+
const channelBToA = yield* nodeB.makeChannel({
|
|
857
|
+
target: 'A',
|
|
858
|
+
channelName: 'test-concurrent',
|
|
859
|
+
schema: ExampleSchema,
|
|
860
|
+
mode: 'proxy',
|
|
861
|
+
timeout: 3000,
|
|
862
|
+
simulation: {
|
|
863
|
+
onPayload: {
|
|
864
|
+
beforeAckSend: ACK_DELAY_MS,
|
|
865
|
+
afterAckFork: 0,
|
|
866
|
+
afterListenQueueOffer: 0,
|
|
867
|
+
},
|
|
868
|
+
},
|
|
869
|
+
})
|
|
870
|
+
|
|
871
|
+
yield* channelBToA.listen.pipe(
|
|
872
|
+
Stream.mapEffect(Effect.fromResult),
|
|
873
|
+
Stream.tap((msg) =>
|
|
874
|
+
Effect.sync(() => {
|
|
875
|
+
receivedMessages.push(msg.message)
|
|
876
|
+
}),
|
|
877
|
+
),
|
|
878
|
+
Stream.take(MESSAGE_COUNT),
|
|
879
|
+
Stream.runDrain,
|
|
880
|
+
)
|
|
881
|
+
})
|
|
882
|
+
|
|
883
|
+
const startTime = Date.now()
|
|
884
|
+
yield* Effect.all([senderCode, receiverCode], { concurrency: 'unbounded' })
|
|
885
|
+
const elapsed = Date.now() - startTime
|
|
886
|
+
|
|
887
|
+
console.log(`[regression-test] Received ${receivedMessages.length} messages in ${elapsed}ms`)
|
|
888
|
+
console.log(`[regression-test] Messages: ${receivedMessages.join(', ')}`)
|
|
889
|
+
|
|
890
|
+
// All messages should be received
|
|
891
|
+
expect(receivedMessages.length).toBe(MESSAGE_COUNT)
|
|
892
|
+
|
|
893
|
+
// With forked ACKs, elapsed time should be much less than MESSAGE_COUNT * ACK_DELAY_MS
|
|
894
|
+
// because ACKs don't block message processing
|
|
895
|
+
const blockingTime = MESSAGE_COUNT * ACK_DELAY_MS
|
|
896
|
+
console.log(`[regression-test] Elapsed: ${elapsed}ms, Blocking estimate: ${blockingTime}ms`)
|
|
897
|
+
}).pipe(Vitest.withTestCtx(test)),
|
|
898
|
+
)
|
|
899
|
+
})
|
|
900
|
+
|
|
901
|
+
Vitest.live('should fail with timeout due to missing edge', (test) =>
|
|
626
902
|
Effect.gen(function* () {
|
|
627
903
|
const nodeA = yield* makeMeshNode('A')
|
|
628
904
|
const nodeB = yield* makeMeshNode('B')
|
|
@@ -633,19 +909,19 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
633
909
|
|
|
634
910
|
const nodeACode = Effect.gen(function* () {
|
|
635
911
|
const err = yield* createChannel(nodeA, 'C').pipe(Effect.timeout(200), Effect.flip)
|
|
636
|
-
expect(err._tag).toBe('
|
|
912
|
+
expect(err._tag).toBe('TimeoutError')
|
|
637
913
|
})
|
|
638
914
|
|
|
639
915
|
const nodeCCode = Effect.gen(function* () {
|
|
640
916
|
const err = yield* createChannel(nodeC, 'A').pipe(Effect.timeout(200), Effect.flip)
|
|
641
|
-
expect(err._tag).toBe('
|
|
917
|
+
expect(err._tag).toBe('TimeoutError')
|
|
642
918
|
})
|
|
643
919
|
|
|
644
920
|
yield* Effect.all([nodeACode, nodeCCode], { concurrency: 'unbounded' })
|
|
645
921
|
}).pipe(Vitest.withTestCtx(test)),
|
|
646
922
|
)
|
|
647
923
|
|
|
648
|
-
Vitest.
|
|
924
|
+
Vitest.live('should fail with timeout due no transferable', (test) =>
|
|
649
925
|
Effect.gen(function* () {
|
|
650
926
|
const nodeA = yield* makeMeshNode('A')
|
|
651
927
|
const nodeB = yield* makeMeshNode('B')
|
|
@@ -654,28 +930,28 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
654
930
|
|
|
655
931
|
const nodeACode = Effect.gen(function* () {
|
|
656
932
|
const err = yield* createChannel(nodeA, 'B').pipe(Effect.timeout(200), Effect.flip)
|
|
657
|
-
expect(err._tag).toBe('
|
|
933
|
+
expect(err._tag).toBe('TimeoutError')
|
|
658
934
|
})
|
|
659
935
|
|
|
660
936
|
const nodeBCode = Effect.gen(function* () {
|
|
661
937
|
const err = yield* createChannel(nodeB, 'A').pipe(Effect.timeout(200), Effect.flip)
|
|
662
|
-
expect(err._tag).toBe('
|
|
938
|
+
expect(err._tag).toBe('TimeoutError')
|
|
663
939
|
})
|
|
664
940
|
|
|
665
941
|
yield* Effect.all([nodeACode, nodeBCode], { concurrency: 'unbounded' })
|
|
666
942
|
}).pipe(Vitest.withTestCtx(test)),
|
|
667
943
|
)
|
|
668
944
|
|
|
669
|
-
Vitest.
|
|
945
|
+
Vitest.live('reconnect with re-created node', (test) =>
|
|
670
946
|
Effect.gen(function* () {
|
|
671
947
|
const nodeCgen1Scope = yield* Scope.make()
|
|
672
948
|
|
|
673
949
|
const nodeA = yield* makeMeshNode('A')
|
|
674
950
|
const nodeB = yield* makeMeshNode('B')
|
|
675
|
-
const nodeCgen1 = yield* makeMeshNode('C').pipe(Scope.
|
|
951
|
+
const nodeCgen1 = yield* makeMeshNode('C').pipe(Scope.provide(nodeCgen1Scope))
|
|
676
952
|
|
|
677
953
|
yield* connectNodesViaMessageChannel(nodeA, nodeB)
|
|
678
|
-
yield* connectNodesViaMessageChannel(nodeB, nodeCgen1).pipe(Scope.
|
|
954
|
+
yield* connectNodesViaMessageChannel(nodeB, nodeCgen1).pipe(Scope.provide(nodeCgen1Scope))
|
|
679
955
|
|
|
680
956
|
const nodeACode = Effect.gen(function* () {
|
|
681
957
|
const channelAToB = yield* createChannel(nodeA, 'C')
|
|
@@ -694,7 +970,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
694
970
|
|
|
695
971
|
yield* Effect.all([nodeACode, nodeCCode(nodeCgen1)], { concurrency: 'unbounded' }).pipe(
|
|
696
972
|
Effect.withSpan('test1'),
|
|
697
|
-
Scope.
|
|
973
|
+
Scope.provide(nodeCgen1Scope),
|
|
698
974
|
)
|
|
699
975
|
|
|
700
976
|
yield* Scope.close(nodeCgen1Scope, Exit.void)
|
|
@@ -717,7 +993,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
717
993
|
* D
|
|
718
994
|
*/
|
|
719
995
|
Vitest.describe('diamond topology', () => {
|
|
720
|
-
Vitest.
|
|
996
|
+
Vitest.live('should work', (test) =>
|
|
721
997
|
Effect.gen(function* () {
|
|
722
998
|
const nodeA = yield* makeMeshNode('A')
|
|
723
999
|
const nodeB = yield* makeMeshNode('B')
|
|
@@ -756,7 +1032,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
756
1032
|
* Topology: Butterfly topology with two connected hubs (C-D) each serving multiple nodes
|
|
757
1033
|
*/
|
|
758
1034
|
Vitest.describe('butterfly topology', () => {
|
|
759
|
-
Vitest.
|
|
1035
|
+
Vitest.live('should work', (test) =>
|
|
760
1036
|
Effect.gen(function* () {
|
|
761
1037
|
const nodeA = yield* makeMeshNode('A')
|
|
762
1038
|
const nodeB = yield* makeMeshNode('B')
|
|
@@ -793,7 +1069,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
793
1069
|
Vitest.describe('mixture of direct and proxy edge connections', () => {
|
|
794
1070
|
// TODO test case to better guard against case where side A tries to create a proxy channel to B
|
|
795
1071
|
// and side B tries to create a direct to A
|
|
796
|
-
Vitest.
|
|
1072
|
+
Vitest.live('should work for proxy channels', (test) =>
|
|
797
1073
|
Effect.gen(function* () {
|
|
798
1074
|
const nodeA = yield* makeMeshNode('A')
|
|
799
1075
|
const nodeB = yield* makeMeshNode('B')
|
|
@@ -805,7 +1081,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
805
1081
|
}).pipe(Vitest.withTestCtx(test)),
|
|
806
1082
|
)
|
|
807
1083
|
|
|
808
|
-
Vitest.
|
|
1084
|
+
Vitest.live('should work for directs', (test) =>
|
|
809
1085
|
Effect.gen(function* () {
|
|
810
1086
|
const nodeA = yield* makeMeshNode('A')
|
|
811
1087
|
const nodeB = yield* makeMeshNode('B')
|
|
@@ -832,7 +1108,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
832
1108
|
})
|
|
833
1109
|
|
|
834
1110
|
Vitest.describe('listenForChannel', () => {
|
|
835
|
-
Vitest.
|
|
1111
|
+
Vitest.live('connect later', (test) =>
|
|
836
1112
|
Effect.gen(function* () {
|
|
837
1113
|
const nodeA = yield* makeMeshNode('A')
|
|
838
1114
|
|
|
@@ -871,7 +1147,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
871
1147
|
)
|
|
872
1148
|
|
|
873
1149
|
// TODO provide a way to allow for reconnecting in the `listenForChannel` case
|
|
874
|
-
Vitest.
|
|
1150
|
+
Vitest.live.skip('reconnect', (test) =>
|
|
875
1151
|
Effect.gen(function* () {
|
|
876
1152
|
const nodeA = yield* makeMeshNode('A')
|
|
877
1153
|
|
|
@@ -935,7 +1211,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
935
1211
|
)
|
|
936
1212
|
|
|
937
1213
|
Vitest.describe('prop tests', { timeout: propTestTimeout }, () => {
|
|
938
|
-
Vitest.
|
|
1214
|
+
Vitest.live.prop(
|
|
939
1215
|
'listenForChannel A <> B <> C',
|
|
940
1216
|
[Delay, Delay, Delay, Delay, ChannelType],
|
|
941
1217
|
([delayNodeA, delayNodeC, delayConnectAB, delayConnectBC, channelType], test) =>
|
|
@@ -944,7 +1220,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
944
1220
|
const nodeB = yield* makeMeshNode('B')
|
|
945
1221
|
const nodeC = yield* makeMeshNode('C')
|
|
946
1222
|
|
|
947
|
-
const mode = channelType.includes('proxy') ? 'proxy' : 'direct'
|
|
1223
|
+
const mode = channelType.includes('proxy') === true ? 'proxy' : 'direct'
|
|
948
1224
|
const connect = channelType === 'direct' ? connectNodesViaMessageChannel : connectNodesViaBroadcastChannel
|
|
949
1225
|
yield* connect(nodeA, nodeB).pipe(maybeDelay(delayConnectAB, 'delayConnectAB'))
|
|
950
1226
|
yield* connect(nodeB, nodeC).pipe(maybeDelay(delayConnectBC, 'delayConnectBC'))
|
|
@@ -996,7 +1272,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
996
1272
|
})
|
|
997
1273
|
|
|
998
1274
|
Vitest.describe('broadcast channel', () => {
|
|
999
|
-
Vitest.
|
|
1275
|
+
Vitest.live('should work', (test) =>
|
|
1000
1276
|
Effect.gen(function* () {
|
|
1001
1277
|
const nodeA = yield* makeMeshNode('A')
|
|
1002
1278
|
const nodeB = yield* makeMeshNode('B')
|
|
@@ -1009,23 +1285,23 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
1009
1285
|
const channelOnC = yield* nodeC.makeBroadcastChannel({ channelName: 'test', schema: Schema.String })
|
|
1010
1286
|
|
|
1011
1287
|
const listenOnAFiber = yield* channelOnA.listen.pipe(
|
|
1012
|
-
Stream.
|
|
1288
|
+
Stream.mapEffect(Effect.fromResult),
|
|
1013
1289
|
Stream.runHead,
|
|
1014
|
-
Effect.
|
|
1015
|
-
Effect.
|
|
1290
|
+
Effect.flatMap(Effect.fromOption),
|
|
1291
|
+
Effect.forkChild,
|
|
1016
1292
|
)
|
|
1017
1293
|
const listenOnCFiber = yield* channelOnC.listen.pipe(
|
|
1018
|
-
Stream.
|
|
1294
|
+
Stream.mapEffect(Effect.fromResult),
|
|
1019
1295
|
Stream.runHead,
|
|
1020
|
-
Effect.
|
|
1021
|
-
Effect.
|
|
1296
|
+
Effect.flatMap(Effect.fromOption),
|
|
1297
|
+
Effect.forkChild,
|
|
1022
1298
|
)
|
|
1023
1299
|
|
|
1024
1300
|
yield* channelOnA.send('A1')
|
|
1025
1301
|
yield* channelOnC.send('C1')
|
|
1026
1302
|
|
|
1027
|
-
expect(yield* listenOnAFiber).toEqual('C1')
|
|
1028
|
-
expect(yield* listenOnCFiber).toEqual('A1')
|
|
1303
|
+
expect(yield* Fiber.join(listenOnAFiber)).toEqual('C1')
|
|
1304
|
+
expect(yield* Fiber.join(listenOnCFiber)).toEqual('A1')
|
|
1029
1305
|
}).pipe(Vitest.withTestCtx(test)),
|
|
1030
1306
|
)
|
|
1031
1307
|
})
|