@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
@@ -6,9 +6,6 @@ import {
6
6
  Duration,
7
7
  Effect,
8
8
  Fiber,
9
- FiberRef,
10
- HashSet,
11
- Logger,
12
9
  pipe,
13
10
  Scope,
14
11
  type Stream,
@@ -18,10 +15,11 @@ import { log } from 'effect/Console'
18
15
  import { dual, type LazyArg } from 'effect/Function'
19
16
  import type { Predicate, Refinement } from 'effect/Predicate'
20
17
 
21
- import { isPromise } from '../mod.ts'
18
+ import { isDevEnv, isPromise, objectToString } from '../mod.ts'
22
19
  import { UnknownError } from './Error.ts'
23
20
 
24
21
  export * from 'effect/Effect'
22
+ export { spanEvent } from './spanEvent.ts'
25
23
 
26
24
  // export const log = <A>(message: A, ...rest: any[]): Effect.Effect<void> =>
27
25
  // Effect.sync(() => {
@@ -57,16 +55,16 @@ export type SyncOrPromiseOrEffect<TResult, TError = never, TContext = never> =
57
55
 
58
56
  export const tryAll = <Res>(
59
57
  fn: () => Res,
60
- ): Res extends Effect.Effect<infer A, infer E, never>
61
- ? Effect.Effect<A, E | UnknownException, never>
58
+ ): Res extends Effect.Effect<infer A, infer E>
59
+ ? Effect.Effect<A, E | UnknownException>
62
60
  : Res extends Promise<infer A>
