@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.
Files changed (316) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/NoopTracer.d.ts +7 -8
  3. package/dist/NoopTracer.d.ts.map +1 -1
  4. package/dist/NoopTracer.js +13 -15
  5. package/dist/NoopTracer.js.map +1 -1
  6. package/dist/NoopTracer.test.d.ts +2 -0
  7. package/dist/NoopTracer.test.d.ts.map +1 -0
  8. package/dist/NoopTracer.test.js +17 -0
  9. package/dist/NoopTracer.test.js.map +1 -0
  10. package/dist/binary.js +1 -1
  11. package/dist/binary.js.map +1 -1
  12. package/dist/browser/Opfs/Opfs.d.ts +201 -0
  13. package/dist/browser/Opfs/Opfs.d.ts.map +1 -0
  14. package/dist/browser/Opfs/Opfs.js +170 -0
  15. package/dist/browser/Opfs/Opfs.js.map +1 -0
  16. package/dist/browser/Opfs/debug-utils.d.ts +20 -0
  17. package/dist/browser/Opfs/debug-utils.d.ts.map +1 -0
  18. package/dist/browser/Opfs/debug-utils.js +95 -0
  19. package/dist/browser/Opfs/debug-utils.js.map +1 -0
  20. package/dist/browser/Opfs/mod.d.ts +4 -0
  21. package/dist/browser/Opfs/mod.d.ts.map +1 -0
  22. package/dist/browser/Opfs/mod.js +4 -0
  23. package/dist/browser/Opfs/mod.js.map +1 -0
  24. package/dist/browser/Opfs/utils.d.ts +71 -0
  25. package/dist/browser/Opfs/utils.d.ts.map +1 -0
  26. package/dist/browser/Opfs/utils.js +227 -0
  27. package/dist/browser/Opfs/utils.js.map +1 -0
  28. package/dist/browser/QuotaExceededError.d.ts +60 -0
  29. package/dist/browser/QuotaExceededError.d.ts.map +1 -0
  30. package/dist/browser/QuotaExceededError.js +2 -0
  31. package/dist/browser/QuotaExceededError.js.map +1 -0
  32. package/dist/browser/WebChannelBrowser.d.ts +22 -0
  33. package/dist/browser/WebChannelBrowser.d.ts.map +1 -0
  34. package/dist/browser/WebChannelBrowser.js +87 -0
  35. package/dist/browser/WebChannelBrowser.js.map +1 -0
  36. package/dist/browser/WebError.d.ts +389 -0
  37. package/dist/browser/WebError.d.ts.map +1 -0
  38. package/dist/browser/WebError.js +423 -0
  39. package/dist/browser/WebError.js.map +1 -0
  40. package/dist/browser/WebError.test.d.ts +2 -0
  41. package/dist/browser/WebError.test.d.ts.map +1 -0
  42. package/dist/browser/WebError.test.js +46 -0
  43. package/dist/browser/WebError.test.js.map +1 -0
  44. package/dist/browser/WebLock.d.ts.map +1 -0
  45. package/dist/{effect → browser}/WebLock.js +21 -21
  46. package/dist/browser/WebLock.js.map +1 -0
  47. package/dist/{browser.d.ts → browser/detect.d.ts} +1 -1
  48. package/dist/browser/detect.d.ts.map +1 -0
  49. package/dist/{browser.js → browser/detect.js} +7 -7
  50. package/dist/browser/detect.js.map +1 -0
  51. package/dist/browser/mod.d.ts +8 -0
  52. package/dist/browser/mod.d.ts.map +1 -0
  53. package/dist/browser/mod.js +8 -0
  54. package/dist/browser/mod.js.map +1 -0
  55. package/dist/cuid/cuid.browser.js +1 -1
  56. package/dist/cuid/cuid.browser.js.map +1 -1
  57. package/dist/cuid/cuid.node.js +1 -1
  58. package/dist/cuid/cuid.node.js.map +1 -1
  59. package/dist/effect/Debug.d.ts +38 -0
  60. package/dist/effect/Debug.d.ts.map +1 -0
  61. package/dist/effect/Debug.js +350 -0
  62. package/dist/effect/Debug.js.map +1 -0
  63. package/dist/effect/Effect.d.ts +91 -22
  64. package/dist/effect/Effect.d.ts.map +1 -1
  65. package/dist/effect/Effect.js +126 -38
  66. package/dist/effect/Effect.js.map +1 -1
  67. package/dist/effect/Effect.test.d.ts +2 -0
  68. package/dist/effect/Effect.test.d.ts.map +1 -0
  69. package/dist/effect/Effect.test.js +72 -0
  70. package/dist/effect/Effect.test.js.map +1 -0
  71. package/dist/effect/Error.d.ts +4 -6
  72. package/dist/effect/Error.d.ts.map +1 -1
  73. package/dist/effect/Error.js +1 -1
  74. package/dist/effect/Error.js.map +1 -1
  75. package/dist/effect/Logger.d.ts +0 -6
  76. package/dist/effect/Logger.d.ts.map +1 -1
  77. package/dist/effect/Logger.js +0 -32
  78. package/dist/effect/Logger.js.map +1 -1
  79. package/dist/effect/OtelTracer.d.ts +1 -1
  80. package/dist/effect/OtelTracer.d.ts.map +1 -1
  81. package/dist/effect/OtelTracer.js +2 -3
  82. package/dist/effect/OtelTracer.js.map +1 -1
  83. package/dist/effect/RpcClient.d.ts +7 -7
  84. package/dist/effect/RpcClient.d.ts.map +1 -1
  85. package/dist/effect/RpcClient.js +74 -54
  86. package/dist/effect/RpcClient.js.map +1 -1
  87. package/dist/effect/Schedule.d.ts +1 -1
  88. package/dist/effect/Schedule.d.ts.map +1 -1
  89. package/dist/effect/Schedule.js +2 -2
  90. package/dist/effect/Schedule.js.map +1 -1
  91. package/dist/effect/Schema/debug-diff.d.ts +1 -1
  92. package/dist/effect/Schema/debug-diff.d.ts.map +1 -1
  93. package/dist/effect/Schema/debug-diff.js +9 -10
  94. package/dist/effect/Schema/debug-diff.js.map +1 -1
  95. package/dist/effect/Schema/debug-diff.test.js +7 -4
  96. package/dist/effect/Schema/debug-diff.test.js.map +1 -1
  97. package/dist/effect/Schema/index.d.ts +28 -18
  98. package/dist/effect/Schema/index.d.ts.map +1 -1
  99. package/dist/effect/Schema/index.js +55 -28
  100. package/dist/effect/Schema/index.js.map +1 -1
  101. package/dist/effect/Stream.d.ts +13 -5
  102. package/dist/effect/Stream.d.ts.map +1 -1
  103. package/dist/effect/Stream.js +19 -12
  104. package/dist/effect/Stream.js.map +1 -1
  105. package/dist/effect/Stream.test.js +12 -12
  106. package/dist/effect/Stream.test.js.map +1 -1
  107. package/dist/effect/Subscribable.d.ts +9 -3
  108. package/dist/effect/Subscribable.d.ts.map +1 -1
  109. package/dist/effect/Subscribable.js +20 -35
  110. package/dist/effect/Subscribable.js.map +1 -1
  111. package/dist/effect/SubscriptionRef.d.ts +5 -6
  112. package/dist/effect/SubscriptionRef.d.ts.map +1 -1
  113. package/dist/effect/SubscriptionRef.js +2 -3
  114. package/dist/effect/SubscriptionRef.js.map +1 -1
  115. package/dist/effect/TaskTracing.d.ts +1 -1
  116. package/dist/effect/TaskTracing.d.ts.map +1 -1
  117. package/dist/effect/TaskTracing.js +12 -16
  118. package/dist/effect/TaskTracing.js.map +1 -1
  119. package/dist/effect/WebChannel/WebChannel.d.ts +11 -30
  120. package/dist/effect/WebChannel/WebChannel.d.ts.map +1 -1
  121. package/dist/effect/WebChannel/WebChannel.js +70 -125
  122. package/dist/effect/WebChannel/WebChannel.js.map +1 -1
  123. package/dist/effect/WebChannel/WebChannel.test.js +16 -16
  124. package/dist/effect/WebChannel/WebChannel.test.js.map +1 -1
  125. package/dist/effect/WebChannel/broadcastChannelWithAck.d.ts.map +1 -1
  126. package/dist/effect/WebChannel/broadcastChannelWithAck.js +20 -13
  127. package/dist/effect/WebChannel/broadcastChannelWithAck.js.map +1 -1
  128. package/dist/effect/WebChannel/common.d.ts +17 -18
  129. package/dist/effect/WebChannel/common.d.ts.map +1 -1
  130. package/dist/effect/WebChannel/common.js +7 -7
  131. package/dist/effect/WebChannel/common.js.map +1 -1
  132. package/dist/effect/WebSocket.d.ts +4 -6
  133. package/dist/effect/WebSocket.d.ts.map +1 -1
  134. package/dist/effect/WebSocket.js +17 -17
  135. package/dist/effect/WebSocket.js.map +1 -1
  136. package/dist/effect/WebSocket.test.js +2 -2
  137. package/dist/effect/WebSocket.test.js.map +1 -1
  138. package/dist/effect/mod.browser-build.test.d.ts +2 -0
  139. package/dist/effect/mod.browser-build.test.d.ts.map +1 -0
  140. package/dist/effect/mod.browser-build.test.js +18 -0
  141. package/dist/effect/mod.browser-build.test.js.map +1 -0
  142. package/dist/effect/mod.d.ts +30 -0
  143. package/dist/effect/mod.d.ts.map +1 -0
  144. package/dist/effect/mod.js +30 -0
  145. package/dist/effect/mod.js.map +1 -0
  146. package/dist/effect/spanEvent.d.ts +12 -0
  147. package/dist/effect/spanEvent.d.ts.map +1 -0
  148. package/dist/effect/spanEvent.js +12 -0
  149. package/dist/effect/spanEvent.js.map +1 -0
  150. package/dist/effect/spanEvent.test.d.ts +2 -0
  151. package/dist/effect/spanEvent.test.d.ts.map +1 -0
  152. package/dist/effect/spanEvent.test.js +79 -0
  153. package/dist/effect/spanEvent.test.js.map +1 -0
  154. package/dist/env.d.ts.map +1 -1
  155. package/dist/env.js +1 -1
  156. package/dist/env.js.map +1 -1
  157. package/dist/fast-deep-equal.d.ts.map +1 -1
  158. package/dist/fast-deep-equal.js +9 -10
  159. package/dist/fast-deep-equal.js.map +1 -1
  160. package/dist/global.d.ts +3 -0
  161. package/dist/global.d.ts.map +1 -1
  162. package/dist/global.js.map +1 -1
  163. package/dist/misc.d.ts +9 -1
  164. package/dist/misc.d.ts.map +1 -1
  165. package/dist/misc.js +12 -4
  166. package/dist/misc.js.map +1 -1
  167. package/dist/mod.d.ts +177 -20
  168. package/dist/mod.d.ts.map +1 -1
  169. package/dist/mod.js +159 -22
  170. package/dist/mod.js.map +1 -1
  171. package/dist/node/mod.d.ts +45 -6
  172. package/dist/node/mod.d.ts.map +1 -1
  173. package/dist/node/mod.js +51 -9
  174. package/dist/node/mod.js.map +1 -1
  175. package/dist/object/index.d.ts +0 -9
  176. package/dist/object/index.d.ts.map +1 -1
  177. package/dist/object/index.js +0 -9
  178. package/dist/object/index.js.map +1 -1
  179. package/dist/object/stringify-object.js +2 -2
  180. package/dist/object/stringify-object.js.map +1 -1
  181. package/dist/object/stringify-object.test.js.map +1 -1
  182. package/dist/qr.d.ts +38 -0
  183. package/dist/qr.d.ts.map +1 -0
  184. package/dist/qr.js +109 -0
  185. package/dist/qr.js.map +1 -0
  186. package/dist/set.js +1 -1
  187. package/dist/set.js.map +1 -1
  188. package/dist/string.d.ts +0 -2
  189. package/dist/string.d.ts.map +1 -1
  190. package/dist/string.js +0 -2
  191. package/dist/string.js.map +1 -1
  192. package/package.json +61 -55
  193. package/src/NoopTracer.test.ts +20 -0
  194. package/src/NoopTracer.ts +28 -37
  195. package/src/binary.ts +1 -1
  196. package/src/browser/Opfs/Opfs.ts +463 -0
  197. package/src/browser/Opfs/debug-utils.ts +154 -0
  198. package/src/browser/Opfs/mod.ts +3 -0
  199. package/src/browser/Opfs/utils.ts +297 -0
  200. package/src/browser/QuotaExceededError.ts +60 -0
  201. package/src/browser/WebChannelBrowser.ts +145 -0
  202. package/src/browser/WebError.test.ts +66 -0
  203. package/src/browser/WebError.ts +587 -0
  204. package/src/{effect → browser}/WebLock.ts +27 -28
  205. package/src/{browser.ts → browser/detect.ts} +6 -6
  206. package/src/browser/mod.ts +8 -0
  207. package/src/cuid/cuid.browser.ts +1 -1
  208. package/src/cuid/cuid.node.ts +1 -1
  209. package/src/effect/Debug.ts +477 -0
  210. package/src/effect/Effect.test.ts +117 -0
  211. package/src/effect/Effect.ts +174 -79
  212. package/src/effect/Error.ts +1 -1
  213. package/src/effect/Logger.ts +0 -48
  214. package/src/effect/OtelTracer.ts +2 -3
  215. package/src/effect/RpcClient.ts +85 -65
  216. package/src/effect/Schedule.ts +4 -5
  217. package/src/effect/Schema/debug-diff.test.ts +7 -7
  218. package/src/effect/Schema/debug-diff.ts +11 -12
  219. package/src/effect/Schema/index.ts +121 -57
  220. package/src/effect/Stream.test.ts +14 -13
  221. package/src/effect/Stream.ts +23 -16
  222. package/src/effect/Subscribable.ts +31 -42
  223. package/src/effect/SubscriptionRef.ts +9 -13
  224. package/src/effect/TaskTracing.ts +13 -16
  225. package/src/effect/WebChannel/WebChannel.test.ts +24 -24
  226. package/src/effect/WebChannel/WebChannel.ts +102 -192
  227. package/src/effect/WebChannel/broadcastChannelWithAck.ts +25 -15
  228. package/src/effect/WebChannel/common.ts +16 -17
  229. package/src/effect/WebSocket.test.ts +2 -2
  230. package/src/effect/WebSocket.ts +20 -16
  231. package/src/effect/mod.browser-build.test.ts +19 -0
  232. package/src/effect/{index.ts → mod.ts} +43 -69
  233. package/src/effect/spanEvent.test.ts +92 -0
  234. package/src/effect/spanEvent.ts +17 -0
  235. package/src/env.ts +2 -1
  236. package/src/fast-deep-equal.ts +9 -10
  237. package/src/global.ts +4 -2
  238. package/src/misc.ts +13 -5
  239. package/src/mod.ts +188 -32
  240. package/src/node/mod.ts +57 -12
  241. package/src/object/index.ts +0 -24
  242. package/src/object/stringify-object.test.ts +1 -0
  243. package/src/object/stringify-object.ts +2 -2
  244. package/src/qr.ts +125 -0
  245. package/src/set.ts +1 -1
  246. package/src/string.ts +0 -3
  247. package/dist/.tsbuildinfo.json +0 -1
  248. package/dist/browser.d.ts.map +0 -1
  249. package/dist/browser.js.map +0 -1
  250. package/dist/effect/BucketQueue.d.ts +0 -20
  251. package/dist/effect/BucketQueue.d.ts.map +0 -1
  252. package/dist/effect/BucketQueue.js +0 -34
  253. package/dist/effect/BucketQueue.js.map +0 -1
  254. package/dist/effect/Scheduler.d.ts +0 -4
  255. package/dist/effect/Scheduler.d.ts.map +0 -1
  256. package/dist/effect/Scheduler.js +0 -9
  257. package/dist/effect/Scheduler.js.map +0 -1
  258. package/dist/effect/ServiceContext.d.ts +0 -37
  259. package/dist/effect/ServiceContext.d.ts.map +0 -1
  260. package/dist/effect/ServiceContext.js +0 -55
  261. package/dist/effect/ServiceContext.js.map +0 -1
  262. package/dist/effect/WebLock.d.ts.map +0 -1
  263. package/dist/effect/WebLock.js.map +0 -1
  264. package/dist/effect/index.d.ts +0 -32
  265. package/dist/effect/index.d.ts.map +0 -1
  266. package/dist/effect/index.js +0 -35
  267. package/dist/effect/index.js.map +0 -1
  268. package/dist/guards.d.ts +0 -6
  269. package/dist/guards.d.ts.map +0 -1
  270. package/dist/guards.js +0 -6
  271. package/dist/guards.js.map +0 -1
  272. package/dist/node/ChildProcessRunner/ChildProcessRunner.d.ts +0 -4
  273. package/dist/node/ChildProcessRunner/ChildProcessRunner.d.ts.map +0 -1
  274. package/dist/node/ChildProcessRunner/ChildProcessRunner.js +0 -122
  275. package/dist/node/ChildProcessRunner/ChildProcessRunner.js.map +0 -1
  276. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.d.ts +0 -2
  277. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.d.ts.map +0 -1
  278. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.js +0 -218
  279. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.js.map +0 -1
  280. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.d.ts +0 -84
  281. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.d.ts.map +0 -1
  282. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.js +0 -68
  283. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.js.map +0 -1
  284. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.d.ts +0 -2
  285. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.d.ts.map +0 -1
  286. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.js +0 -52
  287. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.js.map +0 -1
  288. package/dist/node/ChildProcessRunner/ChildProcessWorker.d.ts +0 -23
  289. package/dist/node/ChildProcessRunner/ChildProcessWorker.d.ts.map +0 -1
  290. package/dist/node/ChildProcessRunner/ChildProcessWorker.js +0 -138
  291. package/dist/node/ChildProcessRunner/ChildProcessWorker.js.map +0 -1
  292. package/dist/object/omit.d.ts +0 -3
  293. package/dist/object/omit.d.ts.map +0 -1
  294. package/dist/object/omit.js +0 -14
  295. package/dist/object/omit.js.map +0 -1
  296. package/dist/object/pick.d.ts +0 -14
  297. package/dist/object/pick.d.ts.map +0 -1
  298. package/dist/object/pick.js +0 -17
  299. package/dist/object/pick.js.map +0 -1
  300. package/dist/time.d.ts +0 -12
  301. package/dist/time.d.ts.map +0 -1
  302. package/dist/time.js +0 -22
  303. package/dist/time.js.map +0 -1
  304. package/src/effect/BucketQueue.ts +0 -49
  305. package/src/effect/Scheduler.ts +0 -13
  306. package/src/effect/ServiceContext.ts +0 -108
  307. package/src/guards.ts +0 -8
  308. package/src/node/ChildProcessRunner/ChildProcessRunner.ts +0 -135
  309. package/src/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.ts +0 -302
  310. package/src/node/ChildProcessRunner/ChildProcessRunnerTest/schema.ts +0 -78
  311. package/src/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.ts +0 -66
  312. package/src/node/ChildProcessRunner/ChildProcessWorker.ts +0 -168
  313. package/src/object/omit.ts +0 -17
  314. package/src/object/pick.ts +0 -27
  315. package/src/time.ts +0 -25
  316. /package/dist/{effect → browser}/WebLock.d.ts +0 -0
