@livestore/utils 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/NoopTracer.d.ts +7 -8
- package/dist/NoopTracer.d.ts.map +1 -1
- package/dist/NoopTracer.js +3 -18
- package/dist/NoopTracer.js.map +1 -1
- package/dist/NoopTracer.test.d.ts +2 -0
- package/dist/NoopTracer.test.d.ts.map +1 -0
- package/dist/NoopTracer.test.js +17 -0
- package/dist/NoopTracer.test.js.map +1 -0
- package/dist/browser/Opfs/Opfs.d.ts +179 -29
- package/dist/browser/Opfs/Opfs.d.ts.map +1 -1
- package/dist/browser/Opfs/Opfs.js +132 -307
- package/dist/browser/Opfs/Opfs.js.map +1 -1
- package/dist/browser/Opfs/debug-utils.d.ts.map +1 -1
- package/dist/browser/Opfs/debug-utils.js +5 -4
- package/dist/browser/Opfs/debug-utils.js.map +1 -1
- package/dist/browser/Opfs/utils.d.ts +8 -8
- package/dist/browser/Opfs/utils.d.ts.map +1 -1
- package/dist/browser/Opfs/utils.js +35 -26
- package/dist/browser/Opfs/utils.js.map +1 -1
- package/dist/browser/QuotaExceededError.d.ts +10 -9
- package/dist/browser/QuotaExceededError.d.ts.map +1 -1
- package/dist/browser/WebChannelBrowser.d.ts.map +1 -1
- package/dist/browser/WebChannelBrowser.js +21 -10
- package/dist/browser/WebChannelBrowser.js.map +1 -1
- package/dist/browser/WebError.d.ts +54 -86
- package/dist/browser/WebError.d.ts.map +1 -1
- package/dist/browser/WebError.js +91 -84
- package/dist/browser/WebError.js.map +1 -1
- package/dist/browser/WebError.test.js +6 -6
- package/dist/browser/WebError.test.js.map +1 -1
- package/dist/browser/WebLock.d.ts.map +1 -1
- package/dist/browser/WebLock.js +13 -13
- package/dist/browser/WebLock.js.map +1 -1
- package/dist/effect/Debug.d.ts +4 -7
- package/dist/effect/Debug.d.ts.map +1 -1
- package/dist/effect/Debug.js +32 -36
- package/dist/effect/Debug.js.map +1 -1
- package/dist/effect/Effect.d.ts +31 -22
- package/dist/effect/Effect.d.ts.map +1 -1
- package/dist/effect/Effect.js +51 -47
- package/dist/effect/Effect.js.map +1 -1
- package/dist/effect/Effect.test.d.ts +2 -0
- package/dist/effect/Effect.test.d.ts.map +1 -0
- package/dist/effect/Effect.test.js +72 -0
- package/dist/effect/Effect.test.js.map +1 -0
- package/dist/effect/Error.d.ts +4 -6
- package/dist/effect/Error.d.ts.map +1 -1
- package/dist/effect/Logger.d.ts +0 -6
- package/dist/effect/Logger.d.ts.map +1 -1
- package/dist/effect/Logger.js +0 -32
- package/dist/effect/Logger.js.map +1 -1
- package/dist/effect/OtelTracer.d.ts +1 -1
- package/dist/effect/OtelTracer.d.ts.map +1 -1
- package/dist/effect/OtelTracer.js +2 -3
- package/dist/effect/OtelTracer.js.map +1 -1
- package/dist/effect/RpcClient.d.ts +7 -7
- package/dist/effect/RpcClient.d.ts.map +1 -1
- package/dist/effect/RpcClient.js +72 -52
- package/dist/effect/RpcClient.js.map +1 -1
- package/dist/effect/Schedule.d.ts +1 -1
- package/dist/effect/Schedule.d.ts.map +1 -1
- package/dist/effect/Schedule.js +2 -2
- package/dist/effect/Schedule.js.map +1 -1
- package/dist/effect/Schema/debug-diff.d.ts +1 -1
- package/dist/effect/Schema/debug-diff.d.ts.map +1 -1
- package/dist/effect/Schema/debug-diff.js +5 -7
- package/dist/effect/Schema/debug-diff.js.map +1 -1
- package/dist/effect/Schema/debug-diff.test.js +7 -4
- package/dist/effect/Schema/debug-diff.test.js.map +1 -1
- package/dist/effect/Schema/index.d.ts +28 -20
- package/dist/effect/Schema/index.d.ts.map +1 -1
- package/dist/effect/Schema/index.js +55 -28
- package/dist/effect/Schema/index.js.map +1 -1
- package/dist/effect/Stream.d.ts +13 -5
- package/dist/effect/Stream.d.ts.map +1 -1
- package/dist/effect/Stream.js +19 -12
- package/dist/effect/Stream.js.map +1 -1
- package/dist/effect/Stream.test.js +9 -9
- package/dist/effect/Stream.test.js.map +1 -1
- package/dist/effect/Subscribable.d.ts +9 -3
- package/dist/effect/Subscribable.d.ts.map +1 -1
- package/dist/effect/Subscribable.js +20 -35
- package/dist/effect/Subscribable.js.map +1 -1
- package/dist/effect/SubscriptionRef.d.ts +5 -6
- package/dist/effect/SubscriptionRef.d.ts.map +1 -1
- package/dist/effect/SubscriptionRef.js +2 -3
- package/dist/effect/SubscriptionRef.js.map +1 -1
- package/dist/effect/TaskTracing.d.ts +1 -1
- package/dist/effect/TaskTracing.d.ts.map +1 -1
- package/dist/effect/TaskTracing.js +12 -16
- package/dist/effect/TaskTracing.js.map +1 -1
- package/dist/effect/WebChannel/WebChannel.d.ts +7 -7
- package/dist/effect/WebChannel/WebChannel.d.ts.map +1 -1
- package/dist/effect/WebChannel/WebChannel.js +64 -43
- package/dist/effect/WebChannel/WebChannel.js.map +1 -1
- package/dist/effect/WebChannel/WebChannel.test.js +15 -15
- package/dist/effect/WebChannel/WebChannel.test.js.map +1 -1
- package/dist/effect/WebChannel/broadcastChannelWithAck.d.ts.map +1 -1
- package/dist/effect/WebChannel/broadcastChannelWithAck.js +16 -9
- package/dist/effect/WebChannel/broadcastChannelWithAck.js.map +1 -1
- package/dist/effect/WebChannel/common.d.ts +16 -17
- package/dist/effect/WebChannel/common.d.ts.map +1 -1
- package/dist/effect/WebChannel/common.js +6 -6
- package/dist/effect/WebChannel/common.js.map +1 -1
- package/dist/effect/WebSocket.d.ts +4 -6
- package/dist/effect/WebSocket.d.ts.map +1 -1
- package/dist/effect/WebSocket.js +4 -4
- package/dist/effect/WebSocket.js.map +1 -1
- package/dist/effect/WebSocket.test.js +2 -2
- package/dist/effect/WebSocket.test.js.map +1 -1
- package/dist/effect/mod.browser-build.test.d.ts +2 -0
- package/dist/effect/mod.browser-build.test.d.ts.map +1 -0
- package/dist/effect/mod.browser-build.test.js +18 -0
- package/dist/effect/mod.browser-build.test.js.map +1 -0
- package/dist/effect/mod.d.ts +13 -15
- package/dist/effect/mod.d.ts.map +1 -1
- package/dist/effect/mod.js +13 -18
- package/dist/effect/mod.js.map +1 -1
- package/dist/effect/spanEvent.d.ts.map +1 -1
- package/dist/effect/spanEvent.js +2 -2
- package/dist/effect/spanEvent.js.map +1 -1
- package/dist/effect/spanEvent.test.js +10 -13
- package/dist/effect/spanEvent.test.js.map +1 -1
- package/dist/fast-deep-equal.d.ts.map +1 -1
- package/dist/fast-deep-equal.js +1 -2
- package/dist/fast-deep-equal.js.map +1 -1
- package/dist/misc.js +2 -2
- package/dist/misc.js.map +1 -1
- package/dist/mod.d.ts +0 -35
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +0 -8
- package/dist/mod.js.map +1 -1
- package/dist/node/mod.d.ts +17 -11
- package/dist/node/mod.d.ts.map +1 -1
- package/dist/node/mod.js +23 -16
- package/dist/node/mod.js.map +1 -1
- package/dist/object/index.d.ts +0 -9
- package/dist/object/index.d.ts.map +1 -1
- package/dist/object/index.js +0 -9
- package/dist/object/index.js.map +1 -1
- package/dist/string.d.ts +0 -2
- package/dist/string.d.ts.map +1 -1
- package/dist/string.js +0 -2
- package/dist/string.js.map +1 -1
- package/package.json +18 -36
- package/src/NoopTracer.test.ts +20 -0
- package/src/NoopTracer.ts +18 -41
- package/src/browser/Opfs/Opfs.ts +304 -277
- package/src/browser/Opfs/debug-utils.ts +7 -6
- package/src/browser/Opfs/utils.ts +36 -26
- package/src/browser/QuotaExceededError.ts +13 -10
- package/src/browser/WebChannelBrowser.ts +25 -11
- package/src/browser/WebError.test.ts +6 -6
- package/src/browser/WebError.ts +107 -133
- package/src/browser/WebLock.ts +13 -14
- package/src/effect/Debug.ts +66 -59
- package/src/effect/Effect.test.ts +117 -0
- package/src/effect/Effect.ts +84 -71
- package/src/effect/Logger.ts +0 -48
- package/src/effect/OtelTracer.ts +2 -3
- package/src/effect/RpcClient.ts +80 -61
- package/src/effect/Schedule.ts +4 -5
- package/src/effect/Schema/debug-diff.test.ts +6 -6
- package/src/effect/Schema/debug-diff.ts +6 -8
- package/src/effect/Schema/index.ts +122 -61
- package/src/effect/Stream.test.ts +9 -9
- package/src/effect/Stream.ts +23 -16
- package/src/effect/Subscribable.ts +31 -42
- package/src/effect/SubscriptionRef.ts +8 -12
- package/src/effect/TaskTracing.ts +13 -16
- package/src/effect/WebChannel/WebChannel.test.ts +23 -23
- package/src/effect/WebChannel/WebChannel.ts +87 -55
- package/src/effect/WebChannel/broadcastChannelWithAck.ts +21 -11
- package/src/effect/WebChannel/common.ts +14 -15
- package/src/effect/WebSocket.test.ts +2 -2
- package/src/effect/WebSocket.ts +9 -6
- package/src/effect/mod.browser-build.test.ts +19 -0
- package/src/effect/mod.ts +35 -69
- package/src/effect/spanEvent.test.ts +10 -13
- package/src/effect/spanEvent.ts +5 -3
- package/src/fast-deep-equal.ts +1 -2
- package/src/misc.ts +2 -2
- package/src/mod.ts +0 -36
- package/src/node/mod.ts +26 -20
- package/src/object/index.ts +0 -24
- package/src/string.ts +0 -3
- package/dist/effect/BucketQueue.d.ts +0 -20
- package/dist/effect/BucketQueue.d.ts.map +0 -1
- package/dist/effect/BucketQueue.js +0 -34
- package/dist/effect/BucketQueue.js.map +0 -1
- package/dist/effect/Scheduler.d.ts +0 -4
- package/dist/effect/Scheduler.d.ts.map +0 -1
- package/dist/effect/Scheduler.js +0 -9
- package/dist/effect/Scheduler.js.map +0 -1
- package/dist/effect/ServiceContext.d.ts +0 -37
- package/dist/effect/ServiceContext.d.ts.map +0 -1
- package/dist/effect/ServiceContext.js +0 -55
- package/dist/effect/ServiceContext.js.map +0 -1
- package/dist/guards.d.ts +0 -20
- package/dist/guards.d.ts.map +0 -1
- package/dist/guards.js +0 -20
- package/dist/guards.js.map +0 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunner.d.ts +0 -4
- package/dist/node/ChildProcessRunner/ChildProcessRunner.d.ts.map +0 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunner.js +0 -128
- package/dist/node/ChildProcessRunner/ChildProcessRunner.js.map +0 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.d.ts +0 -10
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.d.ts.map +0 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.js +0 -217
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.js.map +0 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.d.ts +0 -84
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.d.ts.map +0 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.js +0 -68
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.js.map +0 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.d.ts +0 -2
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.d.ts.map +0 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.js +0 -52
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.js.map +0 -1
- package/dist/node/ChildProcessRunner/ChildProcessWorker.d.ts +0 -23
- package/dist/node/ChildProcessRunner/ChildProcessWorker.d.ts.map +0 -1
- package/dist/node/ChildProcessRunner/ChildProcessWorker.js +0 -138
- package/dist/node/ChildProcessRunner/ChildProcessWorker.js.map +0 -1
- package/dist/object/omit.d.ts +0 -3
- package/dist/object/omit.d.ts.map +0 -1
- package/dist/object/omit.js +0 -14
- package/dist/object/omit.js.map +0 -1
- package/dist/object/pick.d.ts +0 -14
- package/dist/object/pick.d.ts.map +0 -1
- package/dist/object/pick.js +0 -17
- package/dist/object/pick.js.map +0 -1
- package/dist/time.d.ts +0 -12
- package/dist/time.d.ts.map +0 -1
- package/dist/time.js +0 -22
- package/dist/time.js.map +0 -1
- package/src/effect/BucketQueue.ts +0 -49
- package/src/effect/Scheduler.ts +0 -13
- package/src/effect/ServiceContext.ts +0 -108
- package/src/guards.ts +0 -23
- package/src/node/ChildProcessRunner/ChildProcessRunner.ts +0 -148
- package/src/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.ts +0 -311
- package/src/node/ChildProcessRunner/ChildProcessRunnerTest/schema.ts +0 -78
- package/src/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.ts +0 -65
- package/src/node/ChildProcessRunner/ChildProcessWorker.ts +0 -169
- package/src/object/omit.ts +0 -17
- package/src/object/pick.ts +0 -27
- package/src/time.ts +0 -25
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import * as Vitest from '@effect/vitest'
|
|
2
|
-
import { Effect, Schema, Stream } from 'effect'
|
|
2
|
+
import { Effect, Fiber, Schema, Stream } from 'effect'
|
|
3
3
|
import { JSDOM } from 'jsdom'
|
|
4
4
|
|
|
5
5
|
import * as WebChannel from '../../browser/WebChannelBrowser.ts'
|
|
6
6
|
|
|
7
7
|
Vitest.describe('WebChannel', () => {
|
|
8
8
|
Vitest.describe('windowChannel', () => {
|
|
9
|
-
Vitest.
|
|
9
|
+
Vitest.live('should work with 2 windows', () =>
|
|
10
10
|
Effect.gen(function* () {
|
|
11
11
|
const windowA = new JSDOM().window as unknown as globalThis.Window
|
|
12
12
|
const windowB = new JSDOM().window as unknown as globalThis.Window
|
|
@@ -16,19 +16,19 @@ Vitest.describe('WebChannel', () => {
|
|
|
16
16
|
listenWindow: windowA,
|
|
17
17
|
sendWindow: windowB,
|
|
18
18
|
ids: { own: 'a', other: 'b' },
|
|
19
|
-
schema: Schema.
|
|
19
|
+
schema: Schema.Finite,
|
|
20
20
|
})
|
|
21
21
|
|
|
22
22
|
const msgFromBFiber = yield* channelToB.listen.pipe(
|
|
23
|
-
Stream.flatten(),
|
|
24
23
|
Stream.runHead,
|
|
25
|
-
Effect.
|
|
26
|
-
Effect.
|
|
24
|
+
Effect.flatMap(Effect.fromOption),
|
|
25
|
+
Effect.flatMap(Effect.fromResult),
|
|
26
|
+
Effect.forkChild,
|
|
27
27
|
)
|
|
28
28
|
|
|
29
29
|
yield* channelToB.send(1)
|
|
30
30
|
|
|
31
|
-
Vitest.expect(yield* msgFromBFiber).toEqual(2)
|
|
31
|
+
Vitest.expect(yield* Fiber.join(msgFromBFiber)).toEqual(2)
|
|
32
32
|
})
|
|
33
33
|
|
|
34
34
|
const codeSideB = Effect.gen(function* () {
|
|
@@ -36,26 +36,26 @@ Vitest.describe('WebChannel', () => {
|
|
|
36
36
|
listenWindow: windowB,
|
|
37
37
|
sendWindow: windowA,
|
|
38
38
|
ids: { own: 'b', other: 'a' },
|
|
39
|
-
schema: Schema.
|
|
39
|
+
schema: Schema.Finite,
|
|
40
40
|
})
|
|
41
41
|
|
|
42
42
|
const msgFromAFiber = yield* channelToA.listen.pipe(
|
|
43
|
-
Stream.flatten(),
|
|
44
43
|
Stream.runHead,
|
|
45
|
-
Effect.
|
|
46
|
-
Effect.
|
|
44
|
+
Effect.flatMap(Effect.fromOption),
|
|
45
|
+
Effect.flatMap(Effect.fromResult),
|
|
46
|
+
Effect.forkChild,
|
|
47
47
|
)
|
|
48
48
|
|
|
49
49
|
yield* channelToA.send(2)
|
|
50
50
|
|
|
51
|
-
Vitest.expect(yield* msgFromAFiber).toEqual(1)
|
|
51
|
+
Vitest.expect(yield* Fiber.join(msgFromAFiber)).toEqual(1)
|
|
52
52
|
})
|
|
53
53
|
|
|
54
54
|
yield* Effect.all([codeSideA, codeSideB], { concurrency: 'unbounded' })
|
|
55
55
|
}),
|
|
56
56
|
)
|
|
57
57
|
|
|
58
|
-
Vitest.
|
|
58
|
+
Vitest.live('should work with the same window', () =>
|
|
59
59
|
Effect.gen(function* () {
|
|
60
60
|
const window = new JSDOM().window as unknown as globalThis.Window
|
|
61
61
|
|
|
@@ -64,19 +64,19 @@ Vitest.describe('WebChannel', () => {
|
|
|
64
64
|
listenWindow: window,
|
|
65
65
|
sendWindow: window,
|
|
66
66
|
ids: { own: 'a', other: 'b' },
|
|
67
|
-
schema: Schema.
|
|
67
|
+
schema: Schema.Finite,
|
|
68
68
|
})
|
|
69
69
|
|
|
70
70
|
const msgFromBFiber = yield* channelToB.listen.pipe(
|
|
71
|
-
Stream.flatten(),
|
|
72
71
|
Stream.runHead,
|
|
73
|
-
Effect.
|
|
74
|
-
Effect.
|
|
72
|
+
Effect.flatMap(Effect.fromOption),
|
|
73
|
+
Effect.flatMap(Effect.fromResult),
|
|
74
|
+
Effect.forkChild,
|
|
75
75
|
)
|
|
76
76
|
|
|
77
77
|
yield* channelToB.send(1)
|
|
78
78
|
|
|
79
|
-
Vitest.expect(yield* msgFromBFiber).toEqual(2)
|
|
79
|
+
Vitest.expect(yield* Fiber.join(msgFromBFiber)).toEqual(2)
|
|
80
80
|
})
|
|
81
81
|
|
|
82
82
|
const codeSideB = Effect.gen(function* () {
|
|
@@ -84,19 +84,19 @@ Vitest.describe('WebChannel', () => {
|
|
|
84
84
|
listenWindow: window,
|
|
85
85
|
sendWindow: window,
|
|
86
86
|
ids: { own: 'b', other: 'a' },
|
|
87
|
-
schema: Schema.
|
|
87
|
+
schema: Schema.Finite,
|
|
88
88
|
})
|
|
89
89
|
|
|
90
90
|
const msgFromAFiber = yield* channelToA.listen.pipe(
|
|
91
|
-
Stream.flatten(),
|
|
92
91
|
Stream.runHead,
|
|
93
|
-
Effect.
|
|
94
|
-
Effect.
|
|
92
|
+
Effect.flatMap(Effect.fromOption),
|
|
93
|
+
Effect.flatMap(Effect.fromResult),
|
|
94
|
+
Effect.forkChild,
|
|
95
95
|
)
|
|
96
96
|
|
|
97
97
|
yield* channelToA.send(2)
|
|
98
98
|
|
|
99
|
-
Vitest.expect(yield* msgFromAFiber).toEqual(1)
|
|
99
|
+
Vitest.expect(yield* Fiber.join(msgFromAFiber)).toEqual(1)
|
|
100
100
|
})
|
|
101
101
|
|
|
102
102
|
yield* Effect.all([codeSideA, codeSideB], { concurrency: 'unbounded' })
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import type { Duration } from 'effect'
|
|
2
|
+
import { Cause, Deferred, Result, Exit, Filter, identity, Option, PubSub, Queue, Scope } from 'effect'
|
|
3
3
|
|
|
4
4
|
import { shouldNeverHappen } from '../../misc.ts'
|
|
5
5
|
import * as Effect from '../Effect.ts'
|
|
@@ -27,7 +27,7 @@ export const noopChannel = <MsgListen, MsgSend>(): Effect.Effect<WebChannel<MsgL
|
|
|
27
27
|
[WebChannelSymbol]: WebChannelSymbol,
|
|
28
28
|
send: () => Effect.void,
|
|
29
29
|
listen: Stream.never,
|
|
30
|
-
closedDeferred: yield* Deferred.make<void>()
|
|
30
|
+
closedDeferred: yield* Effect.acquireRelease(Deferred.make<void>(), Deferred.done(Exit.void)),
|
|
31
31
|
shutdown: Scope.close(scope, Exit.succeed('shutdown')),
|
|
32
32
|
schema: {
|
|
33
33
|
listen: Schema.Any,
|
|
@@ -49,15 +49,25 @@ export const messagePortChannel: <MsgListen, MsgSend, MsgListenEncoded, MsgSendE
|
|
|
49
49
|
|
|
50
50
|
const label = debugId === undefined ? 'messagePort' : `messagePort:${debugId}`
|
|
51
51
|
|
|
52
|
+
// In Effect v4, forking a consumer is not a readiness handshake. Install the MessagePort
|
|
53
|
+
// listener while constructing the channel and buffer into a scoped queue so early messages
|
|
54
|
+
// are not lost before `listen` is pulled.
|
|
55
|
+
const messageQueue = yield* Effect.acquireRelease(Queue.unbounded<MessageDataEvent>(), Queue.shutdown)
|
|
56
|
+
const handler = (event: MessageDataEvent) => {
|
|
57
|
+
Queue.offerUnsafe(messageQueue, event)
|
|
58
|
+
}
|
|
59
|
+
port.addEventListener('message', handler)
|
|
60
|
+
yield* Effect.addFinalizer(() => Effect.sync(() => port.removeEventListener('message', handler)))
|
|
61
|
+
|
|
52
62
|
const send = (message: any) =>
|
|
53
63
|
Effect.gen(function* () {
|
|
54
64
|
const [messageEncoded, transferables] = yield* Schema.encodeWithTransferables(schema.send)(message)
|
|
55
65
|
port.postMessage(messageEncoded, transferables)
|
|
56
66
|
})
|
|
57
67
|
|
|
58
|
-
const listen = Stream.
|
|
68
|
+
const listen = Stream.fromQueue(messageQueue).pipe(
|
|
59
69
|
// Stream.tap((_) => Effect.log(`${label}:message`, _.data)),
|
|
60
|
-
Stream.map((_) => Schema.
|
|
70
|
+
Stream.map((_) => Schema.decodeResult(schema.listen)(_.data)),
|
|
61
71
|
listenToDebugPing(label),
|
|
62
72
|
)
|
|
63
73
|
|
|
@@ -65,10 +75,15 @@ export const messagePortChannel: <MsgListen, MsgSend, MsgListenEncoded, MsgSendE
|
|
|
65
75
|
|
|
66
76
|
port.start()
|
|
67
77
|
|
|
68
|
-
const closedDeferred = yield* Deferred.make<void>()
|
|
78
|
+
const closedDeferred = yield* Effect.acquireRelease(Deferred.make<void>(), Deferred.done(Exit.void))
|
|
69
79
|
const supportsTransferables = true
|
|
70
80
|
|
|
71
|
-
yield* Effect.addFinalizer(() =>
|
|
81
|
+
yield* Effect.addFinalizer(() =>
|
|
82
|
+
Effect.try({
|
|
83
|
+
try: () => port.close(),
|
|
84
|
+
catch: (cause) => new Cause.UnknownError(cause),
|
|
85
|
+
}).pipe(Effect.ignore),
|
|
86
|
+
)
|
|
72
87
|
|
|
73
88
|
return {
|
|
74
89
|
[WebChannelSymbol]: WebChannelSymbol,
|
|
@@ -82,10 +97,7 @@ export const messagePortChannel: <MsgListen, MsgSend, MsgListenEncoded, MsgSendE
|
|
|
82
97
|
}).pipe(Effect.withSpan(`WebChannel:messagePortChannel`)),
|
|
83
98
|
)
|
|
84
99
|
|
|
85
|
-
const sameThreadChannels =
|
|
86
|
-
'livestore:sameThreadChannels',
|
|
87
|
-
() => new Map<string, PubSub.PubSub<any>>(),
|
|
88
|
-
)
|
|
100
|
+
const sameThreadChannels = new Map<string, PubSub.PubSub<any>>()
|
|
89
101
|
|
|
90
102
|
export const sameThreadChannel = <MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded>({
|
|
91
103
|
schema: inputSchema,
|
|
@@ -98,7 +110,7 @@ export const sameThreadChannel = <MsgListen, MsgSend, MsgListenEncoded, MsgSendE
|
|
|
98
110
|
Effect.gen(function* () {
|
|
99
111
|
let pubSub = sameThreadChannels.get(channelName)
|
|
100
112
|
if (pubSub === undefined) {
|
|
101
|
-
pubSub = yield* PubSub.unbounded<any>()
|
|
113
|
+
pubSub = yield* Effect.acquireRelease(PubSub.unbounded<any>(), PubSub.shutdown)
|
|
102
114
|
sameThreadChannels.set(channelName, pubSub)
|
|
103
115
|
}
|
|
104
116
|
|
|
@@ -106,9 +118,9 @@ export const sameThreadChannel = <MsgListen, MsgSend, MsgListenEncoded, MsgSendE
|
|
|
106
118
|
|
|
107
119
|
const send = (message: MsgSend) => PubSub.publish(pubSub, message)
|
|
108
120
|
|
|
109
|
-
const listen = Stream.fromPubSub(pubSub).pipe(Stream.map(
|
|
121
|
+
const listen = Stream.fromPubSub(pubSub).pipe(Stream.map(Result.succeed), listenToDebugPing(channelName))
|
|
110
122
|
|
|
111
|
-
const closedDeferred = yield* Deferred.make<void>()
|
|
123
|
+
const closedDeferred = yield* Effect.acquireRelease(Deferred.make<void>(), Deferred.done(Exit.void))
|
|
112
124
|
|
|
113
125
|
return {
|
|
114
126
|
[WebChannelSymbol]: WebChannelSymbol,
|
|
@@ -138,12 +150,12 @@ export const messagePortChannelWithAck: <MsgListen, MsgSend, MsgListenEncoded, M
|
|
|
138
150
|
|
|
139
151
|
const ChannelRequest = Schema.TaggedStruct('ChannelRequest', {
|
|
140
152
|
id: Schema.String,
|
|
141
|
-
payload: Schema.Union(schema.listen, schema.send),
|
|
142
|
-
}).
|
|
153
|
+
payload: Schema.Union([schema.listen, schema.send]),
|
|
154
|
+
}).annotate({ title: 'webmesh.ChannelRequest' })
|
|
143
155
|
const ChannelRequestAck = Schema.TaggedStruct('ChannelRequestAck', {
|
|
144
156
|
reqId: Schema.String,
|
|
145
|
-
}).
|
|
146
|
-
const ChannelMessage = Schema.Union(ChannelRequest, ChannelRequestAck).
|
|
157
|
+
}).annotate({ title: 'webmesh.ChannelRequestAck' })
|
|
158
|
+
const ChannelMessage = Schema.Union([ChannelRequest, ChannelRequestAck]).annotate({
|
|
147
159
|
title: 'webmesh.ChannelMessage',
|
|
148
160
|
})
|
|
149
161
|
|
|
@@ -154,6 +166,15 @@ export const messagePortChannelWithAck: <MsgListen, MsgSend, MsgListenEncoded, M
|
|
|
154
166
|
id: debugId,
|
|
155
167
|
}
|
|
156
168
|
|
|
169
|
+
// The ack channel sends protocol messages during setup. Buffer MessagePort events eagerly so
|
|
170
|
+
// the initial ping/pong cannot race a lazily-started `listen` stream under Effect v4.
|
|
171
|
+
const messageQueue = yield* Effect.acquireRelease(Queue.unbounded<MessageDataEvent>(), Queue.shutdown)
|
|
172
|
+
const handler = (event: MessageDataEvent) => {
|
|
173
|
+
Queue.offerUnsafe(messageQueue, event)
|
|
174
|
+
}
|
|
175
|
+
port.addEventListener('message', handler)
|
|
176
|
+
yield* Effect.addFinalizer(() => Effect.sync(() => port.removeEventListener('message', handler)))
|
|
177
|
+
|
|
157
178
|
const send = (message: any) =>
|
|
158
179
|
Effect.gen(function* () {
|
|
159
180
|
debugInfo.sendTotal++
|
|
@@ -171,48 +192,56 @@ export const messagePortChannelWithAck: <MsgListen, MsgSend, MsgListenEncoded, M
|
|
|
171
192
|
|
|
172
193
|
port.postMessage(messageEncoded, transferables)
|
|
173
194
|
|
|
174
|
-
yield* ack
|
|
195
|
+
yield* Deferred.await(ack)
|
|
175
196
|
|
|
176
197
|
requestAckMap.delete(id)
|
|
177
198
|
|
|
178
199
|
debugInfo.sendPending--
|
|
179
200
|
})
|
|
180
201
|
|
|
181
|
-
|
|
182
|
-
// https://github.com/livestorejs/livestore/issues/262
|
|
183
|
-
const listen = Stream.fromEventListener<MessageEvent>(port, 'message').pipe(
|
|
202
|
+
const listen = Stream.fromQueue(messageQueue).pipe(
|
|
184
203
|
// Stream.onStart(Effect.log(`${label}:listen:start`)),
|
|
185
204
|
// Stream.tap((_) => Effect.log(`${label}:message`, _.data)),
|
|
186
|
-
Stream.map((_) => Schema.
|
|
205
|
+
Stream.map((_) => Schema.decodeResult(ChannelMessage)(_.data)),
|
|
187
206
|
Stream.tap((msg) =>
|
|
188
207
|
Effect.gen(function* () {
|
|
189
|
-
if (msg
|
|
190
|
-
if (msg.
|
|
191
|
-
yield* Deferred.succeed(requestAckMap.get(msg.
|
|
192
|
-
} else if (msg.
|
|
208
|
+
if (Result.isSuccess(msg) === true) {
|
|
209
|
+
if (msg.success._tag === 'ChannelRequestAck') {
|
|
210
|
+
yield* Deferred.succeed(requestAckMap.get(msg.success.reqId)!, void 0)
|
|
211
|
+
} else if (msg.success._tag === 'ChannelRequest') {
|
|
193
212
|
debugInfo.listenTotal++
|
|
194
|
-
port.postMessage(
|
|
213
|
+
port.postMessage(
|
|
214
|
+
yield* Schema.encodeEffect(ChannelMessage)({ _tag: 'ChannelRequestAck', reqId: msg.success.id }).pipe(
|
|
215
|
+
Effect.orDie,
|
|
216
|
+
),
|
|
217
|
+
)
|
|
195
218
|
}
|
|
196
219
|
}
|
|
197
220
|
}),
|
|
198
221
|
),
|
|
199
|
-
Stream.filterMap(
|
|
200
|
-
msg
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
222
|
+
Stream.filterMap(
|
|
223
|
+
Filter.fromPredicateOption((msg) =>
|
|
224
|
+
Result.isFailure(msg) === true
|
|
225
|
+
? Option.some(msg as Result.Result<any, any>)
|
|
226
|
+
: msg.success._tag === 'ChannelRequest'
|
|
227
|
+
? Option.some(Result.succeed(msg.success.payload) as Result.Result<any, any>)
|
|
228
|
+
: Option.none(),
|
|
229
|
+
),
|
|
205
230
|
),
|
|
206
|
-
(_) => _ as Stream.Stream<Either.Either<any, any>>,
|
|
207
231
|
listenToDebugPing(label),
|
|
208
232
|
)
|
|
209
233
|
|
|
210
234
|
port.start()
|
|
211
235
|
|
|
212
|
-
const closedDeferred = yield* Deferred.make<void>()
|
|
236
|
+
const closedDeferred = yield* Effect.acquireRelease(Deferred.make<void>(), Deferred.done(Exit.void))
|
|
213
237
|
const supportsTransferables = true
|
|
214
238
|
|
|
215
|
-
yield* Effect.addFinalizer(() =>
|
|
239
|
+
yield* Effect.addFinalizer(() =>
|
|
240
|
+
Effect.try({
|
|
241
|
+
try: () => port.close(),
|
|
242
|
+
catch: (cause) => new Cause.UnknownError(cause),
|
|
243
|
+
}).pipe(Effect.ignore),
|
|
244
|
+
)
|
|
216
245
|
|
|
217
246
|
return {
|
|
218
247
|
[WebChannelSymbol]: WebChannelSymbol,
|
|
@@ -227,6 +256,9 @@ export const messagePortChannelWithAck: <MsgListen, MsgSend, MsgListenEncoded, M
|
|
|
227
256
|
}).pipe(Effect.withSpan(`WebChannel:messagePortChannelWithAck`)),
|
|
228
257
|
)
|
|
229
258
|
|
|
259
|
+
/** DOM and Node worker_threads expose incompatible MessageEvent globals; the channel only needs payload data. */
|
|
260
|
+
type MessageDataEvent = Event & { readonly data: any }
|
|
261
|
+
|
|
230
262
|
export type QueueChannelProxy<MsgListen, MsgSend> = {
|
|
231
263
|
/** Only meant to be used externally */
|
|
232
264
|
webChannel: WebChannel<MsgListen, MsgSend>
|
|
@@ -250,19 +282,19 @@ export const queueChannelProxy = <MsgListen, MsgSend>({
|
|
|
250
282
|
schema: inputSchema,
|
|
251
283
|
}: {
|
|
252
284
|
schema:
|
|
253
|
-
| Schema.
|
|
254
|
-
| { listen: Schema.
|
|
285
|
+
| Schema.Codec<MsgListen | MsgSend, any>
|
|
286
|
+
| { listen: Schema.Codec<MsgListen, any>; send: Schema.Codec<MsgSend, any> }
|
|
255
287
|
}): Effect.Effect<QueueChannelProxy<MsgListen, MsgSend>, never, Scope.Scope> =>
|
|
256
288
|
Effect.scopeWithCloseable((scope) =>
|
|
257
289
|
Effect.gen(function* () {
|
|
258
|
-
const sendQueue = yield* Queue.unbounded<MsgSend>()
|
|
259
|
-
const listenQueue = yield* Queue.unbounded<MsgListen>()
|
|
290
|
+
const sendQueue = yield* Effect.acquireRelease(Queue.unbounded<MsgSend>(), Queue.shutdown)
|
|
291
|
+
const listenQueue = yield* Effect.acquireRelease(Queue.unbounded<MsgListen>(), Queue.shutdown)
|
|
260
292
|
|
|
261
293
|
const send = (message: MsgSend) => Queue.offer(sendQueue, message)
|
|
262
294
|
|
|
263
|
-
const listen = Stream.fromQueue(listenQueue).pipe(Stream.map(
|
|
295
|
+
const listen = Stream.fromQueue(listenQueue).pipe(Stream.map(Result.succeed), listenToDebugPing('queueChannel'))
|
|
264
296
|
|
|
265
|
-
const closedDeferred = yield* Deferred.make<void>()
|
|
297
|
+
const closedDeferred = yield* Effect.acquireRelease(Deferred.make<void>(), Deferred.done(Exit.void))
|
|
266
298
|
const supportsTransferables = true
|
|
267
299
|
|
|
268
300
|
const schema = mapSchema(inputSchema)
|
|
@@ -292,13 +324,13 @@ export const toOpenChannel = <MsgListen, MsgSend>(
|
|
|
292
324
|
* If the other end doesn't respond within `timeout` milliseconds, the channel is shutdown.
|
|
293
325
|
*/
|
|
294
326
|
heartbeat?: {
|
|
295
|
-
interval:
|
|
296
|
-
timeout:
|
|
327
|
+
interval: Duration.Input
|
|
328
|
+
timeout: Duration.Input
|
|
297
329
|
}
|
|
298
330
|
},
|
|
299
331
|
): Effect.Effect<WebChannel<MsgListen, MsgSend>, never, Scope.Scope> =>
|
|
300
332
|
Effect.gen(function* () {
|
|
301
|
-
const queue = yield* Queue.unbounded<
|
|
333
|
+
const queue = yield* Effect.acquireRelease(Queue.unbounded<Result.Result<MsgListen, any>>(), Queue.shutdown)
|
|
302
334
|
|
|
303
335
|
const heartbeatChannel = channel as WebChannel<
|
|
304
336
|
MsgListen | typeof WebChannelHeartbeat.Type,
|
|
@@ -314,13 +346,13 @@ export const toOpenChannel = <MsgListen, MsgSend>(
|
|
|
314
346
|
options?.heartbeat !== undefined
|
|
315
347
|
? Stream.filterEffect(
|
|
316
348
|
Effect.fn(function* (msg) {
|
|
317
|
-
if (msg
|
|
318
|
-
if (msg.
|
|
319
|
-
yield* heartbeatChannel.send(WebChannelPong.make({ requestId: msg.
|
|
349
|
+
if (Result.isSuccess(msg) === true && Schema.is(WebChannelHeartbeat)(msg.success) === true) {
|
|
350
|
+
if (msg.success._tag === 'WebChannel.Ping') {
|
|
351
|
+
yield* heartbeatChannel.send(WebChannelPong.make({ requestId: msg.success.requestId }))
|
|
320
352
|
} else {
|
|
321
353
|
const { deferred, requestId } = pendingPingDeferredRef.current ?? shouldNeverHappen('No pending ping')
|
|
322
|
-
if (requestId !== msg.
|
|
323
|
-
shouldNeverHappen('Received pong for unexpected requestId', requestId, msg.
|
|
354
|
+
if (requestId !== msg.success.requestId) {
|
|
355
|
+
shouldNeverHappen('Received pong for unexpected requestId', requestId, msg.success.requestId)
|
|
324
356
|
}
|
|
325
357
|
yield* Deferred.succeed(deferred, void 0)
|
|
326
358
|
}
|
|
@@ -331,7 +363,7 @@ export const toOpenChannel = <MsgListen, MsgSend>(
|
|
|
331
363
|
}),
|
|
332
364
|
)
|
|
333
365
|
: identity,
|
|
334
|
-
Stream.
|
|
366
|
+
Stream.tapArray((array) => Queue.offerAll(queue, array)),
|
|
335
367
|
Stream.runDrain,
|
|
336
368
|
Effect.forkScoped,
|
|
337
369
|
)
|
|
@@ -345,9 +377,9 @@ export const toOpenChannel = <MsgListen, MsgSend>(
|
|
|
345
377
|
yield* heartbeatChannel.send(WebChannelPing.make({ requestId }))
|
|
346
378
|
const deferred = yield* Deferred.make<void>()
|
|
347
379
|
pendingPingDeferredRef.current = { deferred, requestId }
|
|
348
|
-
yield* deferred.pipe(
|
|
380
|
+
yield* Deferred.await(deferred).pipe(
|
|
349
381
|
Effect.timeout(timeout),
|
|
350
|
-
Effect.catchTag('
|
|
382
|
+
Effect.catchTag('TimeoutError', () => channel.shutdown),
|
|
351
383
|
)
|
|
352
384
|
}
|
|
353
385
|
}).pipe(Effect.withSpan(`WebChannel:heartbeat`), Effect.forkScoped)
|
|
@@ -356,7 +388,7 @@ export const toOpenChannel = <MsgListen, MsgSend>(
|
|
|
356
388
|
// We're currently limiting the chunk size to 1 to not drop messages in scearnios where
|
|
357
389
|
// the listen stream get subscribed to, only take N messages and then unsubscribe.
|
|
358
390
|
// Without this limit, messages would be dropped.
|
|
359
|
-
const listen = Stream.fromQueue(queue
|
|
391
|
+
const listen = Stream.fromQueue(queue).pipe(Stream.rechunk(1))
|
|
360
392
|
|
|
361
393
|
return {
|
|
362
394
|
[WebChannelSymbol]: WebChannelSymbol,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { Deferred, Exit, Predicate, Queue,
|
|
1
|
+
import { Deferred, Exit, Filter, Latch, Predicate, Queue, Scope, Stream } from 'effect'
|
|
2
2
|
|
|
3
3
|
import * as Effect from '../Effect.ts'
|
|
4
|
+
import * as Schema from '../Schema/index.ts'
|
|
4
5
|
import type { InputSchema, WebChannel } from './common.ts'
|
|
5
6
|
import { listenToDebugPing, mapSchema, WebChannelSymbol } from './common.ts'
|
|
6
7
|
|
|
@@ -23,7 +24,7 @@ const PayloadMessage = Schema.TaggedStruct('PayloadMessage', {
|
|
|
23
24
|
payload: Schema.Any,
|
|
24
25
|
})
|
|
25
26
|
|
|
26
|
-
const Message = Schema.Union(ConnectMessage, ConnectAckMessage, DisconnectMessage, PayloadMessage)
|
|
27
|
+
const Message = Schema.Union([ConnectMessage, ConnectAckMessage, DisconnectMessage, PayloadMessage])
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
30
|
* Same as `broadcastChannel`, but with a queue in between to guarantee message delivery and meant
|
|
@@ -39,28 +40,35 @@ export const broadcastChannelWithAck = <MsgListen, MsgSend, MsgListenEncoded, Ms
|
|
|
39
40
|
Effect.scopeWithCloseable((scope) =>
|
|
40
41
|
Effect.gen(function* () {
|
|
41
42
|
const channel = new BroadcastChannel(channelName)
|
|
42
|
-
|
|
43
|
+
// BroadcastChannel messages are dropped unless a listener is already registered. Effect v4
|
|
44
|
+
// makes stream startup ordering explicit, so buffer events from channel construction time.
|
|
45
|
+
const messageQueue = yield* Effect.acquireRelease(Queue.unbounded<MessageDataEvent>(), Queue.shutdown)
|
|
43
46
|
const connectionId = crypto.randomUUID()
|
|
44
47
|
const schema = mapSchema(inputSchema)
|
|
45
48
|
|
|
46
49
|
const peerIdRef = { current: undefined as undefined | string }
|
|
47
|
-
const connectedLatch = yield*
|
|
50
|
+
const connectedLatch = yield* Latch.make(false)
|
|
48
51
|
const supportsTransferables = false
|
|
49
52
|
|
|
50
53
|
const postMessage = (msg: typeof Message.Type) => channel.postMessage(Schema.encodeSync(Message)(msg))
|
|
51
54
|
|
|
55
|
+
const handler = (event: MessageDataEvent) => {
|
|
56
|
+
Queue.offerUnsafe(messageQueue, event)
|
|
57
|
+
}
|
|
58
|
+
channel.addEventListener('message', handler)
|
|
59
|
+
yield* Effect.addFinalizer(() => Effect.sync(() => channel.removeEventListener('message', handler)))
|
|
60
|
+
|
|
52
61
|
const send = (message: MsgSend) =>
|
|
53
62
|
Effect.gen(function* () {
|
|
54
63
|
yield* connectedLatch.await
|
|
55
64
|
|
|
56
|
-
const payload = yield* Schema.
|
|
65
|
+
const payload = yield* Schema.encodeEffect(schema.send)(message)
|
|
57
66
|
postMessage(PayloadMessage.make({ from: connectionId, to: peerIdRef.current!, payload }))
|
|
58
67
|
})
|
|
59
68
|
|
|
60
|
-
const listen = Stream.
|
|
69
|
+
const listen = Stream.fromQueue(messageQueue).pipe(
|
|
61
70
|
Stream.map(({ data }) => data),
|
|
62
|
-
Stream.
|
|
63
|
-
Stream.filterMap((_) => _),
|
|
71
|
+
Stream.filterMap(Filter.fromPredicateOption(Schema.decodeUnknownOption(Message))),
|
|
64
72
|
Stream.mapEffect((data) =>
|
|
65
73
|
Effect.gen(function* () {
|
|
66
74
|
switch (data._tag) {
|
|
@@ -89,7 +97,7 @@ export const broadcastChannelWithAck = <MsgListen, MsgSend, MsgListenEncoded, Ms
|
|
|
89
97
|
}
|
|
90
98
|
case 'PayloadMessage': {
|
|
91
99
|
if (data.to === connectionId) {
|
|
92
|
-
return Schema.
|
|
100
|
+
return Schema.decodeResult(schema.listen)(data.payload)
|
|
93
101
|
}
|
|
94
102
|
return undefined
|
|
95
103
|
}
|
|
@@ -110,11 +118,10 @@ export const broadcastChannelWithAck = <MsgListen, MsgSend, MsgListenEncoded, Ms
|
|
|
110
118
|
Effect.gen(function* () {
|
|
111
119
|
postMessage(DisconnectMessage.make({ from: connectionId }))
|
|
112
120
|
channel.close()
|
|
113
|
-
yield* Queue.shutdown(messageQueue)
|
|
114
121
|
}),
|
|
115
122
|
)
|
|
116
123
|
|
|
117
|
-
const closedDeferred = yield* Deferred.make<void>()
|
|
124
|
+
const closedDeferred = yield* Effect.acquireRelease(Deferred.make<void>(), Deferred.done(Exit.void))
|
|
118
125
|
|
|
119
126
|
return {
|
|
120
127
|
[WebChannelSymbol]: WebChannelSymbol,
|
|
@@ -127,3 +134,6 @@ export const broadcastChannelWithAck = <MsgListen, MsgSend, MsgListenEncoded, Ms
|
|
|
127
134
|
}
|
|
128
135
|
}).pipe(Effect.withSpan(`WebChannel:broadcastChannelWithAck(${channelName})`)),
|
|
129
136
|
)
|
|
137
|
+
|
|
138
|
+
/** DOM and Node worker_threads expose incompatible MessageEvent globals; the channel only needs payload data. */
|
|
139
|
+
type MessageDataEvent = Event & { readonly data: any }
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import { Effect, Predicate, Schema, Stream } from 'effect'
|
|
1
|
+
import { type Deferred, Effect, Predicate, Result, Schema, Stream } from 'effect'
|
|
3
2
|
|
|
4
3
|
export const WebChannelSymbol = Symbol('WebChannel')
|
|
5
4
|
export type WebChannelSymbol = typeof WebChannelSymbol
|
|
@@ -9,12 +8,12 @@ export const isWebChannel = <MsgListen, MsgSend>(value: unknown): value is WebCh
|
|
|
9
8
|
|
|
10
9
|
export interface WebChannel<MsgListen, MsgSend, E = never> {
|
|
11
10
|
readonly [WebChannelSymbol]: unknown
|
|
12
|
-
send: (a: MsgSend) => Effect.Effect<void,
|
|
13
|
-
listen: Stream.Stream<
|
|
11
|
+
send: (a: MsgSend) => Effect.Effect<void, Schema.SchemaError | E>
|
|
12
|
+
listen: Stream.Stream<Result.Result<MsgListen, Schema.SchemaError>, E>
|
|
14
13
|
supportsTransferables: boolean
|
|
15
14
|
closedDeferred: Deferred.Deferred<void>
|
|
16
15
|
shutdown: Effect.Effect<void>
|
|
17
|
-
schema: { listen: Schema.
|
|
16
|
+
schema: { listen: Schema.Codec<MsgListen, any>; send: Schema.Codec<MsgSend, any> }
|
|
18
17
|
debugInfo?: Record<string, any> | undefined
|
|
19
18
|
}
|
|
20
19
|
|
|
@@ -31,24 +30,24 @@ export const WebChannelPong = Schema.TaggedStruct('WebChannel.Pong', {
|
|
|
31
30
|
requestId: Schema.String,
|
|
32
31
|
})
|
|
33
32
|
|
|
34
|
-
export const WebChannelHeartbeat = Schema.Union(WebChannelPing, WebChannelPong)
|
|
33
|
+
export const WebChannelHeartbeat = Schema.Union([WebChannelPing, WebChannelPong])
|
|
35
34
|
|
|
36
35
|
type WebChannelMessages = typeof DebugPingMessage.Type | typeof WebChannelPing.Type | typeof WebChannelPong.Type
|
|
37
36
|
|
|
38
37
|
export const schemaWithWebChannelMessages = <MsgListen, MsgSend>(
|
|
39
38
|
schema: OutputSchema<MsgListen, MsgSend, any, any>,
|
|
40
39
|
): OutputSchema<MsgListen | WebChannelMessages, MsgSend | WebChannelMessages, any, any> => ({
|
|
41
|
-
send: Schema.Union(schema.send, DebugPingMessage, WebChannelPing, WebChannelPong),
|
|
42
|
-
listen: Schema.Union(schema.listen, DebugPingMessage, WebChannelPing, WebChannelPong),
|
|
40
|
+
send: Schema.Union([schema.send, DebugPingMessage, WebChannelPing, WebChannelPong]),
|
|
41
|
+
listen: Schema.Union([schema.listen, DebugPingMessage, WebChannelPing, WebChannelPong]),
|
|
43
42
|
})
|
|
44
43
|
|
|
45
44
|
export type InputSchema<MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded> =
|
|
46
|
-
| Schema.
|
|
45
|
+
| Schema.Codec<MsgListen | MsgSend, MsgListenEncoded | MsgSendEncoded>
|
|
47
46
|
| OutputSchema<MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded>
|
|
48
47
|
|
|
49
48
|
export type OutputSchema<MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded> = {
|
|
50
|
-
listen: Schema.
|
|
51
|
-
send: Schema.
|
|
49
|
+
listen: Schema.Codec<MsgListen, MsgListenEncoded>
|
|
50
|
+
send: Schema.Codec<MsgSend, MsgSendEncoded>
|
|
52
51
|
}
|
|
53
52
|
|
|
54
53
|
export const mapSchema = <MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded>(
|
|
@@ -61,13 +60,13 @@ export const mapSchema = <MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded>(
|
|
|
61
60
|
export const listenToDebugPing =
|
|
62
61
|
(channelName: string) =>
|
|
63
62
|
<MsgListen>(
|
|
64
|
-
stream: Stream.Stream<
|
|
65
|
-
): Stream.Stream<
|
|
63
|
+
stream: Stream.Stream<Result.Result<MsgListen, Schema.SchemaError>>,
|
|
64
|
+
): Stream.Stream<Result.Result<MsgListen, Schema.SchemaError>> =>
|
|
66
65
|
stream.pipe(
|
|
67
66
|
Stream.filterEffect(
|
|
68
67
|
Effect.fn(function* (msg) {
|
|
69
|
-
if (msg
|
|
70
|
-
yield* Effect.logDebug(`WebChannel:ping [${channelName}] ${msg.
|
|
68
|
+
if (Result.isSuccess(msg) === true && Schema.is(DebugPingMessage)(msg.success) === true) {
|
|
69
|
+
yield* Effect.logDebug(`WebChannel:ping [${channelName}] ${msg.success.message}`, msg.success.payload)
|
|
71
70
|
return false
|
|
72
71
|
}
|
|
73
72
|
return true
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { FetchHttpClient } from '@effect/platform'
|
|
2
1
|
import * as Vitest from '@effect/vitest'
|
|
3
2
|
import { Effect, Exit } from 'effect'
|
|
3
|
+
import { FetchHttpClient } from 'effect/unstable/http'
|
|
4
4
|
|
|
5
5
|
import { makeWebSocket } from './WebSocket.ts'
|
|
6
6
|
|
|
7
7
|
Vitest.describe('WebSocket', () => {
|
|
8
|
-
Vitest.
|
|
8
|
+
Vitest.live(
|
|
9
9
|
'should create a WebSocket connection',
|
|
10
10
|
Effect.fn(function* () {
|
|
11
11
|
const exit = yield* makeWebSocket({ url: 'ws://localhost:1000' }).pipe(Effect.timeout(500), Effect.exit)
|
package/src/effect/WebSocket.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import { HttpClient } from '@effect/platform'
|
|
2
1
|
import type { Schedule, Scope } from 'effect'
|
|
3
2
|
import { Effect, Exit, identity, Schema } from 'effect'
|
|
3
|
+
import { HttpClient } from 'effect/unstable/http'
|
|
4
4
|
|
|
5
|
-
export class WebSocketError extends Schema.TaggedError<WebSocketError>('~@livestore/utils/WebSocketError')(
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
export class WebSocketError extends Schema.TaggedError<WebSocketError>('~@livestore/utils/WebSocketError')(
|
|
6
|
+
'WebSocketError',
|
|
7
|
+
{
|
|
8
|
+
cause: Schema.Defect(),
|
|
9
|
+
},
|
|
10
|
+
) {}
|
|
8
11
|
|
|
9
12
|
// TODO refactor using Effect socket implementation
|
|
10
13
|
// https://github.com/Effect-TS/effect/blob/main/packages%2Fexperimental%2Fsrc%2FDevTools%2FClient.ts#L113
|
|
@@ -25,7 +28,7 @@ export const makeWebSocket = ({
|
|
|
25
28
|
Effect.gen(function* () {
|
|
26
29
|
yield* validateUrl(url)
|
|
27
30
|
|
|
28
|
-
const socket = yield* Effect.
|
|
31
|
+
const socket = yield* Effect.callback<globalThis.WebSocket, WebSocketError>((cb, signal) => {
|
|
29
32
|
try {
|
|
30
33
|
const socket = new globalThis.WebSocket(url)
|
|
31
34
|
|
|
@@ -63,7 +66,7 @@ export const makeWebSocket = ({
|
|
|
63
66
|
}
|
|
64
67
|
}).pipe(
|
|
65
68
|
Effect.tapErrorTag('WebSocketError', () => tryLogWebsocketConnectError(url)),
|
|
66
|
-
reconnect !== undefined ? Effect.retry(reconnect) : identity,
|
|
69
|
+
reconnect !== undefined && reconnect !== false ? Effect.retry(reconnect) : identity,
|
|
67
70
|
)
|
|
68
71
|
|
|
69
72
|
/**
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { build } from 'esbuild'
|
|
2
|
+
import { expect, it } from 'vitest'
|
|
3
|
+
|
|
4
|
+
it('bundles the Effect facade for browsers without Node builtins', async () => {
|
|
5
|
+
const result = await build({
|
|
6
|
+
bundle: true,
|
|
7
|
+
format: 'esm',
|
|
8
|
+
platform: 'browser',
|
|
9
|
+
stdin: {
|
|
10
|
+
contents: `import { Schema } from './mod.ts'; export const BrowserSchema = Schema.Struct({ value: Schema.String })`,
|
|
11
|
+
loader: 'ts',
|
|
12
|
+
resolveDir: import.meta.dirname,
|
|
13
|
+
sourcefile: 'browser-build-fixture.ts',
|
|
14
|
+
},
|
|
15
|
+
write: false,
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
expect(result.outputFiles).toHaveLength(1)
|
|
19
|
+
})
|