@livestore/utils 0.4.0-dev.8 → 0.4.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 (226) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/NoopTracer.d.ts.map +1 -1
  3. package/dist/NoopTracer.js +17 -4
  4. package/dist/NoopTracer.js.map +1 -1
  5. package/dist/binary.js +1 -1
  6. package/dist/binary.js.map +1 -1
  7. package/dist/browser/Opfs/Opfs.d.ts +51 -0
  8. package/dist/browser/Opfs/Opfs.d.ts.map +1 -0
  9. package/dist/browser/Opfs/Opfs.js +345 -0
  10. package/dist/browser/Opfs/Opfs.js.map +1 -0
  11. package/dist/browser/Opfs/debug-utils.d.ts +20 -0
  12. package/dist/browser/Opfs/debug-utils.d.ts.map +1 -0
  13. package/dist/browser/Opfs/debug-utils.js +94 -0
  14. package/dist/browser/Opfs/debug-utils.js.map +1 -0
  15. package/dist/browser/Opfs/mod.d.ts +4 -0
  16. package/dist/browser/Opfs/mod.d.ts.map +1 -0
  17. package/dist/browser/Opfs/mod.js +4 -0
  18. package/dist/browser/Opfs/mod.js.map +1 -0
  19. package/dist/browser/Opfs/utils.d.ts +71 -0
  20. package/dist/browser/Opfs/utils.d.ts.map +1 -0
  21. package/dist/browser/Opfs/utils.js +218 -0
  22. package/dist/browser/Opfs/utils.js.map +1 -0
  23. package/dist/browser/QuotaExceededError.d.ts +59 -0
  24. package/dist/browser/QuotaExceededError.d.ts.map +1 -0
  25. package/dist/browser/QuotaExceededError.js +2 -0
  26. package/dist/browser/QuotaExceededError.js.map +1 -0
  27. package/dist/browser/WebChannelBrowser.d.ts +22 -0
  28. package/dist/browser/WebChannelBrowser.d.ts.map +1 -0
  29. package/dist/browser/WebChannelBrowser.js +76 -0
  30. package/dist/browser/WebChannelBrowser.js.map +1 -0
  31. package/dist/browser/WebError.d.ts +421 -0
  32. package/dist/browser/WebError.d.ts.map +1 -0
  33. package/dist/browser/WebError.js +416 -0
  34. package/dist/browser/WebError.js.map +1 -0
  35. package/dist/browser/WebError.test.d.ts +2 -0
  36. package/dist/browser/WebError.test.d.ts.map +1 -0
  37. package/dist/browser/WebError.test.js +46 -0
  38. package/dist/browser/WebError.test.js.map +1 -0
  39. package/dist/browser/WebLock.d.ts.map +1 -0
  40. package/dist/{effect → browser}/WebLock.js +9 -9
  41. package/dist/browser/WebLock.js.map +1 -0
  42. package/dist/{browser.d.ts → browser/detect.d.ts} +1 -1
  43. package/dist/browser/detect.d.ts.map +1 -0
  44. package/dist/{browser.js → browser/detect.js} +7 -7
  45. package/dist/browser/detect.js.map +1 -0
  46. package/dist/browser/mod.d.ts +8 -0
  47. package/dist/browser/mod.d.ts.map +1 -0
  48. package/dist/browser/mod.js +8 -0
  49. package/dist/browser/mod.js.map +1 -0
  50. package/dist/cuid/cuid.browser.js +1 -1
  51. package/dist/cuid/cuid.browser.js.map +1 -1
  52. package/dist/cuid/cuid.node.js +1 -1
  53. package/dist/cuid/cuid.node.js.map +1 -1
  54. package/dist/effect/BucketQueue.d.ts +1 -1
  55. package/dist/effect/BucketQueue.d.ts.map +1 -1
  56. package/dist/effect/BucketQueue.js.map +1 -1
  57. package/dist/effect/Debug.d.ts +41 -0
  58. package/dist/effect/Debug.d.ts.map +1 -0
  59. package/dist/effect/Debug.js +354 -0
  60. package/dist/effect/Debug.js.map +1 -0
  61. package/dist/effect/Effect.d.ts +72 -12
  62. package/dist/effect/Effect.d.ts.map +1 -1
  63. package/dist/effect/Effect.js +96 -12
  64. package/dist/effect/Effect.js.map +1 -1
  65. package/dist/effect/Error.js +1 -1
  66. package/dist/effect/Error.js.map +1 -1
  67. package/dist/effect/Logger.js +2 -2
  68. package/dist/effect/Logger.js.map +1 -1
  69. package/dist/effect/RpcClient.d.ts.map +1 -1
  70. package/dist/effect/RpcClient.js +11 -4
  71. package/dist/effect/RpcClient.js.map +1 -1
  72. package/dist/effect/Schema/debug-diff.js +5 -4
  73. package/dist/effect/Schema/debug-diff.js.map +1 -1
  74. package/dist/effect/Schema/debug-diff.test.js +1 -1
  75. package/dist/effect/Schema/debug-diff.test.js.map +1 -1
  76. package/dist/effect/Schema/index.d.ts +5 -3
  77. package/dist/effect/Schema/index.d.ts.map +1 -1
  78. package/dist/effect/Schema/index.js +2 -2
  79. package/dist/effect/Schema/index.js.map +1 -1
  80. package/dist/effect/ServiceContext.js +6 -6
  81. package/dist/effect/ServiceContext.js.map +1 -1
  82. package/dist/effect/Stream.test.js +3 -3
  83. package/dist/effect/Stream.test.js.map +1 -1
  84. package/dist/effect/SubscriptionRef.d.ts +4 -4
  85. package/dist/effect/SubscriptionRef.d.ts.map +1 -1
  86. package/dist/effect/WebChannel/WebChannel.d.ts +4 -23
  87. package/dist/effect/WebChannel/WebChannel.d.ts.map +1 -1
  88. package/dist/effect/WebChannel/WebChannel.js +9 -85
  89. package/dist/effect/WebChannel/WebChannel.js.map +1 -1
  90. package/dist/effect/WebChannel/WebChannel.test.js +1 -1
  91. package/dist/effect/WebChannel/WebChannel.test.js.map +1 -1
  92. package/dist/effect/WebChannel/broadcastChannelWithAck.js +4 -4
  93. package/dist/effect/WebChannel/broadcastChannelWithAck.js.map +1 -1
  94. package/dist/effect/WebChannel/common.d.ts +2 -2
  95. package/dist/effect/WebChannel/common.d.ts.map +1 -1
  96. package/dist/effect/WebChannel/common.js +2 -2
  97. package/dist/effect/WebChannel/common.js.map +1 -1
  98. package/dist/effect/WebSocket.d.ts.map +1 -1
  99. package/dist/effect/WebSocket.js +14 -14
  100. package/dist/effect/WebSocket.js.map +1 -1
  101. package/dist/effect/{index.d.ts → mod.d.ts} +4 -4
  102. package/dist/effect/mod.d.ts.map +1 -0
  103. package/dist/effect/{index.js → mod.js} +5 -5
  104. package/dist/effect/mod.js.map +1 -0
  105. package/dist/effect/spanEvent.d.ts +12 -0
  106. package/dist/effect/spanEvent.d.ts.map +1 -0
  107. package/dist/effect/spanEvent.js +12 -0
  108. package/dist/effect/spanEvent.js.map +1 -0
  109. package/dist/effect/spanEvent.test.d.ts +2 -0
  110. package/dist/effect/spanEvent.test.d.ts.map +1 -0
  111. package/dist/effect/spanEvent.test.js +82 -0
  112. package/dist/effect/spanEvent.test.js.map +1 -0
  113. package/dist/env.d.ts.map +1 -1
  114. package/dist/env.js +1 -1
  115. package/dist/env.js.map +1 -1
  116. package/dist/fast-deep-equal.js +9 -9
  117. package/dist/fast-deep-equal.js.map +1 -1
  118. package/dist/global.d.ts +3 -0
  119. package/dist/global.d.ts.map +1 -1
  120. package/dist/global.js.map +1 -1
  121. package/dist/guards.d.ts +14 -0
  122. package/dist/guards.d.ts.map +1 -1
  123. package/dist/guards.js +14 -0
  124. package/dist/guards.js.map +1 -1
  125. package/dist/misc.d.ts +9 -1
  126. package/dist/misc.d.ts.map +1 -1
  127. package/dist/misc.js +11 -3
  128. package/dist/misc.js.map +1 -1
  129. package/dist/mod.d.ts +197 -5
  130. package/dist/mod.d.ts.map +1 -1
  131. package/dist/mod.js +162 -17
  132. package/dist/mod.js.map +1 -1
  133. package/dist/node/ChildProcessRunner/ChildProcessRunner.d.ts.map +1 -1
  134. package/dist/node/ChildProcessRunner/ChildProcessRunner.js +15 -9
  135. package/dist/node/ChildProcessRunner/ChildProcessRunner.js.map +1 -1
  136. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.d.ts +8 -0
  137. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.d.ts.map +1 -1
  138. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.js +16 -17
  139. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.js.map +1 -1
  140. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.d.ts +4 -4
  141. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.js +3 -3
  142. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.js.map +1 -1
  143. package/dist/node/ChildProcessRunner/ChildProcessWorker.d.ts.map +1 -1
  144. package/dist/node/ChildProcessRunner/ChildProcessWorker.js +4 -4
  145. package/dist/node/ChildProcessRunner/ChildProcessWorker.js.map +1 -1
  146. package/dist/node/mod.d.ts +34 -1
  147. package/dist/node/mod.d.ts.map +1 -1
  148. package/dist/node/mod.js +37 -2
  149. package/dist/node/mod.js.map +1 -1
  150. package/dist/object/index.d.ts.map +1 -1
  151. package/dist/object/index.js.map +1 -1
  152. package/dist/object/omit.js +1 -1
  153. package/dist/object/omit.js.map +1 -1
  154. package/dist/object/stringify-object.js +2 -2
  155. package/dist/object/stringify-object.js.map +1 -1
  156. package/dist/object/stringify-object.test.js.map +1 -1
  157. package/dist/qr.d.ts +38 -0
  158. package/dist/qr.d.ts.map +1 -0
  159. package/dist/qr.js +109 -0
  160. package/dist/qr.js.map +1 -0
  161. package/dist/set.js +1 -1
  162. package/dist/set.js.map +1 -1
  163. package/dist/time.js +1 -1
  164. package/dist/time.js.map +1 -1
  165. package/package.json +78 -54
  166. package/src/NoopTracer.ts +22 -8
  167. package/src/binary.ts +1 -1
  168. package/src/browser/Opfs/Opfs.ts +436 -0
  169. package/src/browser/Opfs/debug-utils.ts +153 -0
  170. package/src/browser/Opfs/mod.ts +3 -0
  171. package/src/browser/Opfs/utils.ts +287 -0
  172. package/src/browser/QuotaExceededError.ts +57 -0
  173. package/src/browser/WebChannelBrowser.ts +131 -0
  174. package/src/browser/WebError.test.ts +66 -0
  175. package/src/browser/WebError.ts +613 -0
  176. package/src/{effect → browser}/WebLock.ts +15 -15
  177. package/src/{browser.ts → browser/detect.ts} +6 -6
  178. package/src/browser/mod.ts +8 -0
  179. package/src/cuid/cuid.browser.ts +1 -1
  180. package/src/cuid/cuid.node.ts +1 -1
  181. package/src/effect/BucketQueue.ts +1 -1
  182. package/src/effect/Debug.ts +470 -0
  183. package/src/effect/Effect.ts +118 -36
  184. package/src/effect/Error.ts +1 -1
  185. package/src/effect/Logger.ts +2 -2
  186. package/src/effect/RpcClient.ts +14 -6
  187. package/src/effect/Schema/debug-diff.test.ts +2 -2
  188. package/src/effect/Schema/debug-diff.ts +6 -5
  189. package/src/effect/Schema/index.ts +10 -7
  190. package/src/effect/ServiceContext.ts +6 -6
  191. package/src/effect/Stream.test.ts +5 -4
  192. package/src/effect/SubscriptionRef.ts +5 -5
  193. package/src/effect/WebChannel/WebChannel.test.ts +1 -1
  194. package/src/effect/WebChannel/WebChannel.ts +19 -141
  195. package/src/effect/WebChannel/broadcastChannelWithAck.ts +4 -4
  196. package/src/effect/WebChannel/common.ts +5 -5
  197. package/src/effect/WebSocket.ts +13 -12
  198. package/src/effect/{index.ts → mod.ts} +10 -2
  199. package/src/effect/spanEvent.test.ts +95 -0
  200. package/src/effect/spanEvent.ts +15 -0
  201. package/src/env.ts +2 -1
  202. package/src/fast-deep-equal.ts +9 -9
  203. package/src/global.ts +4 -2
  204. package/src/guards.ts +15 -0
  205. package/src/misc.ts +12 -4
  206. package/src/mod.ts +209 -17
  207. package/src/node/ChildProcessRunner/ChildProcessRunner.ts +23 -10
  208. package/src/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.ts +30 -21
  209. package/src/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.ts +10 -11
  210. package/src/node/ChildProcessRunner/ChildProcessWorker.ts +5 -4
  211. package/src/node/mod.ts +41 -2
  212. package/src/object/index.ts +1 -1
  213. package/src/object/omit.ts +1 -1
  214. package/src/object/stringify-object.test.ts +1 -0
  215. package/src/object/stringify-object.ts +2 -2
  216. package/src/qr.ts +125 -0
  217. package/src/set.ts +1 -1
  218. package/src/time.ts +1 -1
  219. package/dist/.tsbuildinfo.json +0 -1
  220. package/dist/browser.d.ts.map +0 -1
  221. package/dist/browser.js.map +0 -1
  222. package/dist/effect/WebLock.d.ts.map +0 -1
  223. package/dist/effect/WebLock.js.map +0 -1
  224. package/dist/effect/index.d.ts.map +0 -1
  225. package/dist/effect/index.js.map +0 -1
  226. /package/dist/{effect → browser}/WebLock.d.ts +0 -0
