@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.
Files changed (247) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/NoopTracer.d.ts +7 -8
  3. package/dist/NoopTracer.d.ts.map +1 -1
  4. package/dist/NoopTracer.js +3 -18
  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/browser/Opfs/Opfs.d.ts +179 -29
  11. package/dist/browser/Opfs/Opfs.d.ts.map +1 -1
  12. package/dist/browser/Opfs/Opfs.js +132 -307
  13. package/dist/browser/Opfs/Opfs.js.map +1 -1
  14. package/dist/browser/Opfs/debug-utils.d.ts.map +1 -1
  15. package/dist/browser/Opfs/debug-utils.js +5 -4
  16. package/dist/browser/Opfs/debug-utils.js.map +1 -1
  17. package/dist/browser/Opfs/utils.d.ts +8 -8
  18. package/dist/browser/Opfs/utils.d.ts.map +1 -1
  19. package/dist/browser/Opfs/utils.js +35 -26
  20. package/dist/browser/Opfs/utils.js.map +1 -1
  21. package/dist/browser/QuotaExceededError.d.ts +10 -9
  22. package/dist/browser/QuotaExceededError.d.ts.map +1 -1
  23. package/dist/browser/WebChannelBrowser.d.ts.map +1 -1
  24. package/dist/browser/WebChannelBrowser.js +21 -10
  25. package/dist/browser/WebChannelBrowser.js.map +1 -1
  26. package/dist/browser/WebError.d.ts +54 -86
  27. package/dist/browser/WebError.d.ts.map +1 -1
  28. package/dist/browser/WebError.js +91 -84
  29. package/dist/browser/WebError.js.map +1 -1
  30. package/dist/browser/WebError.test.js +6 -6
  31. package/dist/browser/WebError.test.js.map +1 -1
  32. package/dist/browser/WebLock.d.ts.map +1 -1
  33. package/dist/browser/WebLock.js +13 -13
  34. package/dist/browser/WebLock.js.map +1 -1
  35. package/dist/effect/Debug.d.ts +4 -7
  36. package/dist/effect/Debug.d.ts.map +1 -1
  37. package/dist/effect/Debug.js +32 -36
  38. package/dist/effect/Debug.js.map +1 -1
  39. package/dist/effect/Effect.d.ts +31 -22
  40. package/dist/effect/Effect.d.ts.map +1 -1
  41. package/dist/effect/Effect.js +51 -47
  42. package/dist/effect/Effect.js.map +1 -1
  43. package/dist/effect/Effect.test.d.ts +2 -0
  44. package/dist/effect/Effect.test.d.ts.map +1 -0
  45. package/dist/effect/Effect.test.js +72 -0
  46. package/dist/effect/Effect.test.js.map +1 -0
  47. package/dist/effect/Error.d.ts +4 -6
  48. package/dist/effect/Error.d.ts.map +1 -1
  49. package/dist/effect/Logger.d.ts +0 -6
  50. package/dist/effect/Logger.d.ts.map +1 -1
  51. package/dist/effect/Logger.js +0 -32
  52. package/dist/effect/Logger.js.map +1 -1
  53. package/dist/effect/OtelTracer.d.ts +1 -1
  54. package/dist/effect/OtelTracer.d.ts.map +1 -1
  55. package/dist/effect/OtelTracer.js +2 -3
  56. package/dist/effect/OtelTracer.js.map +1 -1
  57. package/dist/effect/RpcClient.d.ts +7 -7
  58. package/dist/effect/RpcClient.d.ts.map +1 -1
  59. package/dist/effect/RpcClient.js +72 -52
  60. package/dist/effect/RpcClient.js.map +1 -1
  61. package/dist/effect/Schedule.d.ts +1 -1
  62. package/dist/effect/Schedule.d.ts.map +1 -1
  63. package/dist/effect/Schedule.js +2 -2
  64. package/dist/effect/Schedule.js.map +1 -1
  65. package/dist/effect/Schema/debug-diff.d.ts +1 -1
  66. package/dist/effect/Schema/debug-diff.d.ts.map +1 -1
  67. package/dist/effect/Schema/debug-diff.js +5 -7
  68. package/dist/effect/Schema/debug-diff.js.map +1 -1
  69. package/dist/effect/Schema/debug-diff.test.js +7 -4
  70. package/dist/effect/Schema/debug-diff.test.js.map +1 -1
  71. package/dist/effect/Schema/index.d.ts +28 -20
  72. package/dist/effect/Schema/index.d.ts.map +1 -1
  73. package/dist/effect/Schema/index.js +55 -28
  74. package/dist/effect/Schema/index.js.map +1 -1
  75. package/dist/effect/Stream.d.ts +13 -5
  76. package/dist/effect/Stream.d.ts.map +1 -1
  77. package/dist/effect/Stream.js +19 -12
  78. package/dist/effect/Stream.js.map +1 -1
  79. package/dist/effect/Stream.test.js +9 -9
  80. package/dist/effect/Stream.test.js.map +1 -1
  81. package/dist/effect/Subscribable.d.ts +9 -3
  82. package/dist/effect/Subscribable.d.ts.map +1 -1
  83. package/dist/effect/Subscribable.js +20 -35
  84. package/dist/effect/Subscribable.js.map +1 -1
  85. package/dist/effect/SubscriptionRef.d.ts +5 -6
  86. package/dist/effect/SubscriptionRef.d.ts.map +1 -1
  87. package/dist/effect/SubscriptionRef.js +2 -3
  88. package/dist/effect/SubscriptionRef.js.map +1 -1
  89. package/dist/effect/TaskTracing.d.ts +1 -1
  90. package/dist/effect/TaskTracing.d.ts.map +1 -1
  91. package/dist/effect/TaskTracing.js +12 -16
  92. package/dist/effect/TaskTracing.js.map +1 -1
  93. package/dist/effect/WebChannel/WebChannel.d.ts +7 -7
  94. package/dist/effect/WebChannel/WebChannel.d.ts.map +1 -1
  95. package/dist/effect/WebChannel/WebChannel.js +64 -43
  96. package/dist/effect/WebChannel/WebChannel.js.map +1 -1
  97. package/dist/effect/WebChannel/WebChannel.test.js +15 -15
  98. package/dist/effect/WebChannel/WebChannel.test.js.map +1 -1
  99. package/dist/effect/WebChannel/broadcastChannelWithAck.d.ts.map +1 -1
  100. package/dist/effect/WebChannel/broadcastChannelWithAck.js +16 -9
  101. package/dist/effect/WebChannel/broadcastChannelWithAck.js.map +1 -1
  102. package/dist/effect/WebChannel/common.d.ts +16 -17
  103. package/dist/effect/WebChannel/common.d.ts.map +1 -1
  104. package/dist/effect/WebChannel/common.js +6 -6
  105. package/dist/effect/WebChannel/common.js.map +1 -1
  106. package/dist/effect/WebSocket.d.ts +4 -6
  107. package/dist/effect/WebSocket.d.ts.map +1 -1
  108. package/dist/effect/WebSocket.js +4 -4
  109. package/dist/effect/WebSocket.js.map +1 -1
  110. package/dist/effect/WebSocket.test.js +2 -2
  111. package/dist/effect/WebSocket.test.js.map +1 -1
  112. package/dist/effect/mod.browser-build.test.d.ts +2 -0
  113. package/dist/effect/mod.browser-build.test.d.ts.map +1 -0
  114. package/dist/effect/mod.browser-build.test.js +18 -0
  115. package/dist/effect/mod.browser-build.test.js.map +1 -0
  116. package/dist/effect/mod.d.ts +13 -15
  117. package/dist/effect/mod.d.ts.map +1 -1
  118. package/dist/effect/mod.js +13 -18
  119. package/dist/effect/mod.js.map +1 -1
  120. package/dist/effect/spanEvent.d.ts.map +1 -1
  121. package/dist/effect/spanEvent.js +2 -2
  122. package/dist/effect/spanEvent.js.map +1 -1
  123. package/dist/effect/spanEvent.test.js +10 -13
  124. package/dist/effect/spanEvent.test.js.map +1 -1
  125. package/dist/fast-deep-equal.d.ts.map +1 -1
  126. package/dist/fast-deep-equal.js +1 -2
  127. package/dist/fast-deep-equal.js.map +1 -1
  128. package/dist/misc.js +2 -2
  129. package/dist/misc.js.map +1 -1
  130. package/dist/mod.d.ts +0 -35
  131. package/dist/mod.d.ts.map +1 -1
  132. package/dist/mod.js +0 -8
  133. package/dist/mod.js.map +1 -1
  134. package/dist/node/mod.d.ts +17 -11
  135. package/dist/node/mod.d.ts.map +1 -1
  136. package/dist/node/mod.js +23 -16
  137. package/dist/node/mod.js.map +1 -1
  138. package/dist/object/index.d.ts +0 -9
  139. package/dist/object/index.d.ts.map +1 -1
  140. package/dist/object/index.js +0 -9
  141. package/dist/object/index.js.map +1 -1
  142. package/dist/string.d.ts +0 -2
  143. package/dist/string.d.ts.map +1 -1
  144. package/dist/string.js +0 -2
  145. package/dist/string.js.map +1 -1
  146. package/package.json +18 -36
  147. package/src/NoopTracer.test.ts +20 -0
  148. package/src/NoopTracer.ts +18 -41
  149. package/src/browser/Opfs/Opfs.ts +304 -277
  150. package/src/browser/Opfs/debug-utils.ts +7 -6
  151. package/src/browser/Opfs/utils.ts +36 -26
  152. package/src/browser/QuotaExceededError.ts +13 -10
  153. package/src/browser/WebChannelBrowser.ts +25 -11
  154. package/src/browser/WebError.test.ts +6 -6
  155. package/src/browser/WebError.ts +107 -133
  156. package/src/browser/WebLock.ts +13 -14
  157. package/src/effect/Debug.ts +66 -59
  158. package/src/effect/Effect.test.ts +117 -0
  159. package/src/effect/Effect.ts +84 -71
  160. package/src/effect/Logger.ts +0 -48
  161. package/src/effect/OtelTracer.ts +2 -3
  162. package/src/effect/RpcClient.ts +80 -61
  163. package/src/effect/Schedule.ts +4 -5
  164. package/src/effect/Schema/debug-diff.test.ts +6 -6
  165. package/src/effect/Schema/debug-diff.ts +6 -8
  166. package/src/effect/Schema/index.ts +122 -61
  167. package/src/effect/Stream.test.ts +9 -9
  168. package/src/effect/Stream.ts +23 -16
  169. package/src/effect/Subscribable.ts +31 -42
  170. package/src/effect/SubscriptionRef.ts +8 -12
  171. package/src/effect/TaskTracing.ts +13 -16
  172. package/src/effect/WebChannel/WebChannel.test.ts +23 -23
  173. package/src/effect/WebChannel/WebChannel.ts +87 -55
  174. package/src/effect/WebChannel/broadcastChannelWithAck.ts +21 -11
  175. package/src/effect/WebChannel/common.ts +14 -15
  176. package/src/effect/WebSocket.test.ts +2 -2
  177. package/src/effect/WebSocket.ts +9 -6
  178. package/src/effect/mod.browser-build.test.ts +19 -0
  179. package/src/effect/mod.ts +35 -69
  180. package/src/effect/spanEvent.test.ts +10 -13
  181. package/src/effect/spanEvent.ts +5 -3
  182. package/src/fast-deep-equal.ts +1 -2
  183. package/src/misc.ts +2 -2
  184. package/src/mod.ts +0 -36
  185. package/src/node/mod.ts +26 -20
  186. package/src/object/index.ts +0 -24
  187. package/src/string.ts +0 -3
  188. package/dist/effect/BucketQueue.d.ts +0 -20
  189. package/dist/effect/BucketQueue.d.ts.map +0 -1
  190. package/dist/effect/BucketQueue.js +0 -34
  191. package/dist/effect/BucketQueue.js.map +0 -1
  192. package/dist/effect/Scheduler.d.ts +0 -4
  193. package/dist/effect/Scheduler.d.ts.map +0 -1
  194. package/dist/effect/Scheduler.js +0 -9
  195. package/dist/effect/Scheduler.js.map +0 -1
  196. package/dist/effect/ServiceContext.d.ts +0 -37
  197. package/dist/effect/ServiceContext.d.ts.map +0 -1
  198. package/dist/effect/ServiceContext.js +0 -55
  199. package/dist/effect/ServiceContext.js.map +0 -1
  200. package/dist/guards.d.ts +0 -20
  201. package/dist/guards.d.ts.map +0 -1
  202. package/dist/guards.js +0 -20
  203. package/dist/guards.js.map +0 -1
  204. package/dist/node/ChildProcessRunner/ChildProcessRunner.d.ts +0 -4
  205. package/dist/node/ChildProcessRunner/ChildProcessRunner.d.ts.map +0 -1
  206. package/dist/node/ChildProcessRunner/ChildProcessRunner.js +0 -128
  207. package/dist/node/ChildProcessRunner/ChildProcessRunner.js.map +0 -1
  208. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.d.ts +0 -10
  209. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.d.ts.map +0 -1
  210. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.js +0 -217
  211. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.js.map +0 -1
  212. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.d.ts +0 -84
  213. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.d.ts.map +0 -1
  214. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.js +0 -68
  215. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.js.map +0 -1
  216. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.d.ts +0 -2
  217. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.d.ts.map +0 -1
  218. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.js +0 -52
  219. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.js.map +0 -1
  220. package/dist/node/ChildProcessRunner/ChildProcessWorker.d.ts +0 -23
  221. package/dist/node/ChildProcessRunner/ChildProcessWorker.d.ts.map +0 -1
  222. package/dist/node/ChildProcessRunner/ChildProcessWorker.js +0 -138
  223. package/dist/node/ChildProcessRunner/ChildProcessWorker.js.map +0 -1
  224. package/dist/object/omit.d.ts +0 -3
  225. package/dist/object/omit.d.ts.map +0 -1
  226. package/dist/object/omit.js +0 -14
  227. package/dist/object/omit.js.map +0 -1
  228. package/dist/object/pick.d.ts +0 -14
  229. package/dist/object/pick.d.ts.map +0 -1
  230. package/dist/object/pick.js +0 -17
  231. package/dist/object/pick.js.map +0 -1
  232. package/dist/time.d.ts +0 -12
  233. package/dist/time.d.ts.map +0 -1
  234. package/dist/time.js +0 -22
  235. package/dist/time.js.map +0 -1
  236. package/src/effect/BucketQueue.ts +0 -49
  237. package/src/effect/Scheduler.ts +0 -13
  238. package/src/effect/ServiceContext.ts +0 -108
  239. package/src/guards.ts +0 -23
  240. package/src/node/ChildProcessRunner/ChildProcessRunner.ts +0 -148
  241. package/src/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.ts +0 -311
  242. package/src/node/ChildProcessRunner/ChildProcessRunnerTest/schema.ts +0 -78
  243. package/src/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.ts +0 -65
  244. package/src/node/ChildProcessRunner/ChildProcessWorker.ts +0 -169
  245. package/src/object/omit.ts +0 -17
  246. package/src/object/pick.ts +0 -27
  247. 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.scopedLive('should work with 2 windows', () =>
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.Number,
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.flatten,
26
- Effect.fork,
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.Number,
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.flatten,
46
- Effect.fork,
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.scopedLive('should work with the same window', () =>
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.Number,
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.flatten,
74
- Effect.fork,
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.Number,
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.flatten,
94
- Effect.fork,
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 { 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,
@@ -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.fromEventListener<MessageEvent>(port, 'message').pipe(
68
+ const listen = Stream.fromQueue(messageQueue).pipe(
59
69
  // Stream.tap((_) => Effect.log(`${label}:message`, _.data)),
60
- Stream.map((_) => Schema.decodeEither(schema.listen)(_.data)),
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>().pipe(Effect.acquireRelease(Deferred.done(Exit.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(() => 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
+ )
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 = GlobalValue.globalValue(
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>().pipe(Effect.acquireRelease(PubSub.shutdown))
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(Either.right), listenToDebugPing(channelName))
121
+ const listen = Stream.fromPubSub(pubSub).pipe(Stream.map(Result.succeed), listenToDebugPing(channelName))
110
122
 
111
- 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))
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
- }).annotations({ title: 'webmesh.ChannelRequest' })
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
- }).annotations({ title: 'webmesh.ChannelRequestAck' })
146
- const ChannelMessage = Schema.Union(ChannelRequest, ChannelRequestAck).annotations({
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
- // TODO re-implement this via `port.onmessage`
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.decodeEither(ChannelMessage)(_.data)),
205
+ Stream.map((_) => Schema.decodeResult(ChannelMessage)(_.data)),
187
206
  Stream.tap((msg) =>
188
207
  Effect.gen(function* () {
189
- if (msg._tag === 'Right') {
190
- if (msg.right._tag === 'ChannelRequestAck') {
191
- yield* Deferred.succeed(requestAckMap.get(msg.right.reqId)!, void 0)
192
- } 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') {
193
212
  debugInfo.listenTotal++
194
- port.postMessage(yield* Schema.encode(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
+ )
195
218
  }
196
219
  }
197
220
  }),
198
221
  ),
199
- Stream.filterMap((msg) =>
200
- msg._tag === 'Left'
201
- ? Option.some(msg as any)
202
- : msg.right._tag === 'ChannelRequest'
203
- ? Option.some(Either.right(msg.right.payload))
204
- : 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
+ ),
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>().pipe(Effect.acquireRelease(Deferred.done(Exit.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(() => 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
+ )
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.Schema<MsgListen | MsgSend, any>
254
- | { 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> }
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>().pipe(Effect.acquireRelease(Queue.shutdown))
259
- 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)
260
292
 
261
293
  const send = (message: MsgSend) => Queue.offer(sendQueue, message)
262
294
 
263
- 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'))
264
296
 
265
- 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))
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: DurationInput
296
- timeout: DurationInput
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<Either.Either<MsgListen, any>>().pipe(Effect.acquireRelease(Queue.shutdown))
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._tag === 'Right' && Schema.is(WebChannelHeartbeat)(msg.right) === true) {
318
- if (msg.right._tag === 'WebChannel.Ping') {
319
- yield* heartbeatChannel.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 }))
320
352
  } else {
321
353
  const { deferred, requestId } = pendingPingDeferredRef.current ?? shouldNeverHappen('No pending ping')
322
- if (requestId !== msg.right.requestId) {
323
- 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)
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.tapChunk((chunk) => Queue.offerAll(queue, chunk)),
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('TimeoutException', () => channel.shutdown),
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, { maxChunkSize: 1 })
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, 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) {
@@ -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.decodeEither(schema.listen)(data.payload)
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>().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 }
@@ -1,5 +1,4 @@
1
- import type { Deferred, Either, ParseResult } from 'effect'
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, ParseResult.ParseError | E>
13
- listen: Stream.Stream<Either.Either<MsgListen, ParseResult.ParseError>, E>
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.Schema<MsgListen, any>; send: Schema.Schema<MsgSend, any> }
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.Schema<MsgListen | MsgSend, MsgListenEncoded | MsgSendEncoded>
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.Schema<MsgListen, MsgListenEncoded>
51
- send: Schema.Schema<MsgSend, MsgSendEncoded>
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<Either.Either<MsgListen, ParseResult.ParseError>>,
65
- ): Stream.Stream<Either.Either<MsgListen, ParseResult.ParseError>> =>
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._tag === 'Right' && Schema.is(DebugPingMessage)(msg.right) === true) {
70
- yield* Effect.logDebug(`WebChannel:ping [${channelName}] ${msg.right.message}`, msg.right.payload)
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.scopedLive(
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)
@@ -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')('WebSocketError', {
6
- cause: Schema.Defect,
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.async<globalThis.WebSocket, WebSocketError>((cb, signal) => {
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
+ })