@@ -1,5 +1,5 @@
1
- import { Deferred, Either, Exit, GlobalValue, identity, Option, PubSub, Queue, Scope } from 'effect'
2
- import type { DurationInput } from 'effect/Duration'
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>().pipe(Effect.acquireRelease(Deferred.done(Exit.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.fromEventListener<MessageEvent>(port, 'message').pipe(
68
+ const listen = Stream.fromQueue(messageQueue).pipe(
180
69
  // Stream.tap((_) => Effect.log(`${label}:message`, _.data)),
181
- Stream.map((_) => Schema.decodeEither(schema.listen)(_.data)),
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>().pipe(Effect.acquireRelease(Deferred.done(Exit.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(() => Effect.try(() => port.close()).pipe(Effect.ignoreLogged))
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 = GlobalValue.globalValue(
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>().pipe(Effect.acquireRelease(PubSub.shutdown))
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(Either.right), listenToDebugPing(channelName))
121
+ const listen = Stream.fromPubSub(pubSub).pipe(Stream.map(Result.succeed), listenToDebugPing(channelName))
234
122
 
235
- const closedDeferred = yield* Deferred.make<void>().pipe(Effect.acquireRelease(Deferred.done(Exit.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
- }).annotations({ title: 'webmesh.ChannelRequest' })
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
- }).annotations({ title: 'webmesh.ChannelRequestAck' })
270
- const ChannelMessage = Schema.Union(ChannelRequest, ChannelRequestAck).annotations({
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
- // TODO re-implement this via `port.onmessage`
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.decodeEither(ChannelMessage)(_.data)),
205
+ Stream.map((_) => Schema.decodeResult(ChannelMessage)(_.data)),
311
206
  Stream.tap((msg) =>
312
207
  Effect.gen(function* () {
313
- if (msg._tag === 'Right') {
314
- if (msg.right._tag === 'ChannelRequestAck') {
315
- yield* Deferred.succeed(requestAckMap.get(msg.right.reqId)!, void 0)
316
- } else if (msg.right._tag === 'ChannelRequest') {
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(Schema.encodeSync(ChannelMessage)({ _tag: 'ChannelRequestAck', reqId: msg.right.id }))
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((msg) =>
324
- msg._tag === 'Left'
325
- ? Option.some(msg as any)
326
- : msg.right._tag === 'ChannelRequest'
327
- ? Option.some(Either.right(msg.right.payload))
328
- : Option.none(),
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>().pipe(Effect.acquireRelease(Deferred.done(Exit.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(() => Effect.try(() => port.close()).pipe(Effect.ignoreLogged))
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.Schema<MsgListen | MsgSend, any>
378
- | { listen: Schema.Schema<MsgListen, any>; send: Schema.Schema<MsgSend, any> }
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>().pipe(Effect.acquireRelease(Queue.shutdown))
383
- const listenQueue = yield* Queue.unbounded<MsgListen>().pipe(Effect.acquireRelease(Queue.shutdown))
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(Either.right), listenToDebugPing('queueChannel'))
295
+ const listen = Stream.fromQueue(listenQueue).pipe(Stream.map(Result.succeed), listenToDebugPing('queueChannel'))
388
296
 
389
- const closedDeferred = yield* Deferred.make<void>().pipe(Effect.acquireRelease(Deferred.done(Exit.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<any, any>,
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: DurationInput
420
- timeout: DurationInput
327
+ interval: Duration.Input
328
+ timeout: Duration.Input
421
329
  }
422
330
  },
423
- ): Effect.Effect<WebChannel<any, any>, never, Scope.Scope> =>
331
+ ): Effect.Effect<WebChannel<MsgListen, MsgSend>, never, Scope.Scope> =>
424
332
  Effect.gen(function* () {
425
- const queue = yield* Queue.unbounded<Either.Either<any, any>>().pipe(Effect.acquireRelease(Queue.shutdown))
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._tag === 'Right' && Schema.is(WebChannelHeartbeat)(msg.right)) {
437
- if (msg.right._tag === 'WebChannel.Ping') {
438
- yield* channel.send(WebChannelPong.make({ requestId: msg.right.requestId }))
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.right.requestId) {
442
- shouldNeverHappen('Received pong for unexpected requestId', requestId, msg.right.requestId)
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.tapChunk((chunk) => Queue.offerAll(queue, chunk)),
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* channel.send(WebChannelPing.make({ requestId }))
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('TimeoutException', () => channel.shutdown),
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, { maxChunkSize: 1 })
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, Schema, Scope, Stream } from 'effect'
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
- const messageQueue = yield* Queue.unbounded<MsgSend>()
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* Effect.makeLatch(false)
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.encode(schema.send)(message)
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.fromEventListener<MessageEvent>(channel, 'message').pipe(
69
+ const listen = Stream.fromQueue(messageQueue).pipe(
61
70
  Stream.map(({ data }) => data),
62
- Stream.map(Schema.decodeOption(Message)),
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
- break
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
- break
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
- break
96
+ return undefined
89
97
  }
90
98
  case 'PayloadMessage': {
91
99
  if (data.to === connectionId) {
92
- return Schema.decodeEither(schema.listen)(data.payload)
100
+ return Schema.decodeResult(schema.listen)(data.payload)
93
101
  }
94
- break
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>().pipe(Effect.acquireRelease(Deferred.done(Exit.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 }