@@ -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* () {
@@ -225,10 +104,7 @@ export const sameThreadChannel = <MsgListen, MsgSend, MsgListenEncoded, MsgSendE
225
104
 
226
105
  const schema = mapSchema(inputSchema)
227
106
 
228
- const send = (message: MsgSend) =>
229
- Effect.gen(function* () {
230
- yield* PubSub.publish(pubSub, message)
231
- })
107
+ const send = (message: MsgSend) => PubSub.publish(pubSub, message)
232
108
 
233
109
  const listen = Stream.fromPubSub(pubSub).pipe(Stream.map(Either.right), listenToDebugPing(channelName))
234
110
 
@@ -249,7 +125,7 @@ export const sameThreadChannel = <MsgListen, MsgSend, MsgListenEncoded, MsgSendE
249
125
  export const messagePortChannelWithAck: <MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded>(args: {
250
126
  port: MessagePort
251
127
  schema: InputSchema<MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded>
252
- debugId?: string | number
128
+ debugId?: string | number | undefined
253
129
  }) => Effect.Effect<WebChannel<MsgListen, MsgSend>, never, Scope.Scope> = ({ port, schema: inputSchema, debugId }) =>
254
130
  Effect.scopeWithCloseable((scope) =>
255
131
  Effect.gen(function* () {
@@ -315,7 +191,7 @@ export const messagePortChannelWithAck: <MsgListen, MsgSend, MsgListenEncoded, M
315
191
  yield* Deferred.succeed(requestAckMap.get(msg.right.reqId)!, void 0)
316
192
  } else if (msg.right._tag === 'ChannelRequest') {
317
193
  debugInfo.listenTotal++
318
- port.postMessage(Schema.encodeSync(ChannelMessage)({ _tag: 'ChannelRequestAck', reqId: msg.right.id }))
194
+ port.postMessage(yield* Schema.encode(ChannelMessage)({ _tag: 'ChannelRequestAck', reqId: msg.right.id }))
319
195
  }
320
196
  }
321
197
  }),
