@livestore/webmesh 0.4.0 → 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/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 +23 -18
- 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 +20 -21
- package/dist/channel/direct-channel.js.map +1 -1
- package/dist/channel/proxy-channel.d.ts +7 -8
- package/dist/channel/proxy-channel.d.ts.map +1 -1
- package/dist/channel/proxy-channel.js +26 -22
- 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 +3 -2
- 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 +6 -6
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +22 -21
- package/dist/node.js.map +1 -1
- package/dist/node.test.js +74 -71
- 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 +18 -16
- package/dist/websocket-edge.d.ts.map +1 -1
- package/dist/websocket-edge.js +30 -36
- package/dist/websocket-edge.js.map +1 -1
- package/dist/websocket-edge.test.js +14 -14
- package/dist/websocket-edge.test.js.map +1 -1
- 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 +20 -37
- package/src/channel/direct-channel-internal.ts +30 -22
- package/src/channel/direct-channel.ts +26 -23
- package/src/channel/proxy-channel.ts +39 -36
- package/src/common.ts +6 -3
- package/src/mesh-schema.ts +34 -33
- package/src/node.test.ts +99 -86
- package/src/node.ts +32 -32
- package/src/utils.ts +3 -3
- package/src/websocket-edge.test.ts +14 -14
- package/src/websocket-edge.ts +47 -41
- 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),
|
|
@@ -88,16 +89,16 @@ const maybeDelay =
|
|
|
88
89
|
const testTimeout = IS_CI === true ? 30_000 : 1000
|
|
89
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) =>
|
|
@@ -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) =>
|
|
@@ -240,7 +241,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
240
241
|
|
|
241
242
|
console.log('nodeACode:waiting for B to be offline')
|
|
242
243
|
if (waitForBToBeOffline !== undefined) {
|
|
243
|
-
yield* waitForBToBeOffline
|
|
244
|
+
yield* Deferred.await(waitForBToBeOffline)
|
|
244
245
|
}
|
|
245
246
|
|
|
246
247
|
yield* channelAToB.send({ message: 'A2' })
|
|
@@ -285,14 +286,14 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
285
286
|
{ fastCheck: { numRuns: 20 } },
|
|
286
287
|
)
|
|
287
288
|
|
|
288
|
-
Vitest.
|
|
289
|
+
Vitest.live('reconnect with re-created node', (test) =>
|
|
289
290
|
Effect.gen(function* () {
|
|
290
291
|
const nodeBgen1Scope = yield* Scope.make()
|
|
291
292
|
|
|
292
293
|
const nodeA = yield* makeMeshNode('A')
|
|
293
|
-
const nodeBgen1 = yield* makeMeshNode('B').pipe(Scope.
|
|
294
|
+
const nodeBgen1 = yield* makeMeshNode('B').pipe(Scope.provide(nodeBgen1Scope))
|
|
294
295
|
|
|
295
|
-
yield* connectNodesViaMessageChannel(nodeA, nodeBgen1).pipe(Scope.
|
|
296
|
+
yield* connectNodesViaMessageChannel(nodeA, nodeBgen1).pipe(Scope.provide(nodeBgen1Scope))
|
|
296
297
|
|
|
297
298
|
// yield* Effect.sleep(100)
|
|
298
299
|
|
|
@@ -313,7 +314,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
313
314
|
// expect(channelBToA.debugInfo.connectCounter).toBe(1)
|
|
314
315
|
})
|
|
315
316
|
|
|
316
|
-
yield* Effect.all([nodeACode, nodeBCode(nodeBgen1).pipe(Scope.
|
|
317
|
+
yield* Effect.all([nodeACode, nodeBCode(nodeBgen1).pipe(Scope.provide(nodeBgen1Scope))], {
|
|
317
318
|
concurrency: 'unbounded',
|
|
318
319
|
}).pipe(Effect.withSpan('test1'))
|
|
319
320
|
|
|
@@ -328,7 +329,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
328
329
|
}).pipe(Vitest.withTestCtx(test)),
|
|
329
330
|
)
|
|
330
331
|
|
|
331
|
-
const ChannelTypeWithoutMessageChannelProxy = Schema.
|
|
332
|
+
const ChannelTypeWithoutMessageChannelProxy = Schema.Literals(['proxy', 'direct'])
|
|
332
333
|
// TODO there seems to be a flaky case here which gets hit sometimes (e.g. 2025-02-28-17:11)
|
|
333
334
|
// Log output:
|
|
334
335
|
// test: { seed: -964670352, path: "1", endOnFailure: true }
|
|
@@ -344,7 +345,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
344
345
|
// test: Error: Property failed after 2 tests
|
|
345
346
|
// test: { seed: -964670352, path: "1", endOnFailure: true }
|
|
346
347
|
// test: Counterexample: ["direct",["A","B"]]
|
|
347
|
-
Vitest.
|
|
348
|
+
Vitest.live.prop(
|
|
348
349
|
'replace edge while keeping the channel',
|
|
349
350
|
[ChannelTypeWithoutMessageChannelProxy, NodeNames],
|
|
350
351
|
([channelType, nodeNames], test) =>
|
|
@@ -366,7 +367,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
366
367
|
yield* channelXToY.send({ message: `${nodeLabel.x}1` })
|
|
367
368
|
expect(yield* getFirstMessage(channelXToY)).toEqual({ message: `${nodeLabel.y}1` })
|
|
368
369
|
|
|
369
|
-
yield* waitForEdgeReplacement
|
|
370
|
+
yield* Deferred.await(waitForEdgeReplacement)
|
|
370
371
|
|
|
371
372
|
yield* channelXToY.send({ message: `${nodeLabel.x}2` })
|
|
372
373
|
expect(yield* getFirstMessage(channelXToY)).toEqual({ message: `${nodeLabel.y}2` })
|
|
@@ -399,9 +400,9 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
399
400
|
|
|
400
401
|
Vitest.describe('TODO improve latency', () => {
|
|
401
402
|
// TODO we need to improve latency when sending messages concurrently
|
|
402
|
-
Vitest.
|
|
403
|
+
Vitest.live.prop(
|
|
403
404
|
'concurrent messages',
|
|
404
|
-
[ChannelType, Schema.Int.
|
|
405
|
+
[ChannelType, Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 50 }))],
|
|
405
406
|
([channelType, count], test) =>
|
|
406
407
|
Effect.gen(function* () {
|
|
407
408
|
const nodeA = yield* makeMeshNode('A')
|
|
@@ -415,14 +416,18 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
415
416
|
|
|
416
417
|
// send 10 times A1
|
|
417
418
|
yield* Effect.forEach(
|
|
418
|
-
|
|
419
|
+
Array.from({ length: count }, (_, i) => ({ message: `A${i}` })),
|
|
419
420
|
channelAToB.send,
|
|
420
421
|
{ concurrency: 'unbounded' },
|
|
421
422
|
)
|
|
422
423
|
|
|
423
|
-
expect(
|
|
424
|
-
|
|
425
|
-
|
|
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}` })))
|
|
426
431
|
// expect(yield* getFirstMessage(channelAToB)).toEqual({ message: 'A2' })
|
|
427
432
|
})
|
|
428
433
|
|
|
@@ -431,14 +436,18 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
431
436
|
|
|
432
437
|
// send 10 times B1
|
|
433
438
|
yield* Effect.forEach(
|
|
434
|
-
|
|
439
|
+
Array.from({ length: count }, (_, i) => ({ message: `B${i}` })),
|
|
435
440
|
channelBToA.send,
|
|
436
441
|
{ concurrency: 'unbounded' },
|
|
437
442
|
)
|
|
438
443
|
|
|
439
|
-
expect(
|
|
440
|
-
|
|
441
|
-
|
|
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}` })))
|
|
442
451
|
})
|
|
443
452
|
|
|
444
453
|
yield* Effect.all([nodeACode, nodeBCode, connectNodes(nodeA, nodeB).pipe(Effect.delay(100))], {
|
|
@@ -456,7 +465,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
456
465
|
})
|
|
457
466
|
|
|
458
467
|
Vitest.describe('message channel specific tests', () => {
|
|
459
|
-
Vitest.
|
|
468
|
+
Vitest.live('differing initial edge counter', (test) =>
|
|
460
469
|
Effect.gen(function* () {
|
|
461
470
|
const nodeA = yield* makeMeshNode('A')
|
|
462
471
|
const nodeB = yield* makeMeshNode('B')
|
|
@@ -468,28 +477,32 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
468
477
|
const bFiber = yield* Effect.gen(function* () {
|
|
469
478
|
const channelBToA = yield* createChannel(nodeB, 'A')
|
|
470
479
|
yield* channelBToA.listen.pipe(
|
|
471
|
-
Stream.
|
|
480
|
+
Stream.mapEffect(Effect.fromResult),
|
|
472
481
|
Stream.tap((msg) => channelBToA.send({ message: `resp:${msg.message}` })),
|
|
473
482
|
Stream.take(messageCount),
|
|
474
483
|
Stream.runDrain,
|
|
475
484
|
)
|
|
476
|
-
}).pipe(Effect.scoped, Effect.
|
|
485
|
+
}).pipe(Effect.scoped, Effect.forkChild)
|
|
477
486
|
|
|
478
487
|
// yield* createChannel(nodeA, 'B').pipe(Effect.andThen(WebChannel.shutdown))
|
|
479
488
|
// // yield* createChannel(nodeA, 'B').pipe(Effect.andThen(WebChannel.shutdown))
|
|
480
489
|
// // yield* createChannel(nodeA, 'B').pipe(Effect.andThen(WebChannel.shutdown))
|
|
481
|
-
yield* Effect.
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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
|
+
)
|
|
486
499
|
|
|
487
|
-
yield* bFiber
|
|
500
|
+
yield* Fiber.join(bFiber)
|
|
488
501
|
}).pipe(Vitest.withTestCtx(test)),
|
|
489
502
|
)
|
|
490
503
|
})
|
|
491
504
|
|
|
492
|
-
Vitest.
|
|
505
|
+
Vitest.live('manual debug test', (test) =>
|
|
493
506
|
Effect.gen(function* () {
|
|
494
507
|
const nodeA = yield* makeMeshNode('A')
|
|
495
508
|
const nodeB = yield* makeMeshNode('B')
|
|
@@ -517,7 +530,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
517
530
|
}).pipe(Vitest.withTestCtx(test)),
|
|
518
531
|
)
|
|
519
532
|
|
|
520
|
-
Vitest.
|
|
533
|
+
Vitest.live('broadcast edge with message channel', (test) =>
|
|
521
534
|
Effect.gen(function* () {
|
|
522
535
|
const nodeA = yield* makeMeshNode('A')
|
|
523
536
|
const nodeB = yield* makeMeshNode('B')
|
|
@@ -525,13 +538,13 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
525
538
|
yield* connectNodesViaBroadcastChannel(nodeA, nodeB)
|
|
526
539
|
|
|
527
540
|
const err = yield* createChannel(nodeA, 'B', { mode: 'direct' }).pipe(Effect.timeout(200), Effect.flip)
|
|
528
|
-
expect(err._tag).toBe('
|
|
541
|
+
expect(err._tag).toBe('TimeoutError')
|
|
529
542
|
}).pipe(Vitest.withTestCtx(test)),
|
|
530
543
|
)
|
|
531
544
|
})
|
|
532
545
|
|
|
533
546
|
Vitest.describe('A <> B <> C', () => {
|
|
534
|
-
Vitest.
|
|
547
|
+
Vitest.live('should work', (test) =>
|
|
535
548
|
Effect.gen(function* () {
|
|
536
549
|
const nodeA = yield* makeMeshNode('A')
|
|
537
550
|
const nodeB = yield* makeMeshNode('B')
|
|
@@ -561,7 +574,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
561
574
|
}).pipe(Vitest.withTestCtx(test)),
|
|
562
575
|
)
|
|
563
576
|
|
|
564
|
-
Vitest.
|
|
577
|
+
Vitest.live('should work - delayed edge', (test) =>
|
|
565
578
|
Effect.gen(function* () {
|
|
566
579
|
const nodeA = yield* makeMeshNode('A')
|
|
567
580
|
const nodeB = yield* makeMeshNode('B')
|
|
@@ -596,7 +609,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
596
609
|
}).pipe(Vitest.withTestCtx(test)),
|
|
597
610
|
)
|
|
598
611
|
|
|
599
|
-
Vitest.
|
|
612
|
+
Vitest.live('proxy channel', (test) =>
|
|
600
613
|
Effect.gen(function* () {
|
|
601
614
|
const nodeA = yield* makeMeshNode('A')
|
|
602
615
|
const nodeB = yield* makeMeshNode('B')
|
|
@@ -639,7 +652,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
639
652
|
* 2. Verify the pattern works correctly with forked ACKs
|
|
640
653
|
* 3. Serve as a reference for the fix in proxy-channel.ts
|
|
641
654
|
*/
|
|
642
|
-
Vitest.
|
|
655
|
+
Vitest.live('ACK sending should not block message processing', (test) =>
|
|
643
656
|
Effect.gen(function* () {
|
|
644
657
|
const nodeA = yield* makeMeshNode('A')
|
|
645
658
|
const nodeB = yield* makeMeshNode('B')
|
|
@@ -655,34 +668,34 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
655
668
|
const channelAToC = yield* createChannel(nodeA, 'C', { mode: 'proxy' })
|
|
656
669
|
// Send multiple messages concurrently
|
|
657
670
|
yield* Effect.forEach(
|
|
658
|
-
|
|
671
|
+
Array.from({ length: messageCount }, (_, i) => ({ message: `A${i}` })),
|
|
659
672
|
channelAToC.send,
|
|
660
673
|
{ concurrency: 'unbounded' },
|
|
661
674
|
)
|
|
662
675
|
// Receive responses
|
|
663
676
|
const responses = yield* channelAToC.listen.pipe(
|
|
664
|
-
Stream.
|
|
677
|
+
Stream.mapEffect(Effect.fromResult),
|
|
665
678
|
Stream.take(messageCount),
|
|
666
679
|
Stream.runCollect,
|
|
667
680
|
)
|
|
668
|
-
expect(
|
|
681
|
+
expect(responses.length).toBe(messageCount)
|
|
669
682
|
})
|
|
670
683
|
|
|
671
684
|
const nodeCCode = Effect.gen(function* () {
|
|
672
685
|
const channelCToA = yield* createChannel(nodeC, 'A', { mode: 'proxy' })
|
|
673
686
|
// Send multiple messages concurrently
|
|
674
687
|
yield* Effect.forEach(
|
|
675
|
-
|
|
688
|
+
Array.from({ length: messageCount }, (_, i) => ({ message: `C${i}` })),
|
|
676
689
|
channelCToA.send,
|
|
677
690
|
{ concurrency: 'unbounded' },
|
|
678
691
|
)
|
|
679
692
|
// Receive responses
|
|
680
693
|
const responses = yield* channelCToA.listen.pipe(
|
|
681
|
-
Stream.
|
|
694
|
+
Stream.mapEffect(Effect.fromResult),
|
|
682
695
|
Stream.take(messageCount),
|
|
683
696
|
Stream.runCollect,
|
|
684
697
|
)
|
|
685
|
-
expect(
|
|
698
|
+
expect(responses.length).toBe(messageCount)
|
|
686
699
|
})
|
|
687
700
|
|
|
688
701
|
yield* Effect.all([nodeACode, nodeCCode], { concurrency: 'unbounded' })
|
|
@@ -716,7 +729,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
716
729
|
* without being blocked by slow ACK sends. We measure how long it takes to receive
|
|
717
730
|
* all messages - with forked ACKs it should be fast, with blocking ACKs it would be slow.
|
|
718
731
|
*/
|
|
719
|
-
Vitest.
|
|
732
|
+
Vitest.live('messages arrive in listen queue without waiting for ACK send', (test) =>
|
|
720
733
|
Effect.gen(function* () {
|
|
721
734
|
const ACK_DELAY_MS = 50
|
|
722
735
|
const MESSAGE_COUNT = 5
|
|
@@ -741,7 +754,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
741
754
|
// With forked ACKs, all messages get processed immediately at receiver
|
|
742
755
|
// With blocking ACKs, messages would be processed one at a time with delays
|
|
743
756
|
yield* Effect.forEach(
|
|
744
|
-
|
|
757
|
+
Array.from({ length: MESSAGE_COUNT }, (_, i) => ({ message: `msg${i}` })),
|
|
745
758
|
channelAToB.send,
|
|
746
759
|
{ concurrency: 'unbounded' },
|
|
747
760
|
)
|
|
@@ -767,7 +780,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
767
780
|
})
|
|
768
781
|
|
|
769
782
|
yield* channelBToA.listen.pipe(
|
|
770
|
-
Stream.
|
|
783
|
+
Stream.mapEffect(Effect.fromResult),
|
|
771
784
|
Stream.tap((msg) =>
|
|
772
785
|
Effect.sync(() => {
|
|
773
786
|
receivedMessages.push(msg.message)
|
|
@@ -810,7 +823,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
810
823
|
* This test sends multiple messages concurrently and verifies they're all
|
|
811
824
|
* processed even when ACK sends are slow.
|
|
812
825
|
*/
|
|
813
|
-
Vitest.
|
|
826
|
+
Vitest.live('concurrent messages processed despite slow ACK sends', (test) =>
|
|
814
827
|
Effect.gen(function* () {
|
|
815
828
|
const ACK_DELAY_MS = 50
|
|
816
829
|
const MESSAGE_COUNT = 5
|
|
@@ -833,7 +846,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
833
846
|
|
|
834
847
|
// Send all messages concurrently
|
|
835
848
|
yield* Effect.forEach(
|
|
836
|
-
|
|
849
|
+
Array.from({ length: MESSAGE_COUNT }, (_, i) => ({ message: `msg${i}` })),
|
|
837
850
|
channelAToB.send,
|
|
838
851
|
{ concurrency: 'unbounded' },
|
|
839
852
|
)
|
|
@@ -856,7 +869,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
856
869
|
})
|
|
857
870
|
|
|
858
871
|
yield* channelBToA.listen.pipe(
|
|
859
|
-
Stream.
|
|
872
|
+
Stream.mapEffect(Effect.fromResult),
|
|
860
873
|
Stream.tap((msg) =>
|
|
861
874
|
Effect.sync(() => {
|
|
862
875
|
receivedMessages.push(msg.message)
|
|
@@ -885,7 +898,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
885
898
|
)
|
|
886
899
|
})
|
|
887
900
|
|
|
888
|
-
Vitest.
|
|
901
|
+
Vitest.live('should fail with timeout due to missing edge', (test) =>
|
|
889
902
|
Effect.gen(function* () {
|
|
890
903
|
const nodeA = yield* makeMeshNode('A')
|
|
891
904
|
const nodeB = yield* makeMeshNode('B')
|
|
@@ -896,19 +909,19 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
896
909
|
|
|
897
910
|
const nodeACode = Effect.gen(function* () {
|
|
898
911
|
const err = yield* createChannel(nodeA, 'C').pipe(Effect.timeout(200), Effect.flip)
|
|
899
|
-
expect(err._tag).toBe('
|
|
912
|
+
expect(err._tag).toBe('TimeoutError')
|
|
900
913
|
})
|
|
901
914
|
|
|
902
915
|
const nodeCCode = Effect.gen(function* () {
|
|
903
916
|
const err = yield* createChannel(nodeC, 'A').pipe(Effect.timeout(200), Effect.flip)
|
|
904
|
-
expect(err._tag).toBe('
|
|
917
|
+
expect(err._tag).toBe('TimeoutError')
|
|
905
918
|
})
|
|
906
919
|
|
|
907
920
|
yield* Effect.all([nodeACode, nodeCCode], { concurrency: 'unbounded' })
|
|
908
921
|
}).pipe(Vitest.withTestCtx(test)),
|
|
909
922
|
)
|
|
910
923
|
|
|
911
|
-
Vitest.
|
|
924
|
+
Vitest.live('should fail with timeout due no transferable', (test) =>
|
|
912
925
|
Effect.gen(function* () {
|
|
913
926
|
const nodeA = yield* makeMeshNode('A')
|
|
914
927
|
const nodeB = yield* makeMeshNode('B')
|
|
@@ -917,28 +930,28 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
917
930
|
|
|
918
931
|
const nodeACode = Effect.gen(function* () {
|
|
919
932
|
const err = yield* createChannel(nodeA, 'B').pipe(Effect.timeout(200), Effect.flip)
|
|
920
|
-
expect(err._tag).toBe('
|
|
933
|
+
expect(err._tag).toBe('TimeoutError')
|
|
921
934
|
})
|
|
922
935
|
|
|
923
936
|
const nodeBCode = Effect.gen(function* () {
|
|
924
937
|
const err = yield* createChannel(nodeB, 'A').pipe(Effect.timeout(200), Effect.flip)
|
|
925
|
-
expect(err._tag).toBe('
|
|
938
|
+
expect(err._tag).toBe('TimeoutError')
|
|
926
939
|
})
|
|
927
940
|
|
|
928
941
|
yield* Effect.all([nodeACode, nodeBCode], { concurrency: 'unbounded' })
|
|
929
942
|
}).pipe(Vitest.withTestCtx(test)),
|
|
930
943
|
)
|
|
931
944
|
|
|
932
|
-
Vitest.
|
|
945
|
+
Vitest.live('reconnect with re-created node', (test) =>
|
|
933
946
|
Effect.gen(function* () {
|
|
934
947
|
const nodeCgen1Scope = yield* Scope.make()
|
|
935
948
|
|
|
936
949
|
const nodeA = yield* makeMeshNode('A')
|
|
937
950
|
const nodeB = yield* makeMeshNode('B')
|
|
938
|
-
const nodeCgen1 = yield* makeMeshNode('C').pipe(Scope.
|
|
951
|
+
const nodeCgen1 = yield* makeMeshNode('C').pipe(Scope.provide(nodeCgen1Scope))
|
|
939
952
|
|
|
940
953
|
yield* connectNodesViaMessageChannel(nodeA, nodeB)
|
|
941
|
-
yield* connectNodesViaMessageChannel(nodeB, nodeCgen1).pipe(Scope.
|
|
954
|
+
yield* connectNodesViaMessageChannel(nodeB, nodeCgen1).pipe(Scope.provide(nodeCgen1Scope))
|
|
942
955
|
|
|
943
956
|
const nodeACode = Effect.gen(function* () {
|
|
944
957
|
const channelAToB = yield* createChannel(nodeA, 'C')
|
|
@@ -957,7 +970,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
957
970
|
|
|
958
971
|
yield* Effect.all([nodeACode, nodeCCode(nodeCgen1)], { concurrency: 'unbounded' }).pipe(
|
|
959
972
|
Effect.withSpan('test1'),
|
|
960
|
-
Scope.
|
|
973
|
+
Scope.provide(nodeCgen1Scope),
|
|
961
974
|
)
|
|
962
975
|
|
|
963
976
|
yield* Scope.close(nodeCgen1Scope, Exit.void)
|
|
@@ -980,7 +993,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
980
993
|
* D
|
|
981
994
|
*/
|
|
982
995
|
Vitest.describe('diamond topology', () => {
|
|
983
|
-
Vitest.
|
|
996
|
+
Vitest.live('should work', (test) =>
|
|
984
997
|
Effect.gen(function* () {
|
|
985
998
|
const nodeA = yield* makeMeshNode('A')
|
|
986
999
|
const nodeB = yield* makeMeshNode('B')
|
|
@@ -1019,7 +1032,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
1019
1032
|
* Topology: Butterfly topology with two connected hubs (C-D) each serving multiple nodes
|
|
1020
1033
|
*/
|
|
1021
1034
|
Vitest.describe('butterfly topology', () => {
|
|
1022
|
-
Vitest.
|
|
1035
|
+
Vitest.live('should work', (test) =>
|
|
1023
1036
|
Effect.gen(function* () {
|
|
1024
1037
|
const nodeA = yield* makeMeshNode('A')
|
|
1025
1038
|
const nodeB = yield* makeMeshNode('B')
|
|
@@ -1056,7 +1069,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
1056
1069
|
Vitest.describe('mixture of direct and proxy edge connections', () => {
|
|
1057
1070
|
// TODO test case to better guard against case where side A tries to create a proxy channel to B
|
|
1058
1071
|
// and side B tries to create a direct to A
|
|
1059
|
-
Vitest.
|
|
1072
|
+
Vitest.live('should work for proxy channels', (test) =>
|
|
1060
1073
|
Effect.gen(function* () {
|
|
1061
1074
|
const nodeA = yield* makeMeshNode('A')
|
|
1062
1075
|
const nodeB = yield* makeMeshNode('B')
|
|
@@ -1068,7 +1081,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
1068
1081
|
}).pipe(Vitest.withTestCtx(test)),
|
|
1069
1082
|
)
|
|
1070
1083
|
|
|
1071
|
-
Vitest.
|
|
1084
|
+
Vitest.live('should work for directs', (test) =>
|
|
1072
1085
|
Effect.gen(function* () {
|
|
1073
1086
|
const nodeA = yield* makeMeshNode('A')
|
|
1074
1087
|
const nodeB = yield* makeMeshNode('B')
|
|
@@ -1095,7 +1108,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
1095
1108
|
})
|
|
1096
1109
|
|
|
1097
1110
|
Vitest.describe('listenForChannel', () => {
|
|
1098
|
-
Vitest.
|
|
1111
|
+
Vitest.live('connect later', (test) =>
|
|
1099
1112
|
Effect.gen(function* () {
|
|
1100
1113
|
const nodeA = yield* makeMeshNode('A')
|
|
1101
1114
|
|
|
@@ -1134,7 +1147,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
1134
1147
|
)
|
|
1135
1148
|
|
|
1136
1149
|
// TODO provide a way to allow for reconnecting in the `listenForChannel` case
|
|
1137
|
-
Vitest.
|
|
1150
|
+
Vitest.live.skip('reconnect', (test) =>
|
|
1138
1151
|
Effect.gen(function* () {
|
|
1139
1152
|
const nodeA = yield* makeMeshNode('A')
|
|
1140
1153
|
|
|
@@ -1198,7 +1211,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
1198
1211
|
)
|
|
1199
1212
|
|
|
1200
1213
|
Vitest.describe('prop tests', { timeout: propTestTimeout }, () => {
|
|
1201
|
-
Vitest.
|
|
1214
|
+
Vitest.live.prop(
|
|
1202
1215
|
'listenForChannel A <> B <> C',
|
|
1203
1216
|
[Delay, Delay, Delay, Delay, ChannelType],
|
|
1204
1217
|
([delayNodeA, delayNodeC, delayConnectAB, delayConnectBC, channelType], test) =>
|
|
@@ -1259,7 +1272,7 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
1259
1272
|
})
|
|
1260
1273
|
|
|
1261
1274
|
Vitest.describe('broadcast channel', () => {
|
|
1262
|
-
Vitest.
|
|
1275
|
+
Vitest.live('should work', (test) =>
|
|
1263
1276
|
Effect.gen(function* () {
|
|
1264
1277
|
const nodeA = yield* makeMeshNode('A')
|
|
1265
1278
|
const nodeB = yield* makeMeshNode('B')
|
|
@@ -1272,23 +1285,23 @@ Vitest.describe('webmesh node', { timeout: testTimeout }, () => {
|
|
|
1272
1285
|
const channelOnC = yield* nodeC.makeBroadcastChannel({ channelName: 'test', schema: Schema.String })
|
|
1273
1286
|
|
|
1274
1287
|
const listenOnAFiber = yield* channelOnA.listen.pipe(
|
|
1275
|
-
Stream.
|
|
1288
|
+
Stream.mapEffect(Effect.fromResult),
|
|
1276
1289
|
Stream.runHead,
|
|
1277
|
-
Effect.
|
|
1278
|
-
Effect.
|
|
1290
|
+
Effect.flatMap(Effect.fromOption),
|
|
1291
|
+
Effect.forkChild,
|
|
1279
1292
|
)
|
|
1280
1293
|
const listenOnCFiber = yield* channelOnC.listen.pipe(
|
|
1281
|
-
Stream.
|
|
1294
|
+
Stream.mapEffect(Effect.fromResult),
|
|
1282
1295
|
Stream.runHead,
|
|
1283
|
-
Effect.
|
|
1284
|
-
Effect.
|
|
1296
|
+
Effect.flatMap(Effect.fromOption),
|
|
1297
|
+
Effect.forkChild,
|
|
1285
1298
|
)
|
|
1286
1299
|
|
|
1287
1300
|
yield* channelOnA.send('A1')
|
|
1288
1301
|
yield* channelOnC.send('C1')
|
|
1289
1302
|
|
|
1290
|
-
expect(yield* listenOnAFiber).toEqual('C1')
|
|
1291
|
-
expect(yield* listenOnCFiber).toEqual('A1')
|
|
1303
|
+
expect(yield* Fiber.join(listenOnAFiber)).toEqual('C1')
|
|
1304
|
+
expect(yield* Fiber.join(listenOnCFiber)).toEqual('A1')
|
|
1292
1305
|
}).pipe(Vitest.withTestCtx(test)),
|
|
1293
1306
|
)
|
|
1294
1307
|
})
|