@livestore/utils 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/dist/.tsbuildinfo +1 -0
- package/dist/NoopTracer.d.ts +7 -8
- package/dist/NoopTracer.d.ts.map +1 -1
- package/dist/NoopTracer.js +13 -15
- 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/binary.js +1 -1
- package/dist/binary.js.map +1 -1
- package/dist/browser/Opfs/Opfs.d.ts +201 -0
- package/dist/browser/Opfs/Opfs.d.ts.map +1 -0
- package/dist/browser/Opfs/Opfs.js +170 -0
- package/dist/browser/Opfs/Opfs.js.map +1 -0
- package/dist/browser/Opfs/debug-utils.d.ts +20 -0
- package/dist/browser/Opfs/debug-utils.d.ts.map +1 -0
- package/dist/browser/Opfs/debug-utils.js +95 -0
- package/dist/browser/Opfs/debug-utils.js.map +1 -0
- package/dist/browser/Opfs/mod.d.ts +4 -0
- package/dist/browser/Opfs/mod.d.ts.map +1 -0
- package/dist/browser/Opfs/mod.js +4 -0
- package/dist/browser/Opfs/mod.js.map +1 -0
- package/dist/browser/Opfs/utils.d.ts +71 -0
- package/dist/browser/Opfs/utils.d.ts.map +1 -0
- package/dist/browser/Opfs/utils.js +227 -0
- package/dist/browser/Opfs/utils.js.map +1 -0
- package/dist/browser/QuotaExceededError.d.ts +60 -0
- package/dist/browser/QuotaExceededError.d.ts.map +1 -0
- package/dist/browser/QuotaExceededError.js +2 -0
- package/dist/browser/QuotaExceededError.js.map +1 -0
- package/dist/browser/WebChannelBrowser.d.ts +22 -0
- package/dist/browser/WebChannelBrowser.d.ts.map +1 -0
- package/dist/browser/WebChannelBrowser.js +87 -0
- package/dist/browser/WebChannelBrowser.js.map +1 -0
- package/dist/browser/WebError.d.ts +389 -0
- package/dist/browser/WebError.d.ts.map +1 -0
- package/dist/browser/WebError.js +423 -0
- package/dist/browser/WebError.js.map +1 -0
- package/dist/browser/WebError.test.d.ts +2 -0
- package/dist/browser/WebError.test.d.ts.map +1 -0
- package/dist/browser/WebError.test.js +46 -0
- package/dist/browser/WebError.test.js.map +1 -0
- package/dist/browser/WebLock.d.ts.map +1 -0
- package/dist/{effect → browser}/WebLock.js +21 -21
- package/dist/browser/WebLock.js.map +1 -0
- package/dist/{browser.d.ts → browser/detect.d.ts} +1 -1
- package/dist/browser/detect.d.ts.map +1 -0
- package/dist/{browser.js → browser/detect.js} +7 -7
- package/dist/browser/detect.js.map +1 -0
- package/dist/browser/mod.d.ts +8 -0
- package/dist/browser/mod.d.ts.map +1 -0
- package/dist/browser/mod.js +8 -0
- package/dist/browser/mod.js.map +1 -0
- package/dist/cuid/cuid.browser.js +1 -1
- package/dist/cuid/cuid.browser.js.map +1 -1
- package/dist/cuid/cuid.node.js +1 -1
- package/dist/cuid/cuid.node.js.map +1 -1
- package/dist/effect/Debug.d.ts +38 -0
- package/dist/effect/Debug.d.ts.map +1 -0
- package/dist/effect/Debug.js +350 -0
- package/dist/effect/Debug.js.map +1 -0
- package/dist/effect/Effect.d.ts +91 -22
- package/dist/effect/Effect.d.ts.map +1 -1
- package/dist/effect/Effect.js +126 -38
- 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/Error.js +1 -1
- package/dist/effect/Error.js.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 +74 -54
- 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 +9 -10
- 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 -18
- 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 +12 -12
- 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 +11 -30
- package/dist/effect/WebChannel/WebChannel.d.ts.map +1 -1
- package/dist/effect/WebChannel/WebChannel.js +70 -125
- package/dist/effect/WebChannel/WebChannel.js.map +1 -1
- package/dist/effect/WebChannel/WebChannel.test.js +16 -16
- 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 +20 -13
- package/dist/effect/WebChannel/broadcastChannelWithAck.js.map +1 -1
- package/dist/effect/WebChannel/common.d.ts +17 -18
- package/dist/effect/WebChannel/common.d.ts.map +1 -1
- package/dist/effect/WebChannel/common.js +7 -7
- 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 +17 -17
- 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 +30 -0
- package/dist/effect/mod.d.ts.map +1 -0
- package/dist/effect/mod.js +30 -0
- package/dist/effect/mod.js.map +1 -0
- package/dist/effect/spanEvent.d.ts +12 -0
- package/dist/effect/spanEvent.d.ts.map +1 -0
- package/dist/effect/spanEvent.js +12 -0
- package/dist/effect/spanEvent.js.map +1 -0
- package/dist/effect/spanEvent.test.d.ts +2 -0
- package/dist/effect/spanEvent.test.d.ts.map +1 -0
- package/dist/effect/spanEvent.test.js +79 -0
- package/dist/effect/spanEvent.test.js.map +1 -0
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +1 -1
- package/dist/env.js.map +1 -1
- package/dist/fast-deep-equal.d.ts.map +1 -1
- package/dist/fast-deep-equal.js +9 -10
- package/dist/fast-deep-equal.js.map +1 -1
- package/dist/global.d.ts +3 -0
- package/dist/global.d.ts.map +1 -1
- package/dist/global.js.map +1 -1
- package/dist/misc.d.ts +9 -1
- package/dist/misc.d.ts.map +1 -1
- package/dist/misc.js +12 -4
- package/dist/misc.js.map +1 -1
- package/dist/mod.d.ts +177 -20
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +159 -22
- package/dist/mod.js.map +1 -1
- package/dist/node/mod.d.ts +45 -6
- package/dist/node/mod.d.ts.map +1 -1
- package/dist/node/mod.js +51 -9
- 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/object/stringify-object.js +2 -2
- package/dist/object/stringify-object.js.map +1 -1
- package/dist/object/stringify-object.test.js.map +1 -1
- package/dist/qr.d.ts +38 -0
- package/dist/qr.d.ts.map +1 -0
- package/dist/qr.js +109 -0
- package/dist/qr.js.map +1 -0
- package/dist/set.js +1 -1
- package/dist/set.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 +61 -55
- package/src/NoopTracer.test.ts +20 -0
- package/src/NoopTracer.ts +28 -37
- package/src/binary.ts +1 -1
- package/src/browser/Opfs/Opfs.ts +463 -0
- package/src/browser/Opfs/debug-utils.ts +154 -0
- package/src/browser/Opfs/mod.ts +3 -0
- package/src/browser/Opfs/utils.ts +297 -0
- package/src/browser/QuotaExceededError.ts +60 -0
- package/src/browser/WebChannelBrowser.ts +145 -0
- package/src/browser/WebError.test.ts +66 -0
- package/src/browser/WebError.ts +587 -0
- package/src/{effect → browser}/WebLock.ts +27 -28
- package/src/{browser.ts → browser/detect.ts} +6 -6
- package/src/browser/mod.ts +8 -0
- package/src/cuid/cuid.browser.ts +1 -1
- package/src/cuid/cuid.node.ts +1 -1
- package/src/effect/Debug.ts +477 -0
- package/src/effect/Effect.test.ts +117 -0
- package/src/effect/Effect.ts +174 -79
- package/src/effect/Error.ts +1 -1
- package/src/effect/Logger.ts +0 -48
- package/src/effect/OtelTracer.ts +2 -3
- package/src/effect/RpcClient.ts +85 -65
- package/src/effect/Schedule.ts +4 -5
- package/src/effect/Schema/debug-diff.test.ts +7 -7
- package/src/effect/Schema/debug-diff.ts +11 -12
- package/src/effect/Schema/index.ts +121 -57
- package/src/effect/Stream.test.ts +14 -13
- package/src/effect/Stream.ts +23 -16
- package/src/effect/Subscribable.ts +31 -42
- package/src/effect/SubscriptionRef.ts +9 -13
- package/src/effect/TaskTracing.ts +13 -16
- package/src/effect/WebChannel/WebChannel.test.ts +24 -24
- package/src/effect/WebChannel/WebChannel.ts +102 -192
- package/src/effect/WebChannel/broadcastChannelWithAck.ts +25 -15
- package/src/effect/WebChannel/common.ts +16 -17
- package/src/effect/WebSocket.test.ts +2 -2
- package/src/effect/WebSocket.ts +20 -16
- package/src/effect/mod.browser-build.test.ts +19 -0
- package/src/effect/{index.ts → mod.ts} +43 -69
- package/src/effect/spanEvent.test.ts +92 -0
- package/src/effect/spanEvent.ts +17 -0
- package/src/env.ts +2 -1
- package/src/fast-deep-equal.ts +9 -10
- package/src/global.ts +4 -2
- package/src/misc.ts +13 -5
- package/src/mod.ts +188 -32
- package/src/node/mod.ts +57 -12
- package/src/object/index.ts +0 -24
- package/src/object/stringify-object.test.ts +1 -0
- package/src/object/stringify-object.ts +2 -2
- package/src/qr.ts +125 -0
- package/src/set.ts +1 -1
- package/src/string.ts +0 -3
- package/dist/.tsbuildinfo.json +0 -1
- package/dist/browser.d.ts.map +0 -1
- package/dist/browser.js.map +0 -1
- 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/effect/WebLock.d.ts.map +0 -1
- package/dist/effect/WebLock.js.map +0 -1
- package/dist/effect/index.d.ts +0 -32
- package/dist/effect/index.d.ts.map +0 -1
- package/dist/effect/index.js +0 -35
- package/dist/effect/index.js.map +0 -1
- package/dist/guards.d.ts +0 -6
- package/dist/guards.d.ts.map +0 -1
- package/dist/guards.js +0 -6
- 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 -122
- package/dist/node/ChildProcessRunner/ChildProcessRunner.js.map +0 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.d.ts +0 -2
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.d.ts.map +0 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.js +0 -218
- 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 -8
- package/src/node/ChildProcessRunner/ChildProcessRunner.ts +0 -135
- package/src/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.ts +0 -302
- package/src/node/ChildProcessRunner/ChildProcessRunnerTest/schema.ts +0 -78
- package/src/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.ts +0 -66
- package/src/node/ChildProcessRunner/ChildProcessWorker.ts +0 -168
- package/src/object/omit.ts +0 -17
- package/src/object/pick.ts +0 -27
- package/src/time.ts +0 -25
- /package/dist/{effect → browser}/WebLock.d.ts +0 -0
|
@@ -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,
|
|
@@ -38,131 +38,10 @@ export const noopChannel = <MsgListen, MsgSend>(): Effect.Effect<WebChannel<MsgL
|
|
|
38
38
|
}).pipe(Effect.withSpan(`WebChannel:noopChannel`)),
|
|
39
39
|
)
|
|
40
40
|
|
|
41
|
-
/** Only works in browser environments */
|
|
42
|
-
export const broadcastChannel = <MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded>({
|
|
43
|
-
channelName,
|
|
44
|
-
schema: inputSchema,
|
|
45
|
-
}: {
|
|
46
|
-
channelName: string
|
|
47
|
-
schema: InputSchema<MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded>
|
|
48
|
-
}): Effect.Effect<WebChannel<MsgListen, MsgSend>, never, Scope.Scope> =>
|
|
49
|
-
Effect.scopeWithCloseable((scope) =>
|
|
50
|
-
Effect.gen(function* () {
|
|
51
|
-
const schema = mapSchema(inputSchema)
|
|
52
|
-
|
|
53
|
-
const channel = new BroadcastChannel(channelName)
|
|
54
|
-
|
|
55
|
-
yield* Effect.addFinalizer(() => Effect.try(() => channel.close()).pipe(Effect.ignoreLogged))
|
|
56
|
-
|
|
57
|
-
const send = (message: MsgSend) =>
|
|
58
|
-
Effect.gen(function* () {
|
|
59
|
-
const messageEncoded = yield* Schema.encode(schema.send)(message)
|
|
60
|
-
channel.postMessage(messageEncoded)
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
// TODO also listen to `messageerror` in parallel
|
|
64
|
-
const listen = Stream.fromEventListener<MessageEvent>(channel, 'message').pipe(
|
|
65
|
-
Stream.map((_) => Schema.decodeEither(schema.listen)(_.data)),
|
|
66
|
-
listenToDebugPing(channelName),
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
const closedDeferred = yield* Deferred.make<void>().pipe(Effect.acquireRelease(Deferred.done(Exit.void)))
|
|
70
|
-
const supportsTransferables = false
|
|
71
|
-
|
|
72
|
-
return {
|
|
73
|
-
[WebChannelSymbol]: WebChannelSymbol,
|
|
74
|
-
send,
|
|
75
|
-
listen,
|
|
76
|
-
closedDeferred,
|
|
77
|
-
shutdown: Scope.close(scope, Exit.succeed('shutdown')),
|
|
78
|
-
schema,
|
|
79
|
-
supportsTransferables,
|
|
80
|
-
}
|
|
81
|
-
}).pipe(Effect.withSpan(`WebChannel:broadcastChannel(${channelName})`)),
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* NOTE the `listenName` and `sendName` is needed for cases where both sides are using the same window
|
|
86
|
-
* e.g. for a browser extension, so we need a way to know for which side a message is intended for.
|
|
87
|
-
*/
|
|
88
|
-
export const windowChannel = <MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded>({
|
|
89
|
-
listenWindow,
|
|
90
|
-
sendWindow,
|
|
91
|
-
targetOrigin = '*',
|
|
92
|
-
ids,
|
|
93
|
-
schema: inputSchema,
|
|
94
|
-
}: {
|
|
95
|
-
listenWindow: Window
|
|
96
|
-
sendWindow: Window
|
|
97
|
-
targetOrigin?: string
|
|
98
|
-
ids: { own: string; other: string }
|
|
99
|
-
schema: InputSchema<MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded>
|
|
100
|
-
}): Effect.Effect<WebChannel<MsgListen, MsgSend>, never, Scope.Scope> =>
|
|
101
|
-
Effect.scopeWithCloseable((scope) =>
|
|
102
|
-
Effect.gen(function* () {
|
|
103
|
-
const schema = mapSchema(inputSchema)
|
|
104
|
-
|
|
105
|
-
const debugInfo = {
|
|
106
|
-
sendTotal: 0,
|
|
107
|
-
listenTotal: 0,
|
|
108
|
-
targetOrigin,
|
|
109
|
-
ids,
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const WindowMessageListen = Schema.Struct({
|
|
113
|
-
message: schema.listen,
|
|
114
|
-
from: Schema.Literal(ids.other),
|
|
115
|
-
to: Schema.Literal(ids.own),
|
|
116
|
-
}).annotations({ title: 'webmesh.WindowMessageListen' })
|
|
117
|
-
|
|
118
|
-
const WindowMessageSend = Schema.Struct({
|
|
119
|
-
message: schema.send,
|
|
120
|
-
from: Schema.Literal(ids.own),
|
|
121
|
-
to: Schema.Literal(ids.other),
|
|
122
|
-
}).annotations({ title: 'webmesh.WindowMessageSend' })
|
|
123
|
-
|
|
124
|
-
const send = (message: MsgSend) =>
|
|
125
|
-
Effect.gen(function* () {
|
|
126
|
-
debugInfo.sendTotal++
|
|
127
|
-
|
|
128
|
-
const [messageEncoded, transferables] = yield* Schema.encodeWithTransferables(WindowMessageSend)({
|
|
129
|
-
message,
|
|
130
|
-
from: ids.own,
|
|
131
|
-
to: ids.other,
|
|
132
|
-
})
|
|
133
|
-
sendWindow.postMessage(messageEncoded, targetOrigin, transferables)
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
const listen = Stream.fromEventListener<MessageEvent>(listenWindow, 'message').pipe(
|
|
137
|
-
// Stream.tap((_) => Effect.log(`${ids.other}→${ids.own}:message`, _.data)),
|
|
138
|
-
Stream.filter((_) => Schema.is(Schema.encodedSchema(WindowMessageListen))(_.data)),
|
|
139
|
-
Stream.map((_) => {
|
|
140
|
-
debugInfo.listenTotal++
|
|
141
|
-
return Schema.decodeEither(schema.listen)(_.data.message)
|
|
142
|
-
}),
|
|
143
|
-
listenToDebugPing('window'),
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
const closedDeferred = yield* Deferred.make<void>().pipe(Effect.acquireRelease(Deferred.done(Exit.void)))
|
|
147
|
-
const supportsTransferables = true
|
|
148
|
-
|
|
149
|
-
return {
|
|
150
|
-
[WebChannelSymbol]: WebChannelSymbol,
|
|
151
|
-
send,
|
|
152
|
-
listen,
|
|
153
|
-
closedDeferred,
|
|
154
|
-
shutdown: Scope.close(scope, Exit.succeed('shutdown')),
|
|
155
|
-
schema,
|
|
156
|
-
supportsTransferables,
|
|
157
|
-
debugInfo,
|
|
158
|
-
}
|
|
159
|
-
}).pipe(Effect.withSpan(`WebChannel:windowChannel`)),
|
|
160
|
-
)
|
|
161
|
-
|
|
162
41
|
export const messagePortChannel: <MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded>(args: {
|
|
163
42
|
port: MessagePort
|
|
164
43
|
schema: InputSchema<MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded>
|
|
165
|
-
debugId?: string | number
|
|
44
|
+
debugId?: string | number | undefined
|
|
166
45
|
}) => Effect.Effect<WebChannel<MsgListen, MsgSend>, never, Scope.Scope> = ({ port, schema: inputSchema, debugId }) =>
|
|
167
46
|
Effect.scopeWithCloseable((scope) =>
|
|
168
47
|
Effect.gen(function* () {
|
|
@@ -170,15 +49,25 @@ export const messagePortChannel: <MsgListen, MsgSend, MsgListenEncoded, MsgSendE
|
|
|
170
49
|
|
|
171
50
|
const label = debugId === undefined ? 'messagePort' : `messagePort:${debugId}`
|
|
172
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
|
+
|
|
173
62
|
const send = (message: any) =>
|
|
174
63
|
Effect.gen(function* () {
|
|
175
64
|
const [messageEncoded, transferables] = yield* Schema.encodeWithTransferables(schema.send)(message)
|
|
176
65
|
port.postMessage(messageEncoded, transferables)
|
|
177
66
|
})
|
|
178
67
|
|
|
179
|
-
const listen = Stream.
|
|
68
|
+
const listen = Stream.fromQueue(messageQueue).pipe(
|
|
180
69
|
// Stream.tap((_) => Effect.log(`${label}:message`, _.data)),
|
|
181
|
-
Stream.map((_) => Schema.
|
|
70
|
+
Stream.map((_) => Schema.decodeResult(schema.listen)(_.data)),
|
|
182
71
|
listenToDebugPing(label),
|
|
183
72
|
)
|
|
184
73
|
|
|
@@ -186,10 +75,15 @@ export const messagePortChannel: <MsgListen, MsgSend, MsgListenEncoded, MsgSendE
|
|
|
186
75
|
|
|
187
76
|
port.start()
|
|
188
77
|
|
|
189
|
-
const closedDeferred = yield* Deferred.make<void>()
|
|
78
|
+
const closedDeferred = yield* Effect.acquireRelease(Deferred.make<void>(), Deferred.done(Exit.void))
|
|
190
79
|
const supportsTransferables = true
|
|
191
80
|
|
|
192
|
-
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
|
+
)
|
|
193
87
|
|
|
194
88
|
return {
|
|
195
89
|
[WebChannelSymbol]: WebChannelSymbol,
|
|
@@ -203,10 +97,7 @@ export const messagePortChannel: <MsgListen, MsgSend, MsgListenEncoded, MsgSendE
|
|
|
203
97
|
}).pipe(Effect.withSpan(`WebChannel:messagePortChannel`)),
|
|
204
98
|
)
|
|
205
99
|
|
|
206
|
-
const sameThreadChannels =
|
|
207
|
-
'livestore:sameThreadChannels',
|
|
208
|
-
() => new Map<string, PubSub.PubSub<any>>(),
|
|
209
|
-
)
|
|
100
|
+
const sameThreadChannels = new Map<string, PubSub.PubSub<any>>()
|
|
210
101
|
|
|
211
102
|
export const sameThreadChannel = <MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded>({
|
|
212
103
|
schema: inputSchema,
|
|
@@ -219,20 +110,17 @@ export const sameThreadChannel = <MsgListen, MsgSend, MsgListenEncoded, MsgSendE
|
|
|
219
110
|
Effect.gen(function* () {
|
|
220
111
|
let pubSub = sameThreadChannels.get(channelName)
|
|
221
112
|
if (pubSub === undefined) {
|
|
222
|
-
pubSub = yield* PubSub.unbounded<any>()
|
|
113
|
+
pubSub = yield* Effect.acquireRelease(PubSub.unbounded<any>(), PubSub.shutdown)
|
|
223
114
|
sameThreadChannels.set(channelName, pubSub)
|
|
224
115
|
}
|
|
225
116
|
|
|
226
117
|
const schema = mapSchema(inputSchema)
|
|
227
118
|
|
|
228
|
-
const send = (message: MsgSend) =>
|
|
229
|
-
Effect.gen(function* () {
|
|
230
|
-
yield* PubSub.publish(pubSub, message)
|
|
231
|
-
})
|
|
119
|
+
const send = (message: MsgSend) => PubSub.publish(pubSub, message)
|
|
232
120
|
|
|
233
|
-
const listen = Stream.fromPubSub(pubSub).pipe(Stream.map(
|
|
121
|
+
const listen = Stream.fromPubSub(pubSub).pipe(Stream.map(Result.succeed), listenToDebugPing(channelName))
|
|
234
122
|
|
|
235
|
-
const closedDeferred = yield* Deferred.make<void>()
|
|
123
|
+
const closedDeferred = yield* Effect.acquireRelease(Deferred.make<void>(), Deferred.done(Exit.void))
|
|
236
124
|
|
|
237
125
|
return {
|
|
238
126
|
[WebChannelSymbol]: WebChannelSymbol,
|
|
@@ -249,7 +137,7 @@ export const sameThreadChannel = <MsgListen, MsgSend, MsgListenEncoded, MsgSendE
|
|
|
249
137
|
export const messagePortChannelWithAck: <MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded>(args: {
|
|
250
138
|
port: MessagePort
|
|
251
139
|
schema: InputSchema<MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded>
|
|
252
|
-
debugId?: string | number
|
|
140
|
+
debugId?: string | number | undefined
|
|
253
141
|
}) => Effect.Effect<WebChannel<MsgListen, MsgSend>, never, Scope.Scope> = ({ port, schema: inputSchema, debugId }) =>
|
|
254
142
|
Effect.scopeWithCloseable((scope) =>
|
|
255
143
|
Effect.gen(function* () {
|
|
@@ -262,12 +150,12 @@ export const messagePortChannelWithAck: <MsgListen, MsgSend, MsgListenEncoded, M
|
|
|
262
150
|
|
|
263
151
|
const ChannelRequest = Schema.TaggedStruct('ChannelRequest', {
|
|
264
152
|
id: Schema.String,
|
|
265
|
-
payload: Schema.Union(schema.listen, schema.send),
|
|
266
|
-
}).
|
|
153
|
+
payload: Schema.Union([schema.listen, schema.send]),
|
|
154
|
+
}).annotate({ title: 'webmesh.ChannelRequest' })
|
|
267
155
|
const ChannelRequestAck = Schema.TaggedStruct('ChannelRequestAck', {
|
|
268
156
|
reqId: Schema.String,
|
|
269
|
-
}).
|
|
270
|
-
const ChannelMessage = Schema.Union(ChannelRequest, ChannelRequestAck).
|
|
157
|
+
}).annotate({ title: 'webmesh.ChannelRequestAck' })
|
|
158
|
+
const ChannelMessage = Schema.Union([ChannelRequest, ChannelRequestAck]).annotate({
|
|
271
159
|
title: 'webmesh.ChannelMessage',
|
|
272
160
|
})
|
|
273
161
|
|
|
@@ -278,6 +166,15 @@ export const messagePortChannelWithAck: <MsgListen, MsgSend, MsgListenEncoded, M
|
|
|
278
166
|
id: debugId,
|
|
279
167
|
}
|
|
280
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
|
+
|
|
281
178
|
const send = (message: any) =>
|
|
282
179
|
Effect.gen(function* () {
|
|
283
180
|
debugInfo.sendTotal++
|
|
@@ -295,48 +192,56 @@ export const messagePortChannelWithAck: <MsgListen, MsgSend, MsgListenEncoded, M
|
|
|
295
192
|
|
|
296
193
|
port.postMessage(messageEncoded, transferables)
|
|
297
194
|
|
|
298
|
-
yield* ack
|
|
195
|
+
yield* Deferred.await(ack)
|
|
299
196
|
|
|
300
197
|
requestAckMap.delete(id)
|
|
301
198
|
|
|
302
199
|
debugInfo.sendPending--
|
|
303
200
|
})
|
|
304
201
|
|
|
305
|
-
|
|
306
|
-
// https://github.com/livestorejs/livestore/issues/262
|
|
307
|
-
const listen = Stream.fromEventListener<MessageEvent>(port, 'message').pipe(
|
|
202
|
+
const listen = Stream.fromQueue(messageQueue).pipe(
|
|
308
203
|
// Stream.onStart(Effect.log(`${label}:listen:start`)),
|
|
309
204
|
// Stream.tap((_) => Effect.log(`${label}:message`, _.data)),
|
|
310
|
-
Stream.map((_) => Schema.
|
|
205
|
+
Stream.map((_) => Schema.decodeResult(ChannelMessage)(_.data)),
|
|
311
206
|
Stream.tap((msg) =>
|
|
312
207
|
Effect.gen(function* () {
|
|
313
|
-
if (msg
|
|
314
|
-
if (msg.
|
|
315
|
-
yield* Deferred.succeed(requestAckMap.get(msg.
|
|
316
|
-
} 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') {
|
|
317
212
|
debugInfo.listenTotal++
|
|
318
|
-
port.postMessage(
|
|
213
|
+
port.postMessage(
|
|
214
|
+
yield* Schema.encodeEffect(ChannelMessage)({ _tag: 'ChannelRequestAck', reqId: msg.success.id }).pipe(
|
|
215
|
+
Effect.orDie,
|
|
216
|
+
),
|
|
217
|
+
)
|
|
319
218
|
}
|
|
320
219
|
}
|
|
321
220
|
}),
|
|
322
221
|
),
|
|
323
|
-
Stream.filterMap(
|
|
324
|
-
msg
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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
|
+
),
|
|
329
230
|
),
|
|
330
|
-
(_) => _ as Stream.Stream<Either.Either<any, any>>,
|
|
331
231
|
listenToDebugPing(label),
|
|
332
232
|
)
|
|
333
233
|
|
|
334
234
|
port.start()
|
|
335
235
|
|
|
336
|
-
const closedDeferred = yield* Deferred.make<void>()
|
|
236
|
+
const closedDeferred = yield* Effect.acquireRelease(Deferred.make<void>(), Deferred.done(Exit.void))
|
|
337
237
|
const supportsTransferables = true
|
|
338
238
|
|
|
339
|
-
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
|
+
)
|
|
340
245
|
|
|
341
246
|
return {
|
|
342
247
|
[WebChannelSymbol]: WebChannelSymbol,
|
|
@@ -351,6 +256,9 @@ export const messagePortChannelWithAck: <MsgListen, MsgSend, MsgListenEncoded, M
|
|
|
351
256
|
}).pipe(Effect.withSpan(`WebChannel:messagePortChannelWithAck`)),
|
|
352
257
|
)
|
|
353
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
|
+
|
|
354
262
|
export type QueueChannelProxy<MsgListen, MsgSend> = {
|
|
355
263
|
/** Only meant to be used externally */
|
|
356
264
|
webChannel: WebChannel<MsgListen, MsgSend>
|
|
@@ -374,19 +282,19 @@ export const queueChannelProxy = <MsgListen, MsgSend>({
|
|
|
374
282
|
schema: inputSchema,
|
|
375
283
|
}: {
|
|
376
284
|
schema:
|
|
377
|
-
| Schema.
|
|
378
|
-
| { listen: Schema.
|
|
285
|
+
| Schema.Codec<MsgListen | MsgSend, any>
|
|
286
|
+
| { listen: Schema.Codec<MsgListen, any>; send: Schema.Codec<MsgSend, any> }
|
|
379
287
|
}): Effect.Effect<QueueChannelProxy<MsgListen, MsgSend>, never, Scope.Scope> =>
|
|
380
288
|
Effect.scopeWithCloseable((scope) =>
|
|
381
289
|
Effect.gen(function* () {
|
|
382
|
-
const sendQueue = yield* Queue.unbounded<MsgSend>()
|
|
383
|
-
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)
|
|
384
292
|
|
|
385
293
|
const send = (message: MsgSend) => Queue.offer(sendQueue, message)
|
|
386
294
|
|
|
387
|
-
const listen = Stream.fromQueue(listenQueue).pipe(Stream.map(
|
|
295
|
+
const listen = Stream.fromQueue(listenQueue).pipe(Stream.map(Result.succeed), listenToDebugPing('queueChannel'))
|
|
388
296
|
|
|
389
|
-
const closedDeferred = yield* Deferred.make<void>()
|
|
297
|
+
const closedDeferred = yield* Effect.acquireRelease(Deferred.make<void>(), Deferred.done(Exit.void))
|
|
390
298
|
const supportsTransferables = true
|
|
391
299
|
|
|
392
300
|
const schema = mapSchema(inputSchema)
|
|
@@ -408,21 +316,26 @@ export const queueChannelProxy = <MsgListen, MsgSend>({
|
|
|
408
316
|
/**
|
|
409
317
|
* Eagerly starts listening to a channel by buffering incoming messages in a queue.
|
|
410
318
|
*/
|
|
411
|
-
export const toOpenChannel = (
|
|
412
|
-
channel: WebChannel<
|
|
319
|
+
export const toOpenChannel = <MsgListen, MsgSend>(
|
|
320
|
+
channel: WebChannel<MsgListen, MsgSend>,
|
|
413
321
|
options?: {
|
|
414
322
|
/**
|
|
415
323
|
* Sends a heartbeat message to the other end of the channel every `interval`.
|
|
416
324
|
* If the other end doesn't respond within `timeout` milliseconds, the channel is shutdown.
|
|
417
325
|
*/
|
|
418
326
|
heartbeat?: {
|
|
419
|
-
interval:
|
|
420
|
-
timeout:
|
|
327
|
+
interval: Duration.Input
|
|
328
|
+
timeout: Duration.Input
|
|
421
329
|
}
|
|
422
330
|
},
|
|
423
|
-
): Effect.Effect<WebChannel<
|
|
331
|
+
): Effect.Effect<WebChannel<MsgListen, MsgSend>, never, Scope.Scope> =>
|
|
424
332
|
Effect.gen(function* () {
|
|
425
|
-
const queue = yield* Queue.unbounded<
|
|
333
|
+
const queue = yield* Effect.acquireRelease(Queue.unbounded<Result.Result<MsgListen, any>>(), Queue.shutdown)
|
|
334
|
+
|
|
335
|
+
const heartbeatChannel = channel as WebChannel<
|
|
336
|
+
MsgListen | typeof WebChannelHeartbeat.Type,
|
|
337
|
+
MsgSend | typeof WebChannelHeartbeat.Type
|
|
338
|
+
>
|
|
426
339
|
|
|
427
340
|
const pendingPingDeferredRef = {
|
|
428
341
|
current: undefined as { deferred: Deferred.Deferred<void>; requestId: string } | undefined,
|
|
@@ -430,16 +343,16 @@ export const toOpenChannel = (
|
|
|
430
343
|
|
|
431
344
|
yield* channel.listen.pipe(
|
|
432
345
|
// TODO implement this on the "chunk" level for better performance
|
|
433
|
-
options?.heartbeat
|
|
346
|
+
options?.heartbeat !== undefined
|
|
434
347
|
? Stream.filterEffect(
|
|
435
348
|
Effect.fn(function* (msg) {
|
|
436
|
-
if (msg
|
|
437
|
-
if (msg.
|
|
438
|
-
yield*
|
|
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 }))
|
|
439
352
|
} else {
|
|
440
353
|
const { deferred, requestId } = pendingPingDeferredRef.current ?? shouldNeverHappen('No pending ping')
|
|
441
|
-
if (requestId !== msg.
|
|
442
|
-
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)
|
|
443
356
|
}
|
|
444
357
|
yield* Deferred.succeed(deferred, void 0)
|
|
445
358
|
}
|
|
@@ -450,23 +363,23 @@ export const toOpenChannel = (
|
|
|
450
363
|
}),
|
|
451
364
|
)
|
|
452
365
|
: identity,
|
|
453
|
-
Stream.
|
|
366
|
+
Stream.tapArray((array) => Queue.offerAll(queue, array)),
|
|
454
367
|
Stream.runDrain,
|
|
455
368
|
Effect.forkScoped,
|
|
456
369
|
)
|
|
457
370
|
|
|
458
|
-
if (options?.heartbeat) {
|
|
371
|
+
if (options?.heartbeat !== undefined) {
|
|
459
372
|
const { interval, timeout } = options.heartbeat
|
|
460
373
|
yield* Effect.gen(function* () {
|
|
461
374
|
while (true) {
|
|
462
375
|
yield* Effect.sleep(interval)
|
|
463
376
|
const requestId = crypto.randomUUID()
|
|
464
|
-
yield*
|
|
377
|
+
yield* heartbeatChannel.send(WebChannelPing.make({ requestId }))
|
|
465
378
|
const deferred = yield* Deferred.make<void>()
|
|
466
379
|
pendingPingDeferredRef.current = { deferred, requestId }
|
|
467
|
-
yield* deferred.pipe(
|
|
380
|
+
yield* Deferred.await(deferred).pipe(
|
|
468
381
|
Effect.timeout(timeout),
|
|
469
|
-
Effect.catchTag('
|
|
382
|
+
Effect.catchTag('TimeoutError', () => channel.shutdown),
|
|
470
383
|
)
|
|
471
384
|
}
|
|
472
385
|
}).pipe(Effect.withSpan(`WebChannel:heartbeat`), Effect.forkScoped)
|
|
@@ -475,7 +388,7 @@ export const toOpenChannel = (
|
|
|
475
388
|
// We're currently limiting the chunk size to 1 to not drop messages in scearnios where
|
|
476
389
|
// the listen stream get subscribed to, only take N messages and then unsubscribe.
|
|
477
390
|
// Without this limit, messages would be dropped.
|
|
478
|
-
const listen = Stream.fromQueue(queue
|
|
391
|
+
const listen = Stream.fromQueue(queue).pipe(Stream.rechunk(1))
|
|
479
392
|
|
|
480
393
|
return {
|
|
481
394
|
[WebChannelSymbol]: WebChannelSymbol,
|
|
@@ -492,7 +405,4 @@ export const toOpenChannel = (
|
|
|
492
405
|
}
|
|
493
406
|
})
|
|
494
407
|
|
|
495
|
-
export const sendDebugPing = (channel: WebChannel<any, any>) =>
|
|
496
|
-
Effect.gen(function* () {
|
|
497
|
-
yield* channel.send(DebugPingMessage.make({ message: 'ping' }))
|
|
498
|
-
})
|
|
408
|
+
export const sendDebugPing = (channel: WebChannel<any, any>) => channel.send(DebugPingMessage.make({ message: 'ping' }))
|
|
@@ -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) {
|
|
@@ -69,7 +77,7 @@ export const broadcastChannelWithAck = <MsgListen, MsgSend, MsgListenEncoded, Ms
|
|
|
69
77
|
peerIdRef.current = data.from
|
|
70
78
|
postMessage(ConnectAckMessage.make({ from: connectionId, to: data.from }))
|
|
71
79
|
yield* connectedLatch.open
|
|
72
|
-
|
|
80
|
+
return undefined
|
|
73
81
|
}
|
|
74
82
|
// Case: other side sends connect-ack message (because otherside was already online when this side connected)
|
|
75
83
|
case 'ConnectAckMessage': {
|
|
@@ -77,7 +85,7 @@ export const broadcastChannelWithAck = <MsgListen, MsgSend, MsgListenEncoded, Ms
|
|
|
77
85
|
peerIdRef.current = data.from
|
|
78
86
|
yield* connectedLatch.open
|
|
79
87
|
}
|
|
80
|
-
|
|
88
|
+
return undefined
|
|
81
89
|
}
|
|
82
90
|
case 'DisconnectMessage': {
|
|
83
91
|
if (data.from === peerIdRef.current) {
|
|
@@ -85,13 +93,13 @@ export const broadcastChannelWithAck = <MsgListen, MsgSend, MsgListenEncoded, Ms
|
|
|
85
93
|
yield* connectedLatch.close
|
|
86
94
|
yield* establishConnection
|
|
87
95
|
}
|
|
88
|
-
|
|
96
|
+
return undefined
|
|
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
|
}
|
|
96
104
|
}
|
|
97
105
|
}),
|
|
@@ -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 }
|