@@ -408,8 +284,8 @@ export const queueChannelProxy = <MsgListen, MsgSend>({
408
284
  /**
409
285
  * Eagerly starts listening to a channel by buffering incoming messages in a queue.
410
286
  */
411
- export const toOpenChannel = (
412
- channel: WebChannel<any, any>,
287
+ export const toOpenChannel = <MsgListen, MsgSend>(
288
+ channel: WebChannel<MsgListen, MsgSend>,
413
289
  options?: {
414
290
  /**
415
291
  * Sends a heartbeat message to the other end of the channel every `interval`.
@@ -420,9 +296,14 @@ export const toOpenChannel = (
420
296
  timeout: DurationInput
421
297
  }
422
298
  },
423
- ): Effect.Effect<WebChannel<any, any>, never, Scope.Scope> =>
299
+ ): Effect.Effect<WebChannel<MsgListen, MsgSend>, never, Scope.Scope> =>
424
300
  Effect.gen(function* () {
425
- const queue = yield* Queue.unbounded<Either.Either<any, any>>().pipe(Effect.acquireRelease(Queue.shutdown))
301
+ const queue = yield* Queue.unbounded<Either.Either<MsgListen, any>>().pipe(Effect.acquireRelease(Queue.shutdown))
302
+
303
+ const heartbeatChannel = channel as WebChannel<
304
+ MsgListen | typeof WebChannelHeartbeat.Type,
305
+ MsgSend | typeof WebChannelHeartbeat.Type
306
+ >
426
307
 
427
308
  const pendingPingDeferredRef = {
428
309
  current: undefined as { deferred: Deferred.Deferred<void>; requestId: string } | undefined,
@@ -430,12 +311,12 @@ export const toOpenChannel = (
430
311
 
431
312
  yield* channel.listen.pipe(
432
313
  // TODO implement this on the "chunk" level for better performance
433
- options?.heartbeat
314
+ options?.heartbeat !== undefined
434
315
  ? Stream.filterEffect(
435
316
  Effect.fn(function* (msg) {
436
- if (msg._tag === 'Right' && Schema.is(WebChannelHeartbeat)(msg.right)) {
317
+ if (msg._tag === 'Right' && Schema.is(WebChannelHeartbeat)(msg.right) === true) {
437
318
  if (msg.right._tag === 'WebChannel.Ping') {
438
- yield* channel.send(WebChannelPong.make({ requestId: msg.right.requestId }))
319
+ yield* heartbeatChannel.send(WebChannelPong.make({ requestId: msg.right.requestId }))
439
320
  } else {
440
321
  const { deferred, requestId } = pendingPingDeferredRef.current ?? shouldNeverHappen('No pending ping')
441
322
  if (requestId !== msg.right.requestId) {
@@ -455,13 +336,13 @@ export const toOpenChannel = (
455
336
  Effect.forkScoped,
456
337
  )
457
338
 
458
- if (options?.heartbeat) {
339
+ if (options?.heartbeat !== undefined) {
459
340
  const { interval, timeout } = options.heartbeat
460
341
  yield* Effect.gen(function* () {
461
342
  while (true) {
462
343
  yield* Effect.sleep(interval)
463
344
  const requestId = crypto.randomUUID()
464
- yield* channel.send(WebChannelPing.make({ requestId }))
345
+ yield* heartbeatChannel.send(WebChannelPing.make({ requestId }))
465
346
  const deferred = yield* Deferred.make<void>()
466
347
  pendingPingDeferredRef.current = { deferred, requestId }
467
348
  yield* deferred.pipe(
@@ -492,7 +373,4 @@ export const toOpenChannel = (
492
373
  }
493
374
  })
494
375
 
495
- export const sendDebugPing = (channel: WebChannel<any, any>) =>
496
- Effect.gen(function* () {
497
- yield* channel.send(DebugPingMessage.make({ message: 'ping' }))
498
- })
376
+ export const sendDebugPing = (channel: WebChannel<any, any>) => channel.send(DebugPingMessage.make({ message: 'ping' }))
@@ -69,7 +69,7 @@ export const broadcastChannelWithAck = <MsgListen, MsgSend, MsgListenEncoded, Ms
69
69
  peerIdRef.current = data.from
70
70
  postMessage(ConnectAckMessage.make({ from: connectionId, to: data.from }))
71
71
  yield* connectedLatch.open
72
- break
72
+ return undefined
73
73
  }
74
74
  // Case: other side sends connect-ack message (because otherside was already online when this side connected)
75
75
  case 'ConnectAckMessage': {
@@ -77,7 +77,7 @@ export const broadcastChannelWithAck = <MsgListen, MsgSend, MsgListenEncoded, Ms
77
77
  peerIdRef.current = data.from
78
78
  yield* connectedLatch.open
79
79
  }
80
- break
80
+ return undefined
81
81
  }
82
82
  case 'DisconnectMessage': {
83
83
  if (data.from === peerIdRef.current) {
@@ -85,13 +85,13 @@ export const broadcastChannelWithAck = <MsgListen, MsgSend, MsgListenEncoded, Ms
85
85
  yield* connectedLatch.close
86
86
  yield* establishConnection
87
87
  }
88
- break
88
+ return undefined
89
89
  }
90
90
  case 'PayloadMessage': {
91
91
  if (data.to === connectionId) {
92
92
  return Schema.decodeEither(schema.listen)(data.payload)
93
93
  }
94
- break
94
+ return undefined
95
95
  }
96
96
  }
97
97
  }),
@@ -15,7 +15,7 @@ export interface WebChannel<MsgListen, MsgSend, E = never> {
15
15
  closedDeferred: Deferred.Deferred<void>
16
16
  shutdown: Effect.Effect<void>
17
17
  schema: { listen: Schema.Schema<MsgListen, any>; send: Schema.Schema<MsgSend, any> }
18
- debugInfo?: Record<string, any>
18
+ debugInfo?: Record<string, any> | undefined
19
19
  }
20
20
 
21
21
  export const DebugPingMessage = Schema.TaggedStruct('WebChannel.DebugPing', {
@@ -54,19 +54,19 @@ export type OutputSchema<MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded> =
54
54
  export const mapSchema = <MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded>(
55
55
  schema: InputSchema<MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded>,
56
56
  ): OutputSchema<MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded> =>
57
- Predicate.hasProperty(schema, 'send') && Predicate.hasProperty(schema, 'listen')
57
+ Predicate.hasProperty(schema, 'send') === true && Predicate.hasProperty(schema, 'listen') === true
58
58
  ? (schemaWithWebChannelMessages(schema) as any)
59
59
  : (schemaWithWebChannelMessages({ send: schema, listen: schema }) as any)
60
60
 
61
61
  export const listenToDebugPing =
62
62
  (channelName: string) =>
63
63
  <MsgListen>(
64
- stream: Stream.Stream<Either.Either<MsgListen, ParseResult.ParseError>, never>,
65
- ): Stream.Stream<Either.Either<MsgListen, ParseResult.ParseError>, never> =>
64
+ stream: Stream.Stream<Either.Either<MsgListen, ParseResult.ParseError>>,
65
+ ): Stream.Stream<Either.Either<MsgListen, ParseResult.ParseError>> =>
66
66
  stream.pipe(
67
67
  Stream.filterEffect(
68
68
  Effect.fn(function* (msg) {
69
- if (msg._tag === 'Right' && Schema.is(DebugPingMessage)(msg.right)) {
69
+ if (msg._tag === 'Right' && Schema.is(DebugPingMessage)(msg.right) === true) {
70
70
  yield* Effect.logDebug(`WebChannel:ping [${channelName}] ${msg.right.message}`, msg.right.payload)
71
71
  return false
72
72
  }
@@ -2,7 +2,7 @@ import { HttpClient } from '@effect/platform'
2
2
  import type { Schedule, Scope } from 'effect'
3
3
  import { Effect, Exit, identity, Schema } from 'effect'
4
4
 
5
- export class WebSocketError extends Schema.TaggedError<WebSocketError>()('WebSocketError', {
5
+ export class WebSocketError extends Schema.TaggedError<WebSocketError>('~@livestore/utils/WebSocketError')('WebSocketError', {
6
6
  cause: Schema.Defect,
7
7
  }) {}
8
8
 
@@ -63,7 +63,7 @@ export const makeWebSocket = ({
63
63
  }
64
64
  }).pipe(
65
65
  Effect.tapErrorTag('WebSocketError', () => tryLogWebsocketConnectError(url)),
66
- reconnect ? Effect.retry(reconnect) : identity,
66
+ reconnect !== undefined ? Effect.retry(reconnect) : identity,
67
67
  )
68
68
 
69
69
  /**
@@ -81,16 +81,17 @@ export const makeWebSocket = ({
81
81
  */
82
82
  yield* Effect.addFinalizer(
83
83
  Effect.fn(function* (exit) {
84
- try {
85
- if (Exit.isFailure(exit)) {
86
- socket.close(3000)
87
- } else {
88
- socket.close(1000)
89
- }
90
- } catch (error) {
91
- return yield* Effect.die(new WebSocketError({ cause: error }))
92
- }
93
- }),
84
+ yield* Effect.try({
85
+ try: () => {
86
+ if (Exit.isFailure(exit) === true) {
87
+ socket.close(3000)
88
+ } else {
89
+ socket.close(1000)
90
+ }
91
+ },
92
+ catch: (error) => new WebSocketError({ cause: error }),
93
+ })
94
+ }, Effect.orDie),
94
95
  )
95
96
 
96
97
  return socket
@@ -48,7 +48,6 @@ export {
48
48
  WorkerError,
49
49
  WorkerRunner,
50
50
  } from '@effect/platform'
51
- export { BrowserWorker, BrowserWorkerRunner } from '@effect/platform-browser'
52
51
  export {
53
52
  Rpc,
54
53
  // RpcClient, // TODO bring back "original" RpcClient from effect/rpc
@@ -72,6 +71,7 @@ export {
72
71
  Chunk,
73
72
  Config,
74
73
  ConfigError,
74
+ ConfigProvider,
75
75
  Console,
76
76
  Context,
77
77
  Data,
@@ -108,6 +108,7 @@ export {
108
108
  MutableHashMap,
109
109
  MutableHashSet,
110
110
  Option,
111
+ Order,
111
112
  ParseResult,
112
113
  Predicate,
113
114
  Pretty,
@@ -116,15 +117,21 @@ export {
116
117
  // Subscribable,
117
118
  pipe,
118
119
  Queue,
120
+ RcMap,
121
+ RcRef,
119
122
  Record as ReadonlyRecord,
123
+ Redacted,
120
124
  Ref,
121
125
  Request,
122
126
  Runtime,
123
127
  RuntimeFlags,
124
128
  Scope,
129
+ ScopedRef,
130
+ Sink,
125
131
  SortedMap,
126
132
  STM,
127
133
  SynchronizedRef,
134
+ TestClock,
128
135
  TestServices,
129
136
  TQueue,
130
137
  TRef,
@@ -133,10 +140,12 @@ export {
133
140
  } from 'effect'
134
141
  export type { NonEmptyArray } from 'effect/Array'
135
142
  export { constVoid, dual } from 'effect/Function'
143
+ export * as Graph from 'effect/Graph'
136
144
  export { TreeFormatter } from 'effect/ParseResult'
137
145
  export type { Serializable, SerializableWithResult } from 'effect/Schema'
138
146
  export * as SchemaAST from 'effect/SchemaAST'
139
147
  export * as BucketQueue from './BucketQueue.ts'
148
+ export * as Debug from './Debug.ts'
140
149
  export * as Effect from './Effect.ts'
141
150
  export * from './Error.ts'
142
151
  export * as Logger from './Logger.ts'
@@ -151,5 +160,4 @@ export * as Subscribable from './Subscribable.ts'
151
160
  export * as SubscriptionRef from './SubscriptionRef.ts'
152
161
  export * as TaskTracing from './TaskTracing.ts'
153
162
  export * as WebChannel from './WebChannel/mod.ts'
154
- export * as WebLock from './WebLock.ts'
155
163
  export * as WebSocket from './WebSocket.ts'
@@ -0,0 +1,95 @@
1
+ import { it, describe, expect } from '@effect/vitest'
2
+ import { Effect, Tracer } from 'effect'
3
+
4
+ import { spanEvent } from './spanEvent.ts'
5
+
6
+ type RecordedEvent = { name: string; attributes: Record<string, unknown> | undefined }
7
+
8
+ /**
9
+ * Creates a test tracer that captures span events for assertion,
10
+ * using only the public `Tracer` API (no internal NativeSpan access).
11
+ * Each span records its own events, retrievable by span name.
12
+ */
13
+ const makeTestTracer = () => {
14
+ const spanEvents = new Map<string, Array<RecordedEvent>>()
15
+
16
+ const tracer = Tracer.make({
17
+ span(name, parent, context, links, startTime, kind) {
18
+ const events: Array<RecordedEvent> = []
19
+ spanEvents.set(name, events)
20
+ const attributes = new Map<string, unknown>()
21
+ return {
22
+ _tag: 'Span' as const,
23
+ name,
24
+ spanId: `test-${name}`,
25
+ traceId: 'test-trace',
26
+ parent,
27
+ context,
28
+ status: { _tag: 'Started' as const, startTime },
29
+ attributes,
30
+ links: [...links],
31
+ sampled: true,
32
+ kind,
33
+ end() {},
34
+ attribute(key: string, value: unknown) {
35
+ attributes.set(key, value)
36
+ },
37
+ event(eventName: string, _startTime: bigint, attrs?: Record<string, unknown>) {
38
+ events.push({ name: eventName, attributes: attrs })
39
+ },
40
+ addLinks() {},
41
+ }
42
+ },
43
+ context(f) {
44
+ return f()
45
+ },
46
+ })
47
+
48
+ return {
49
+ tracer,
50
+ getEvents: (spanName: string): Array<RecordedEvent> => spanEvents.get(spanName) ?? [],
51
+ }
52
+ }
53
+
54
+ describe('spanEvent', () => {
55
+ it.effect('should emit a span event with the given message', () => {
56
+ const { tracer, getEvents } = makeTestTracer()
57
+ return Effect.gen(function* () {
58
+ yield* spanEvent('test-event')
59
+ const events = getEvents('test-span')
60
+ expect(events).toHaveLength(1)
61
+ expect(events[0]!.name).toBe('test-event')
62
+ }).pipe(Effect.withSpan('test-span'), Effect.withTracer(tracer))
63
+ })
64
+
65
+ it.effect('should emit a span event with attributes', () => {
66
+ const { tracer, getEvents } = makeTestTracer()
67
+ return Effect.gen(function* () {
68
+ yield* spanEvent('event-with-attrs', { key1: 'value1', key2: 42 })
69
+ const events = getEvents('test-span')
70
+ expect(events).toHaveLength(1)
71
+ expect(events[0]!.name).toBe('event-with-attrs')
72
+ expect(events[0]!.attributes).toMatchObject({ key1: 'value1', key2: 42 })
73
+ }).pipe(Effect.withSpan('test-span'), Effect.withTracer(tracer))
74
+ })
75
+
76
+ it.effect('should emit to the nearest enclosing span', () => {
77
+ const { tracer, getEvents } = makeTestTracer()
78
+ return Effect.gen(function* () {
79
+ yield* spanEvent('outer-event')
80
+
81
+ yield* spanEvent('inner-event').pipe(Effect.withSpan('inner-span'))
82
+
83
+ const outerEvents = getEvents('outer-span')
84
+ const innerEvents = getEvents('inner-span')
85
+
86
+ expect(outerEvents).toHaveLength(1)
87
+ expect(outerEvents[0]!.name).toBe('outer-event')
88
+
89
+ expect(innerEvents).toHaveLength(1)
90
+ expect(innerEvents[0]!.name).toBe('inner-event')
91
+ }).pipe(Effect.withSpan('outer-span'), Effect.withTracer(tracer))
92
+ })
93
+
94
+ it.effect('should be a no-op when no span is in context', () => spanEvent('orphan-event'))
95
+ })
@@ -0,0 +1,15 @@
1
+ import { Effect, FiberRef, HashSet, Logger } from 'effect'
2
+
3
+ /**
4
+ * Emits a span event on the current Effect span via the tracer logger.
5
+ *
6
+ * @remarks
7
+ *
8
+ * Unlike raw `otelSpan.addEvent`, this doesn't require manual span threading —
9
+ * it automatically targets the nearest enclosing `Effect.withSpan`. If no span
10
+ * is in context, the call is a no-op.
11
+ */
12
+ export const spanEvent = (message: any, attributes?: Record<string, unknown>) =>
13
+ Effect.locallyWith(Effect.log(message).pipe(Effect.annotateLogs(attributes ?? {})), FiberRef.currentLoggers, () =>
14
+ HashSet.make(Logger.tracerLogger),
15
+ )
package/src/env.ts CHANGED
@@ -26,4 +26,5 @@ export const IS_CI = envTruish(env('CI'))
26
26
 
27
27
  export const IS_BUN = typeof Bun !== 'undefined'
28
28
 
29
- export const IS_REACT_NATIVE = typeof navigator !== 'undefined' && navigator.product === 'ReactNative'
29
+ export const IS_REACT_NATIVE =
30
+ typeof navigator !== 'undefined' && (navigator as unknown as Record<string, unknown>)['product'] === 'ReactNative'
@@ -4,36 +4,36 @@
4
4
  export const deepEqual = <T>(a: T, b: T): boolean => {
5
5
  if (a === b) return true
6
6
 
7
- if (a && b && typeof a === 'object' && typeof b === 'object') {
7
+ if (a != null && b != null && typeof a === 'object' && typeof b === 'object') {
8
8
  if (a.constructor !== b.constructor) return false
9
9
 
10
10
  let length: number
11
11
  let i: any
12
12
  let keys: any
13
- if (Array.isArray(a)) {
13
+ if (Array.isArray(a) === true) {
14
14
  length = a.length
15
15
  // @ts-expect-error ...
16
16
  if (length !== b.length) return false
17
17
  for (i = length; i-- !== 0; )
18
18
  // @ts-expect-error ...
19
- if (!deepEqual(a[i], b[i])) return false
19
+ if (deepEqual(a[i], b[i]) === false) return false
20
20
  return true
21
21
  }
22
22
 
23
23
  if (a instanceof Map && b instanceof Map) {
24
24
  if (a.size !== b.size) return false
25
- for (i of a.entries()) if (!b.has(i[0])) return false
26
- for (i of a.entries()) if (!deepEqual(i[1], b.get(i[0]))) return false
25
+ for (i of a.entries()) if (b.has(i[0]) === false) return false
26
+ for (i of a.entries()) if (deepEqual(i[1], b.get(i[0])) === false) return false
27
27
  return true
28
28
  }
29
29
 
30
30
  if (a instanceof Set && b instanceof Set) {
31
31
  if (a.size !== b.size) return false
32
- for (i of a.entries()) if (!b.has(i[0])) return false
32
+ for (i of a.entries()) if (b.has(i[0]) === false) return false
33
33
  return true
34
34
  }
35
35
 
36
- if (ArrayBuffer.isView(a) && ArrayBuffer.isView(b)) {
36
+ if (ArrayBuffer.isView(a) === true && ArrayBuffer.isView(b) === true) {
37
37
  // @ts-expect-error ...
38
38
  length = a.length
39
39
  // @ts-expect-error ...
@@ -53,13 +53,13 @@ export const deepEqual = <T>(a: T, b: T): boolean => {
53
53
  length = keys.length
54
54
  if (length !== Object.keys(b).length) return false
55
55
 
56
- for (i = length; i-- !== 0; ) if (!Object.hasOwn(b, keys[i])) return false
56
+ for (i = length; i-- !== 0; ) if (Object.prototype.hasOwnProperty.call(b, keys[i]) === false) return false
57
57
 
58
58
  for (i = length; i-- !== 0; ) {
59
59
  const key = keys[i]
60
60
 
61
61
  // @ts-expect-error ...
62
- if (!deepEqual(a[key], b[key])) return false
62
+ if (deepEqual(a[key], b[key]) === false) return false
63
63
  }
64
64
 
65
65
  return true
package/src/global.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  declare global {
2
2
  export type TODO<_Reason extends string = 'unknown'> = any
3
3
  export type UNUSED<_Reason extends string = 'unknown'> = any
4
- }
5
4
 
6
- export {}
5
+ interface ImportMeta {
6
+ main: boolean
7
+ }
8
+ }
package/src/guards.ts CHANGED
@@ -1,8 +1,23 @@
1
+ /** Type guard that narrows `T | undefined` to `T`. Useful for filtering arrays. */
1
2
  export const isNotUndefined = <T>(_: T | undefined): _ is T => _ !== undefined
2
3
 
4
+ /** Type guard that narrows `T | null` to `T`. */
3
5
  export const isNotNull = <T>(_: T | null): _ is T => _ !== null
6
+
7
+ /** Type guard that checks if a value is `undefined`. */
4
8
  export const isUndefined = <T>(_: T | undefined): _ is undefined => _ === undefined
5
9
 
10
+ /** Type guard that checks if a value is `null` or `undefined`. */
6
11
  export const isNil = (val: any): val is null | undefined => val === null || val === undefined
7
12
 
13
+ /**
14
+ * Type guard that narrows `T | undefined | null` to `T`.
15
+ * Commonly used to filter out nullish values from arrays.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * const values = [1, null, 2, undefined, 3]
20
+ * const nonNil = values.filter(isNotNil) // [1, 2, 3] (type: number[])
21
+ * ```
22
+ */
8
23
  export const isNotNil = <T>(val: T | undefined | null): val is T => val !== null && val !== undefined
package/src/misc.ts CHANGED
@@ -10,7 +10,7 @@ export const isDevEnv = () => {
10
10
  }
11
11
 
12
12
  // @ts-expect-error Only exists in Expo / RN
13
- if (globalThis?.__DEV__) {
13
+ if (globalThis?.__DEV__ === true) {
14
14
  return true
15
15
  }
16
16
 
@@ -46,10 +46,18 @@ export const tryAsFunctionAndNew = <TArg, TResult>(
46
46
  export const envTruish = (env: string | undefined) =>
47
47
  env !== undefined && env.toLowerCase() !== 'false' && env.toLowerCase() !== '0'
48
48
 
49
- export const shouldNeverHappen = (msg?: string, ...args: any[]): never => {
49
+ /**
50
+ * Logs and throws for impossible states, pausing at a breakpoint in development.
51
+ *
52
+ * @param msg - The error message to log and pass to the error.
53
+ * @param args - Arbitrary arguments to include in the log.
54
+ *
55
+ * @see {@link dieDebugger} for the Effect equivalent.
56
+ */
57
+ export const shouldNeverHappen = (msg?: string, ...args: ReadonlyArray<unknown>): never => {
50
58
  console.error(msg, ...args)
51
- if (isDevEnv()) {
52
- // biome-ignore lint/suspicious/noDebugger: debugging
59
+ if (isDevEnv() === true) {
60
+ // oxlint-disable-next-line eslint(no-debugger) -- intentional breakpoint during development
53
61
  debugger
54
62
  }
55
63