63
- ? Effect.Effect<A, UnknownException, never>
64
- : Effect.Effect<Res, UnknownException, never> =>
61
+ ? Effect.Effect<A, UnknownException>
62
+ : Effect.Effect<Res, UnknownException> =>
65
63
  Effect.try(() => fn()).pipe(
66
64
  Effect.andThen((fnRes) =>
67
- Effect.isEffect(fnRes)
65
+ Effect.isEffect(fnRes) === true
68
66
  ? (fnRes as any as Effect.Effect<any>)
69
- : isPromise(fnRes)
67
+ : isPromise(fnRes) === true
70
68
  ? Effect.promise(() => fnRes)
71
69
  : Effect.succeed(fnRes),
72
70
  ),
@@ -89,7 +87,7 @@ export const logBefore =
89
87
  export const tapCauseLogPretty = <R, E, A>(eff: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
90
88
  Effect.tapErrorCause(eff, (cause) =>
91
89
  Effect.gen(function* () {
92
- if (Cause.isInterruptedOnly(cause)) {
90
+ if (Cause.isInterruptedOnly(cause) === true) {
93
91
  // console.log('interrupted', Cause.pretty(err), err)
94
92
  return
95
93
  }
@@ -107,6 +105,47 @@ export const tapCauseLogPretty = <R, E, A>(eff: Effect.Effect<A, E, R>): Effect.
107
105
  }),
108
106
  )
109
107
 
108
+ /**
109
+ * Creates a defect, pausing at a breakpoint in development.
110
+ *
111
+ * @param msg - The error message to include in the defect.
112
+ * @param args - Arbitrary arguments available for inspection during debugging.
113
+ *
114
+ * @see {@link shouldNeverHappen} for the non-Effect equivalent that throws synchronously.
115
+ * @see {@link orDieDebugger}
116
+ */
117
+ export const dieDebugger = (msg: string, ...args: ReadonlyArray<unknown>): Effect.Effect<never> =>
118
+ Effect.suspend(() => {
119
+ if (isDevEnv() === true) {
120
+ // oxlint-disable-next-line eslint(no-debugger) -- intentional breakpoint during development
121
+ debugger
122
+ void args // Keeps the variable in scope so it's inspectable when the debugger pauses
123
+ }
124
+ return Effect.dieMessage(msg)
125
+ })
126
+
127
+ /**
128
+ * Converts a failure into a defect, pausing at a breakpoint in development.
129
+ *
130
+ * @param self - The effect on which to apply the operation.
131
+ *
132
+ * @see {@link Effect.orDie}
133
+ * @see {@link dieDebugger}
134
+ */
135
+ export const orDieDebugger = <A, E, R>(self: Effect.Effect<A, E, R>): Effect.Effect<A, never, R> =>
136
+ Effect.matchEffect(self, {
137
+ onFailure: (error) =>
138
+ // Keep the debugger hook so that `debugger` runs only when the wrapped effect actually fails, not while building the wrapper.
139
+ Effect.dieSync(() => {
140
+ if (isDevEnv() === true) {
141
+ // oxlint-disable-next-line eslint(no-debugger) -- intentional breakpoint for impossible states during development
142
+ debugger
143
+ }
144
+ return error
145
+ }),
146
+ onSuccess: Effect.succeed,
147
+ })
148
+
110
149
  export const ignoreIf: {
111
150
  <E, EB extends E>(
112
151
  refinement: Refinement<NoInfer<E>, EB>,
@@ -124,11 +163,11 @@ export const ignoreIf: {
124
163
  export const eventListener = <TEvent = unknown>(
125
164
  target: Stream.EventListener<TEvent>,
126
165
  type: string,
127
- handler: (event: TEvent) => Effect.Effect<void, never, never>,
166
+ handler: (event: TEvent) => Effect.Effect<void>,
128
167
  options?: { once?: boolean },
129
168
  ) =>
130
169
  Effect.gen(function* () {
131
- const runtime = yield* Effect.runtime<never>()
170
+ const runtime = yield* Effect.runtime()
132
171
 
133
172
  const handlerFn = (event: TEvent) => handler(event).pipe(Effect.provide(runtime), Effect.runFork)
134
173
 
@@ -137,16 +176,11 @@ export const eventListener = <TEvent = unknown>(
137
176
  yield* Effect.addFinalizer(() => Effect.sync(() => target.removeEventListener(type, handlerFn)))
138
177
  })
139
178
 
140
- export const spanEvent = (message: any, attributes?: Record<string, any>) =>
141
- Effect.locallyWith(Effect.log(message).pipe(Effect.annotateLogs(attributes ?? {})), FiberRef.currentLoggers, () =>
142
- HashSet.make(Logger.tracerLogger),
143
- )
144
-
145
179
  export const logWarnIfTakesLongerThan =
146
180
  ({ label, duration }: { label: string; duration: Duration.DurationInput }) =>
147
181
  <R, E, A>(eff: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
148
182
  Effect.gen(function* () {
149
- const runtime = yield* Effect.runtime<never>()
183
+ const runtime = yield* Effect.runtime()
150
184
 
151
185
  let tookLongerThanTimer = false
152
186
 
@@ -154,7 +188,7 @@ export const logWarnIfTakesLongerThan =
154
188
  Effect.tap(() => {
155
189
  tookLongerThanTimer = true
156
190
  // TODO include span info
157
- return Effect.logWarning(`${label}: Took longer than ${duration}ms`)
191
+ return Effect.logWarning(`${label}: Took longer than ${objectToString(duration)}ms`)
158
192
  }),
159
193
  Effect.provide(runtime),
160
194
  Effect.runFork,
@@ -169,13 +203,14 @@ export const logWarnIfTakesLongerThan =
169
203
 
170
204
  yield* Fiber.interrupt(timeoutFiber)
171
205
 
172
- if (tookLongerThanTimer) {
206
+ if (tookLongerThanTimer === true) {
173
207
  yield* Effect.logWarning(`${label}: Interrupted after ${end - start}ms`)
174
208
  }
175
209
  }),
176
210
  ),
177
211
  )
178
212
 
213
+ // eslint-disable-next-line overeng/explicit-boolean-compare -- mutated in forked fiber; TS can't see the mutation
179
214
  if (tookLongerThanTimer) {
180
215
  const end = Date.now()
181
216
  yield* Effect.logWarning(`${label}: Actual duration: ${end - start}ms`)
@@ -208,19 +243,61 @@ export const debugLogEnv = (msg?: string): Effect.Effect<Context.Context<never>>
208
243
  Effect.tap((env) => log(msg ?? 'debugLogEnv', env)),
209
244
  )
210
245
 
211
- export const timeoutDie =
212
- <E1>(options: { onTimeout: LazyArg<E1>; duration: Duration.DurationInput }) =>
213
- <R, E, A>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
214
- Effect.orDie(Effect.timeoutFail(options)(self))
215
-
216
- export const timeoutDieMsg =
217
- (options: { error: string; duration: Duration.DurationInput }) =>
218
- <R, E, A>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
219
- Effect.orDie(
220
- Effect.timeoutFail({ onTimeout: () => new UnknownError({ cause: options.error }), duration: options.duration })(
221
- self,
222
- ),
223
- )
246
+ /**
247
+ * Enforces a time limit on an effect, triggering a defect on timeout.
248
+ *
249
+ * @remarks
250
+ *
251
+ * This function allows you to enforce a time limit on the execution of an
252
+ * effect. If the effect does not complete within the given duration, it dies
253
+ * with a {@link Cause.TimeoutException} as an unchecked defect. Unlike
254
+ * {@link Effect.timeout}, which adds `TimeoutException` to the error channel,
255
+ * this function keeps the error channel unchanged by treating the timeout as
256
+ * a defect.
257
+ *
258
+ * The returned effect will either:
259
+ * - Succeed with the original effect's result if it completes within the
260
+ * specified duration.
261
+ * - Die with a {@link Cause.TimeoutException} defect if the time limit is exceeded.
262
+ *
263
+ * @see {@link timeoutOrDieMessage} for a version with a custom message.
264
+ * @see {@link Effect.timeout} for a version that raises a `TimeoutException` as a typed error.
265
+ * @see {@link Effect.timeoutFailCause} for a version that raises a custom defect.
266
+ */
267
+ export const timeoutOrDie = (duration: Duration.DurationInput) =>
268
+ <A, E, R>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
269
+ Effect.timeoutFailCause(self, {
270
+ duration,
271
+ onTimeout: () => Cause.die(new Cause.TimeoutException())
272
+ })
273
+
274
+ /**
275
+ * Enforces a time limit on an effect, triggering a defect with a custom
276
+ * message on timeout.
277
+ *
278
+ * @remarks
279
+ *
280
+ * This function behaves like {@link timeoutOrDie}, but allows you to provide
281
+ * a custom message for the {@link Cause.TimeoutException} defect. This is useful
282
+ * for adding context about which operation timed out, making it easier to
283
+ * diagnose issues in logs or error reports.
284
+ *
285
+ * The returned effect will either:
286
+ * - Succeed with the original effect's result if it completes within the
287
+ * specified duration.
288
+ * - Die with a {@link Cause.TimeoutException} defect containing the provided
289
+ * message if the time limit is exceeded.
290
+ *
291
+ * @see {@link timeoutOrDie} for a version without a custom message.
292
+ * @see {@link Effect.timeout} for a version that raises a `TimeoutException` as a typed error.
293
+ * @see {@link Effect.timeoutFailCause} for a version that raises a custom defect.
294
+ */
295
+ export const timeoutOrDieMessage = (duration: Duration.DurationInput, message: string) =>
296
+ <A, E, R>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
297
+ Effect.timeoutFailCause(self, {
298
+ duration,
299
+ onTimeout: () => Cause.die(new Cause.TimeoutException(message))
300
+ })
224
301
 
225
302
  export const toForkedDeferred = <R, E, A>(
226
303
  eff: Effect.Effect<A, E, R>,
@@ -274,7 +351,12 @@ const getSpanTrace = () => {
274
351
 
275
352
  const logSpanTrace = () => console.log(getSpanTrace())
276
353
 
277
- // @ts-expect-error TODO fix types
354
+ declare global {
355
+ /** Debug helper: returns the current Effect span trace */
356
+ var getSpanTrace: () => string
357
+ /** Debug helper: logs the current Effect span trace */
358
+ var logSpanTrace: () => void
359
+ }
360
+
278
361
  globalThis.getSpanTrace = getSpanTrace
279
- // @ts-expect-error TODO fix types
280
362
  globalThis.logSpanTrace = logSpanTrace
@@ -1,6 +1,6 @@
1
1
  import { Schema } from 'effect'
2
2
 
3
- export class UnknownError extends Schema.TaggedError<UnknownError>()('UnknownError', {
3
+ export class UnknownError extends Schema.TaggedError<UnknownError>('~@livestore/utils/UnknownError')('UnknownError', {
4
4
  cause: Schema.Any,
5
5
  payload: Schema.optional(Schema.Any),
6
6
  }) {}
@@ -23,7 +23,7 @@ export const consoleLogger = (threadName: string) =>
23
23
  const consoleFn =
24
24
  logLevel === LogLevel.Debug
25
25
  ? // Cloudflare Workers doesn't support console.debug 🤷
26
- isCloudflareWorker
26
+ isCloudflareWorker === true
27
27
  ? console.log
28
28
  : console.debug
29
29
  : logLevel === LogLevel.Info
@@ -34,7 +34,7 @@ export const consoleLogger = (threadName: string) =>
34
34
 
35
35
  const annotationsObj = Object.fromEntries(HashMap.entries(annotations))
36
36
 
37
- const messages = Array.isArray(message) ? message : [message]
37
+ const messages = Array.isArray(message) === true ? message : [message]
38
38
  if (Cause.isEmpty(cause) === false) {
39
39
  messages.push(Cause.pretty(cause, { renderErrorCause: true }))
40
40
  }
@@ -6,6 +6,7 @@ import { Protocol } from '@effect/rpc/RpcClient'
6
6
  import { constPing, type FromServerEncoded } from '@effect/rpc/RpcMessage'
7
7
  import { Cause, Deferred, Effect, Layer, Option, Schedule, type Scope } from 'effect'
8
8
  import { constVoid, identity } from 'effect/Function'
9
+
9
10
  import * as SubscriptionRef from './SubscriptionRef.ts'
10
11
 
11
12
  // This is based on `makeProtocolSocket` / `layerProtocolSocket` from `@effect/rpc` in order to:
@@ -40,6 +41,10 @@ export const makeProtocolSocketWithIsConnected = (options: {
40
41
  const pinger = yield* makePinger(write(parser.encode(constPing)!), options?.pingSchedule)
41
42
 
42
43
  yield* Effect.suspend(() => {
44
+ // We rely on the heartbeat watchdog while streaming arbitrarily long payloads.
45
+ // Reset the timer as soon as _any_ frame arrives so that large batches which
46
+ // don't contain explicit `Pong` messages don't trigger the open-timeout defect.
47
+ // (The actual pong handler still calls `onPong()` to resolve manual pings.)
43
48
  // CHANGED: don't reset parser on every message
44
49
  // parser = serialization.unsafeMake()
45
50
  pinger.reset()
@@ -53,6 +58,9 @@ export const makeProtocolSocketWithIsConnected = (options: {
53
58
  while: () => i < responses.length,
54
59
  body: () => {
55
60
  const response = responses[i++]!
61
+ // Keep extending the watchdog for each data frame to avoid
62
+ // disconnecting mid-stream when the server is busy sending batches.
63
+ pinger.reset()
56
64
  if (response._tag === 'Pong') {
57
65
  pinger.onPong()
58
66
  }
@@ -112,8 +120,8 @@ export const makeProtocolSocketWithIsConnected = (options: {
112
120
 
113
121
  const error = Cause.failureOption(cause)
114
122
  if (
115
- options?.retryTransientErrors &&
116
- Option.isSome(error) &&
123
+ options?.retryTransientErrors !== undefined &&
124
+ Option.isSome(error) === true &&
117
125
  (error.value.reason === 'Open' || error.value.reason === 'OpenTimeout')
118
126
  ) {
119
127
  return
@@ -130,7 +138,7 @@ export const makeProtocolSocketWithIsConnected = (options: {
130
138
  }),
131
139
  ),
132
140
  // CHANGED: make configurable via schedule
133
- options?.retryTransientErrors ? Effect.retry(options.retryTransientErrors) : identity,
141
+ options?.retryTransientErrors !== undefined ? Effect.retry(options.retryTransientErrors) : identity,
134
142
  Effect.annotateLogs({
135
143
  module: 'RpcClient',
136
144
  method: 'makeProtocolSocket',
@@ -164,7 +172,7 @@ const makePinger = Effect.fnUntraced(function* <A, E, R>(
164
172
  pingSchedule: Schedule.Schedule<unknown> = Schedule.spaced(10000).pipe(Schedule.addDelay(() => 5000)),
165
173
  ) {
166
174
  // CHANGED: add manual ping deferreds
167
- const manualPingDeferreds = new Set<Deferred.Deferred<void, never>>()
175
+ const manualPingDeferreds = new Set<Deferred.Deferred<void>>()
168
176
 
169
177
  let recievedPong = true
170
178
  const latch = Effect.unsafeMakeLatch()
@@ -181,7 +189,7 @@ const makePinger = Effect.fnUntraced(function* <A, E, R>(
181
189
  }
182
190
  yield* Effect.suspend(() => {
183
191
  // Starting new ping
184
- if (!recievedPong) return latch.open
192
+ if (recievedPong === false) return latch.open
185
193
  recievedPong = false
186
194
  return writePing
187
195
  }).pipe(
@@ -195,7 +203,7 @@ const makePinger = Effect.fnUntraced(function* <A, E, R>(
195
203
 
196
204
  // CHANGED: add manual ping
197
205
  const ping = Effect.gen(function* () {
198
- const deferred = yield* Deferred.make<void, never>()
206
+ const deferred = yield* Deferred.make<void>()
199
207
  manualPingDeferreds.add(deferred)
200
208
  yield* deferred
201
209
  manualPingDeferreds.delete(deferred)
@@ -75,8 +75,8 @@ describe('debug-diff', () => {
75
75
 
76
76
  test('tagged union', () => {
77
77
  const schema = Schema.Union(
78
- Schema.Struct({ _tag: Schema.Literal('a'), a: Schema.String }),
79
- Schema.Struct({ _tag: Schema.Literal('b'), b: Schema.Number }),
78
+ Schema.TaggedStruct('a', { a: Schema.String }),
79
+ Schema.TaggedStruct('b', { b: Schema.Number }),
80
80
  )
81
81
  const a = { _tag: 'a', a: 'hello' } as const
82
82
  const b = { _tag: 'b', b: 1 } as const
@@ -23,8 +23,8 @@ const debugDiffImpl = (ast: SchemaAST.AST, a: any, b: any, path: string, bag: Di
23
23
  if (eq(a, b) === false) {
24
24
  // bag.push({ path, a, b, ast })
25
25
 
26
- if (SchemaAST.isUnion(ast)) {
27
- if (isTaggedUnion(ast)) {
26
+ if (SchemaAST.isUnion(ast) === true) {
27
+ if (isTaggedUnion(ast) === true) {
28
28
  bag.push({ path, a, b, ast })
29
29
  return
30
30
  } else {
@@ -35,7 +35,7 @@ const debugDiffImpl = (ast: SchemaAST.AST, a: any, b: any, path: string, bag: Di
35
35
  } catch {}
36
36
  }
37
37
  }
38
- } else if (SchemaAST.isTypeLiteral(ast)) {
38
+ } else if (SchemaAST.isTypeLiteral(ast) === true) {
39
39
  const props = SchemaAST.getPropertySignatures(ast)
40
40
  for (const prop of props) {
41
41
  debugDiffImpl(prop.type, a[prop.name], b[prop.name], `${path}.${prop.name.toString()}`, bag)
@@ -47,12 +47,13 @@ const debugDiffImpl = (ast: SchemaAST.AST, a: any, b: any, path: string, bag: Di
47
47
  }
48
48
  }
49
49
 
50
- const isTaggedUnion = (ast: SchemaAST.AST) => {
51
- if (SchemaAST.isUnion(ast)) {
50
+ const isTaggedUnion = (ast: SchemaAST.AST): boolean => {
51
+ if (SchemaAST.isUnion(ast) === true) {
52
52
  return ast.types.every((type) => {
53
53
  if (SchemaAST.isTypeLiteral(type) === false) return false
54
54
  const props = SchemaAST.getPropertySignatures(type)
55
55
  return props.some((prop) => prop.name.toString() === '_tag')
56
56
  })
57
57
  }
58
+ return false
58
59
  }
@@ -24,7 +24,7 @@ export const hash = (schema: Schema.Schema<any>) => {
24
24
  }
25
25
 
26
26
  const resolveStructAst = (ast: SchemaAST.AST): SchemaAST.AST => {
27
- if (SchemaAST.isTransformation(ast)) {
27
+ if (SchemaAST.isTransformation(ast) === true) {
28
28
  return resolveStructAst(ast.from)
29
29
  }
30
30
 
@@ -38,9 +38,12 @@ export const getResolvedPropertySignatures = (
38
38
  return SchemaAST.getPropertySignatures(resolvedAst)
39
39
  }
40
40
 
41
+ /** Objects that can be transferred between contexts (workers, etc.) */
42
+ type TransferableObject = ArrayBuffer | MessagePort
43
+
41
44
  export const encodeWithTransferables =
42
- <A, I, R>(schema: Schema.Schema<A, I, R>, options?: ParseOptions | undefined) =>
43
- (a: A, overrideOptions?: ParseOptions | undefined): Effect.Effect<[I, Transferable[]], ParseError, R> =>
45
+ <A, I, R>(schema: Schema.Schema<A, I, R>, options?: ParseOptions) =>
46
+ (a: A, overrideOptions?: ParseOptions): Effect.Effect<[I, TransferableObject[]], ParseError, R> =>
44
47
  Effect.gen(function* () {
45
48
  const collector = yield* Transferable.makeCollector
46
49
 
@@ -48,11 +51,11 @@ export const encodeWithTransferables =
48
51
  Effect.provideService(Transferable.Collector, collector),
49
52
  )
50
53
 
51
- return [encoded, collector.unsafeRead() as Transferable[]]
54
+ return [encoded, collector.unsafeRead() as TransferableObject[]]
52
55
  })
53
56
 
54
57
  export const decodeSyncDebug: <A, I>(
55
- schema: Schema.Schema<A, I, never>,
58
+ schema: Schema.Schema<A, I>,
56
59
  options?: SchemaAST.ParseOptions,
57
60
  ) => (i: I, overrideOptions?: SchemaAST.ParseOptions) => A = (schema, options) => (input, overrideOptions) => {
58
61
  const res = Schema.decodeEither(schema, options)(input, overrideOptions)
@@ -64,7 +67,7 @@ export const decodeSyncDebug: <A, I>(
64
67
  }
65
68
 
66
69
  export const encodeSyncDebug: <A, I>(
67
- schema: Schema.Schema<A, I, never>,
70
+ schema: Schema.Schema<A, I>,
68
71
  options?: SchemaAST.ParseOptions,
69
72
  ) => (a: A, overrideOptions?: SchemaAST.ParseOptions) => I = (schema, options) => (input, overrideOptions) => {
70
73
  const res = Schema.encodeEither(schema, options)(input, overrideOptions)
@@ -96,4 +99,4 @@ export const JsonValue: Schema.Schema<JsonValue> = Schema.Union(
96
99
  Schema.Null,
97
100
  Schema.Array(Schema.suspend(() => JsonValue)),
98
101
  Schema.Record({ key: Schema.String, value: Schema.suspend(() => JsonValue) }),
99
- ).annotations({ title: 'JsonValue' })
102
+ ).annotations({ identifier: 'JsonValue' })
@@ -24,13 +24,13 @@ export const make = <TStaticData, Ctx>(
24
24
  close: Effect.Effect<void> = Effect.dieMessage('close not implemented'),
25
25
  ): ServiceContext<Ctx, TStaticData> => {
26
26
  return {
27
- provide: (self) => Effect.provide(runtime)(self),
28
- runWithErrorLog: <E, A>(self: Effect.Effect<A, E, Ctx>) => runWithErrorLog(Effect.provide(runtime)(self)),
29
- runSync: <E, A>(self: Effect.Effect<A, E, Ctx>) => Effect.runSync(Effect.provide(runtime)(self)),
27
+ provide: (self) => self.pipe(Effect.provide(runtime)),
28
+ runWithErrorLog: <E, A>(self: Effect.Effect<A, E, Ctx>) => runWithErrorLog(self.pipe(Effect.provide(runtime))),
29
+ runSync: <E, A>(self: Effect.Effect<A, E, Ctx>) => Effect.runSync(self.pipe(Effect.provide(runtime))),
30
30
  runPromiseWithErrorLog: <E, A>(self: Effect.Effect<A, E, Ctx>) =>
31
- runPromiseWithErrorLog(Effect.provide(runtime)(self)),
32
- runPromiseExit: <E, A>(self: Effect.Effect<A, E, Ctx>) => Effect.runPromiseExit(Effect.provide(runtime)(self)),
33
- runPromise: <E, A>(self: Effect.Effect<A, E, Ctx>) => Effect.runPromise(Effect.provide(runtime)(self)),
31
+ runPromiseWithErrorLog(self.pipe(Effect.provide(runtime))),
32
+ runPromiseExit: <E, A>(self: Effect.Effect<A, E, Ctx>) => Effect.runPromiseExit(self.pipe(Effect.provide(runtime))),
33
+ runPromise: <E, A>(self: Effect.Effect<A, E, Ctx>) => Effect.runPromise(self.pipe(Effect.provide(runtime))),
34
34
  withRuntime: (fn) => fn(runtime),
35
35
  close: close,
36
36
  closePromise: () => Effect.runPromise(close),
@@ -1,5 +1,6 @@
1
1
  import { Effect, Option, Stream } from 'effect'
2
2
  import { describe, expect, it } from 'vitest'
3
+
3
4
  import { concatWithLastElement, runCollectReadonlyArray } from './Stream.ts'
4
5
 
5
6
  describe('concatWithLastElement', () => {
@@ -9,7 +10,7 @@ describe('concatWithLastElement', () => {
9
10
  lastElement.pipe(
10
11
  Option.match({
11
12
  onNone: () => Stream.make('no-previous'),
12
- onSome: (last) => Stream.make(`last-was-${last}`, 'continuing'),
13
+ onSome: (last) => Stream.make(`last-was-${String(last)}`, 'continuing'),
13
14
  }),
14
15
  ),
15
16
  )
@@ -24,7 +25,7 @@ describe('concatWithLastElement', () => {
24
25
  lastElement.pipe(
25
26
  Option.match({
26
27
  onNone: () => Stream.make('no-previous-element'),
27
- onSome: (last) => Stream.make(`last-was-${last}`),
28
+ onSome: (last) => Stream.make(`last-was-${String(last)}`),
28
29
  }),
29
30
  ),
30
31
  )
@@ -39,7 +40,7 @@ describe('concatWithLastElement', () => {
39
40
  lastElement.pipe(
40
41
  Option.match({
41
42
  onNone: () => Stream.make('unexpected'),
42
- onSome: (last) => Stream.make(`after-${last}`),
43
+ onSome: (last) => Stream.make(`after-${String(last)}`),
43
44
  }),
44
45
  ),
45
46
  )
@@ -83,7 +84,7 @@ describe('concatWithLastElement', () => {
83
84
  const result = concatWithLastElement(stream1, (lastElement) =>
84
85
  lastElement.pipe(
85
86
  Option.match({
86
- onNone: () => Stream.make('no-number') as Stream.Stream<number | string, never, never>,
87
+ onNone: () => Stream.make('no-number') as Stream.Stream<number | string>,
87
88
  onSome: (last) => Stream.make(last * 10, last * 100),
88
89
  }),
89
90
  ),
@@ -5,17 +5,17 @@ import type { Predicate, Refinement } from 'effect/Predicate'
5
5
  export * from 'effect/SubscriptionRef'
6
6
 
7
7
  export const waitUntil: {
8
- <A, B extends A>(
8
+ <A, B extends A>(
9
9
  refinement: Refinement<NoInfer<A>, B>,
10
- ): (sref: SubscriptionRef.SubscriptionRef<A>) => Effect.Effect<B, never, never>
10
+ ): (sref: SubscriptionRef.SubscriptionRef<A>) => Effect.Effect<B>
11
11
  <A, B extends A>(
12
12
  predicate: Predicate<B>,
13
- ): (sref: SubscriptionRef.SubscriptionRef<A>) => Effect.Effect<A, never, never>
13
+ ): (sref: SubscriptionRef.SubscriptionRef<A>) => Effect.Effect<A>
14
14
  <A, B extends A>(
15
15
  sref: SubscriptionRef.SubscriptionRef<A>,
16
16
  refinement: Refinement<NoInfer<A>, B>,
17
- ): Effect.Effect<B, never, never>
18
- <A, B extends A>(sref: SubscriptionRef.SubscriptionRef<A>, predicate: Predicate<B>): Effect.Effect<A, never, never>
17
+ ): Effect.Effect<B>
18
+ <A, B extends A>(sref: SubscriptionRef.SubscriptionRef<A>, predicate: Predicate<B>): Effect.Effect<A>
19
19
  } = dual(2, <A>(sref: SubscriptionRef.SubscriptionRef<A>, predicate: (a: A) => boolean) =>
20
20
  pipe(sref.changes, Stream.filter(predicate), Stream.take(1), Stream.runCollect, Effect.map(Chunk.unsafeHead)),
21
21
  )
@@ -2,7 +2,7 @@ import * as Vitest from '@effect/vitest'
2
2
  import { Effect, Schema, Stream } from 'effect'
3
3
  import { JSDOM } from 'jsdom'
4
4
 
5
- import * as WebChannel from './WebChannel.ts'
5
+ import * as WebChannel from '../../browser/WebChannelBrowser.ts'
6
6
 
7
7
  Vitest.describe('WebChannel', () => {
8
8
  Vitest.describe('windowChannel', () => {