@livestore/utils 0.4.0-dev.9 → 0.5.0-dev.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (316) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/NoopTracer.d.ts +7 -8
  3. package/dist/NoopTracer.d.ts.map +1 -1
  4. package/dist/NoopTracer.js +13 -15
  5. package/dist/NoopTracer.js.map +1 -1
  6. package/dist/NoopTracer.test.d.ts +2 -0
  7. package/dist/NoopTracer.test.d.ts.map +1 -0
  8. package/dist/NoopTracer.test.js +17 -0
  9. package/dist/NoopTracer.test.js.map +1 -0
  10. package/dist/binary.js +1 -1
  11. package/dist/binary.js.map +1 -1
  12. package/dist/browser/Opfs/Opfs.d.ts +201 -0
  13. package/dist/browser/Opfs/Opfs.d.ts.map +1 -0
  14. package/dist/browser/Opfs/Opfs.js +170 -0
  15. package/dist/browser/Opfs/Opfs.js.map +1 -0
  16. package/dist/browser/Opfs/debug-utils.d.ts +20 -0
  17. package/dist/browser/Opfs/debug-utils.d.ts.map +1 -0
  18. package/dist/browser/Opfs/debug-utils.js +95 -0
  19. package/dist/browser/Opfs/debug-utils.js.map +1 -0
  20. package/dist/browser/Opfs/mod.d.ts +4 -0
  21. package/dist/browser/Opfs/mod.d.ts.map +1 -0
  22. package/dist/browser/Opfs/mod.js +4 -0
  23. package/dist/browser/Opfs/mod.js.map +1 -0
  24. package/dist/browser/Opfs/utils.d.ts +71 -0
  25. package/dist/browser/Opfs/utils.d.ts.map +1 -0
  26. package/dist/browser/Opfs/utils.js +227 -0
  27. package/dist/browser/Opfs/utils.js.map +1 -0
  28. package/dist/browser/QuotaExceededError.d.ts +60 -0
  29. package/dist/browser/QuotaExceededError.d.ts.map +1 -0
  30. package/dist/browser/QuotaExceededError.js +2 -0
  31. package/dist/browser/QuotaExceededError.js.map +1 -0
  32. package/dist/browser/WebChannelBrowser.d.ts +22 -0
  33. package/dist/browser/WebChannelBrowser.d.ts.map +1 -0
  34. package/dist/browser/WebChannelBrowser.js +87 -0
  35. package/dist/browser/WebChannelBrowser.js.map +1 -0
  36. package/dist/browser/WebError.d.ts +389 -0
  37. package/dist/browser/WebError.d.ts.map +1 -0
  38. package/dist/browser/WebError.js +423 -0
  39. package/dist/browser/WebError.js.map +1 -0
  40. package/dist/browser/WebError.test.d.ts +2 -0
  41. package/dist/browser/WebError.test.d.ts.map +1 -0
  42. package/dist/browser/WebError.test.js +46 -0
  43. package/dist/browser/WebError.test.js.map +1 -0
  44. package/dist/browser/WebLock.d.ts.map +1 -0
  45. package/dist/{effect → browser}/WebLock.js +21 -21
  46. package/dist/browser/WebLock.js.map +1 -0
  47. package/dist/{browser.d.ts → browser/detect.d.ts} +1 -1
  48. package/dist/browser/detect.d.ts.map +1 -0
  49. package/dist/{browser.js → browser/detect.js} +7 -7
  50. package/dist/browser/detect.js.map +1 -0
  51. package/dist/browser/mod.d.ts +8 -0
  52. package/dist/browser/mod.d.ts.map +1 -0
  53. package/dist/browser/mod.js +8 -0
  54. package/dist/browser/mod.js.map +1 -0
  55. package/dist/cuid/cuid.browser.js +1 -1
  56. package/dist/cuid/cuid.browser.js.map +1 -1
  57. package/dist/cuid/cuid.node.js +1 -1
  58. package/dist/cuid/cuid.node.js.map +1 -1
  59. package/dist/effect/Debug.d.ts +38 -0
  60. package/dist/effect/Debug.d.ts.map +1 -0
  61. package/dist/effect/Debug.js +350 -0
  62. package/dist/effect/Debug.js.map +1 -0
  63. package/dist/effect/Effect.d.ts +91 -22
  64. package/dist/effect/Effect.d.ts.map +1 -1
  65. package/dist/effect/Effect.js +126 -38
  66. package/dist/effect/Effect.js.map +1 -1
  67. package/dist/effect/Effect.test.d.ts +2 -0
  68. package/dist/effect/Effect.test.d.ts.map +1 -0
  69. package/dist/effect/Effect.test.js +72 -0
  70. package/dist/effect/Effect.test.js.map +1 -0
  71. package/dist/effect/Error.d.ts +4 -6
  72. package/dist/effect/Error.d.ts.map +1 -1
  73. package/dist/effect/Error.js +1 -1
  74. package/dist/effect/Error.js.map +1 -1
  75. package/dist/effect/Logger.d.ts +0 -6
  76. package/dist/effect/Logger.d.ts.map +1 -1
  77. package/dist/effect/Logger.js +0 -32
  78. package/dist/effect/Logger.js.map +1 -1
  79. package/dist/effect/OtelTracer.d.ts +1 -1
  80. package/dist/effect/OtelTracer.d.ts.map +1 -1
  81. package/dist/effect/OtelTracer.js +2 -3
  82. package/dist/effect/OtelTracer.js.map +1 -1
  83. package/dist/effect/RpcClient.d.ts +7 -7
  84. package/dist/effect/RpcClient.d.ts.map +1 -1
  85. package/dist/effect/RpcClient.js +74 -54
  86. package/dist/effect/RpcClient.js.map +1 -1
  87. package/dist/effect/Schedule.d.ts +1 -1
  88. package/dist/effect/Schedule.d.ts.map +1 -1
  89. package/dist/effect/Schedule.js +2 -2
  90. package/dist/effect/Schedule.js.map +1 -1
  91. package/dist/effect/Schema/debug-diff.d.ts +1 -1
  92. package/dist/effect/Schema/debug-diff.d.ts.map +1 -1
  93. package/dist/effect/Schema/debug-diff.js +9 -10
  94. package/dist/effect/Schema/debug-diff.js.map +1 -1
  95. package/dist/effect/Schema/debug-diff.test.js +7 -4
  96. package/dist/effect/Schema/debug-diff.test.js.map +1 -1
  97. package/dist/effect/Schema/index.d.ts +28 -18
  98. package/dist/effect/Schema/index.d.ts.map +1 -1
  99. package/dist/effect/Schema/index.js +55 -28
  100. package/dist/effect/Schema/index.js.map +1 -1
  101. package/dist/effect/Stream.d.ts +13 -5
  102. package/dist/effect/Stream.d.ts.map +1 -1
  103. package/dist/effect/Stream.js +19 -12
  104. package/dist/effect/Stream.js.map +1 -1
  105. package/dist/effect/Stream.test.js +12 -12
  106. package/dist/effect/Stream.test.js.map +1 -1
  107. package/dist/effect/Subscribable.d.ts +9 -3
  108. package/dist/effect/Subscribable.d.ts.map +1 -1
  109. package/dist/effect/Subscribable.js +20 -35
  110. package/dist/effect/Subscribable.js.map +1 -1
  111. package/dist/effect/SubscriptionRef.d.ts +5 -6
  112. package/dist/effect/SubscriptionRef.d.ts.map +1 -1
  113. package/dist/effect/SubscriptionRef.js +2 -3
  114. package/dist/effect/SubscriptionRef.js.map +1 -1
  115. package/dist/effect/TaskTracing.d.ts +1 -1
  116. package/dist/effect/TaskTracing.d.ts.map +1 -1
  117. package/dist/effect/TaskTracing.js +12 -16
  118. package/dist/effect/TaskTracing.js.map +1 -1
  119. package/dist/effect/WebChannel/WebChannel.d.ts +11 -30
  120. package/dist/effect/WebChannel/WebChannel.d.ts.map +1 -1
  121. package/dist/effect/WebChannel/WebChannel.js +70 -125
  122. package/dist/effect/WebChannel/WebChannel.js.map +1 -1
  123. package/dist/effect/WebChannel/WebChannel.test.js +16 -16
  124. package/dist/effect/WebChannel/WebChannel.test.js.map +1 -1
  125. package/dist/effect/WebChannel/broadcastChannelWithAck.d.ts.map +1 -1
  126. package/dist/effect/WebChannel/broadcastChannelWithAck.js +20 -13
  127. package/dist/effect/WebChannel/broadcastChannelWithAck.js.map +1 -1
  128. package/dist/effect/WebChannel/common.d.ts +17 -18
  129. package/dist/effect/WebChannel/common.d.ts.map +1 -1
  130. package/dist/effect/WebChannel/common.js +7 -7
  131. package/dist/effect/WebChannel/common.js.map +1 -1
  132. package/dist/effect/WebSocket.d.ts +4 -6
  133. package/dist/effect/WebSocket.d.ts.map +1 -1
  134. package/dist/effect/WebSocket.js +17 -17
  135. package/dist/effect/WebSocket.js.map +1 -1
  136. package/dist/effect/WebSocket.test.js +2 -2
  137. package/dist/effect/WebSocket.test.js.map +1 -1
  138. package/dist/effect/mod.browser-build.test.d.ts +2 -0
  139. package/dist/effect/mod.browser-build.test.d.ts.map +1 -0
  140. package/dist/effect/mod.browser-build.test.js +18 -0
  141. package/dist/effect/mod.browser-build.test.js.map +1 -0
  142. package/dist/effect/mod.d.ts +30 -0
  143. package/dist/effect/mod.d.ts.map +1 -0
  144. package/dist/effect/mod.js +30 -0
  145. package/dist/effect/mod.js.map +1 -0
  146. package/dist/effect/spanEvent.d.ts +12 -0
  147. package/dist/effect/spanEvent.d.ts.map +1 -0
  148. package/dist/effect/spanEvent.js +12 -0
  149. package/dist/effect/spanEvent.js.map +1 -0
  150. package/dist/effect/spanEvent.test.d.ts +2 -0
  151. package/dist/effect/spanEvent.test.d.ts.map +1 -0
  152. package/dist/effect/spanEvent.test.js +79 -0
  153. package/dist/effect/spanEvent.test.js.map +1 -0
  154. package/dist/env.d.ts.map +1 -1
  155. package/dist/env.js +1 -1
  156. package/dist/env.js.map +1 -1
  157. package/dist/fast-deep-equal.d.ts.map +1 -1
  158. package/dist/fast-deep-equal.js +9 -10
  159. package/dist/fast-deep-equal.js.map +1 -1
  160. package/dist/global.d.ts +3 -0
  161. package/dist/global.d.ts.map +1 -1
  162. package/dist/global.js.map +1 -1
  163. package/dist/misc.d.ts +9 -1
  164. package/dist/misc.d.ts.map +1 -1
  165. package/dist/misc.js +12 -4
  166. package/dist/misc.js.map +1 -1
  167. package/dist/mod.d.ts +177 -20
  168. package/dist/mod.d.ts.map +1 -1
  169. package/dist/mod.js +159 -22
  170. package/dist/mod.js.map +1 -1
  171. package/dist/node/mod.d.ts +45 -6
  172. package/dist/node/mod.d.ts.map +1 -1
  173. package/dist/node/mod.js +51 -9
  174. package/dist/node/mod.js.map +1 -1
  175. package/dist/object/index.d.ts +0 -9
  176. package/dist/object/index.d.ts.map +1 -1
  177. package/dist/object/index.js +0 -9
  178. package/dist/object/index.js.map +1 -1
  179. package/dist/object/stringify-object.js +2 -2
  180. package/dist/object/stringify-object.js.map +1 -1
  181. package/dist/object/stringify-object.test.js.map +1 -1
  182. package/dist/qr.d.ts +38 -0
  183. package/dist/qr.d.ts.map +1 -0
  184. package/dist/qr.js +109 -0
  185. package/dist/qr.js.map +1 -0
  186. package/dist/set.js +1 -1
  187. package/dist/set.js.map +1 -1
  188. package/dist/string.d.ts +0 -2
  189. package/dist/string.d.ts.map +1 -1
  190. package/dist/string.js +0 -2
  191. package/dist/string.js.map +1 -1
  192. package/package.json +61 -55
  193. package/src/NoopTracer.test.ts +20 -0
  194. package/src/NoopTracer.ts +28 -37
  195. package/src/binary.ts +1 -1
  196. package/src/browser/Opfs/Opfs.ts +463 -0
  197. package/src/browser/Opfs/debug-utils.ts +154 -0
  198. package/src/browser/Opfs/mod.ts +3 -0
  199. package/src/browser/Opfs/utils.ts +297 -0
  200. package/src/browser/QuotaExceededError.ts +60 -0
  201. package/src/browser/WebChannelBrowser.ts +145 -0
  202. package/src/browser/WebError.test.ts +66 -0
  203. package/src/browser/WebError.ts +587 -0
  204. package/src/{effect → browser}/WebLock.ts +27 -28
  205. package/src/{browser.ts → browser/detect.ts} +6 -6
  206. package/src/browser/mod.ts +8 -0
  207. package/src/cuid/cuid.browser.ts +1 -1
  208. package/src/cuid/cuid.node.ts +1 -1
  209. package/src/effect/Debug.ts +477 -0
  210. package/src/effect/Effect.test.ts +117 -0
  211. package/src/effect/Effect.ts +174 -79
  212. package/src/effect/Error.ts +1 -1
  213. package/src/effect/Logger.ts +0 -48
  214. package/src/effect/OtelTracer.ts +2 -3
  215. package/src/effect/RpcClient.ts +85 -65
  216. package/src/effect/Schedule.ts +4 -5
  217. package/src/effect/Schema/debug-diff.test.ts +7 -7
  218. package/src/effect/Schema/debug-diff.ts +11 -12
  219. package/src/effect/Schema/index.ts +121 -57
  220. package/src/effect/Stream.test.ts +14 -13
  221. package/src/effect/Stream.ts +23 -16
  222. package/src/effect/Subscribable.ts +31 -42
  223. package/src/effect/SubscriptionRef.ts +9 -13
  224. package/src/effect/TaskTracing.ts +13 -16
  225. package/src/effect/WebChannel/WebChannel.test.ts +24 -24
  226. package/src/effect/WebChannel/WebChannel.ts +102 -192
  227. package/src/effect/WebChannel/broadcastChannelWithAck.ts +25 -15
  228. package/src/effect/WebChannel/common.ts +16 -17
  229. package/src/effect/WebSocket.test.ts +2 -2
  230. package/src/effect/WebSocket.ts +20 -16
  231. package/src/effect/mod.browser-build.test.ts +19 -0
  232. package/src/effect/{index.ts → mod.ts} +43 -69
  233. package/src/effect/spanEvent.test.ts +92 -0
  234. package/src/effect/spanEvent.ts +17 -0
  235. package/src/env.ts +2 -1
  236. package/src/fast-deep-equal.ts +9 -10
  237. package/src/global.ts +4 -2
  238. package/src/misc.ts +13 -5
  239. package/src/mod.ts +188 -32
  240. package/src/node/mod.ts +57 -12
  241. package/src/object/index.ts +0 -24
  242. package/src/object/stringify-object.test.ts +1 -0
  243. package/src/object/stringify-object.ts +2 -2
  244. package/src/qr.ts +125 -0
  245. package/src/set.ts +1 -1
  246. package/src/string.ts +0 -3
  247. package/dist/.tsbuildinfo.json +0 -1
  248. package/dist/browser.d.ts.map +0 -1
  249. package/dist/browser.js.map +0 -1
  250. package/dist/effect/BucketQueue.d.ts +0 -20
  251. package/dist/effect/BucketQueue.d.ts.map +0 -1
  252. package/dist/effect/BucketQueue.js +0 -34
  253. package/dist/effect/BucketQueue.js.map +0 -1
  254. package/dist/effect/Scheduler.d.ts +0 -4
  255. package/dist/effect/Scheduler.d.ts.map +0 -1
  256. package/dist/effect/Scheduler.js +0 -9
  257. package/dist/effect/Scheduler.js.map +0 -1
  258. package/dist/effect/ServiceContext.d.ts +0 -37
  259. package/dist/effect/ServiceContext.d.ts.map +0 -1
  260. package/dist/effect/ServiceContext.js +0 -55
  261. package/dist/effect/ServiceContext.js.map +0 -1
  262. package/dist/effect/WebLock.d.ts.map +0 -1
  263. package/dist/effect/WebLock.js.map +0 -1
  264. package/dist/effect/index.d.ts +0 -32
  265. package/dist/effect/index.d.ts.map +0 -1
  266. package/dist/effect/index.js +0 -35
  267. package/dist/effect/index.js.map +0 -1
  268. package/dist/guards.d.ts +0 -6
  269. package/dist/guards.d.ts.map +0 -1
  270. package/dist/guards.js +0 -6
  271. package/dist/guards.js.map +0 -1
  272. package/dist/node/ChildProcessRunner/ChildProcessRunner.d.ts +0 -4
  273. package/dist/node/ChildProcessRunner/ChildProcessRunner.d.ts.map +0 -1
  274. package/dist/node/ChildProcessRunner/ChildProcessRunner.js +0 -122
  275. package/dist/node/ChildProcessRunner/ChildProcessRunner.js.map +0 -1
  276. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.d.ts +0 -2
  277. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.d.ts.map +0 -1
  278. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.js +0 -218
  279. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.js.map +0 -1
  280. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.d.ts +0 -84
  281. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.d.ts.map +0 -1
  282. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.js +0 -68
  283. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.js.map +0 -1
  284. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.d.ts +0 -2
  285. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.d.ts.map +0 -1
  286. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.js +0 -52
  287. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.js.map +0 -1
  288. package/dist/node/ChildProcessRunner/ChildProcessWorker.d.ts +0 -23
  289. package/dist/node/ChildProcessRunner/ChildProcessWorker.d.ts.map +0 -1
  290. package/dist/node/ChildProcessRunner/ChildProcessWorker.js +0 -138
  291. package/dist/node/ChildProcessRunner/ChildProcessWorker.js.map +0 -1
  292. package/dist/object/omit.d.ts +0 -3
  293. package/dist/object/omit.d.ts.map +0 -1
  294. package/dist/object/omit.js +0 -14
  295. package/dist/object/omit.js.map +0 -1
  296. package/dist/object/pick.d.ts +0 -14
  297. package/dist/object/pick.d.ts.map +0 -1
  298. package/dist/object/pick.js +0 -17
  299. package/dist/object/pick.js.map +0 -1
  300. package/dist/time.d.ts +0 -12
  301. package/dist/time.d.ts.map +0 -1
  302. package/dist/time.js +0 -22
  303. package/dist/time.js.map +0 -1
  304. package/src/effect/BucketQueue.ts +0 -49
  305. package/src/effect/Scheduler.ts +0 -13
  306. package/src/effect/ServiceContext.ts +0 -108
  307. package/src/guards.ts +0 -8
  308. package/src/node/ChildProcessRunner/ChildProcessRunner.ts +0 -135
  309. package/src/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.ts +0 -302
  310. package/src/node/ChildProcessRunner/ChildProcessRunnerTest/schema.ts +0 -78
  311. package/src/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.ts +0 -66
  312. package/src/node/ChildProcessRunner/ChildProcessWorker.ts +0 -168
  313. package/src/object/omit.ts +0 -17
  314. package/src/object/pick.ts +0 -27
  315. package/src/time.ts +0 -25
  316. /package/dist/{effect → browser}/WebLock.d.ts +0 -0
@@ -1,5 +1,4 @@
1
- import type { Exit } from 'effect'
2
- import { Deferred, Effect, Runtime } from 'effect'
1
+ import { Deferred, Effect, type Exit } from 'effect'
3
2
 
4
3
  // See https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_API
5
4
  export const withLock =
@@ -14,27 +13,27 @@ export const withLock =
14
13
  }) =>
15
14
  <Ctx, E, A>(eff: Effect.Effect<A, E, Ctx>): Effect.Effect<A, E | E2, Ctx> =>
16
15
  Effect.gen(function* () {
17
- const runtime = yield* Effect.runtime<Ctx>()
16
+ const services = yield* Effect.context<Ctx>()
18
17
 
19
18
  const exit = yield* Effect.tryPromise<Exit.Exit<A, E>, E | E2>({
20
19
  try: (signal) => {
21
- if (signal.aborted) return 'aborted' as never
20
+ if (signal.aborted === true) return 'aborted' as never
22
21
 
23
22
  // NOTE The 'signal' and 'ifAvailable' options cannot be used together.
24
23
  const requestOptions = options?.ifAvailable === true ? options : { ...options, signal }
25
- return navigator.locks.request(lockName, requestOptions, async (lock) => {
24
+ return navigator.locks.request(lockName, requestOptions, async (lock: Lock | null) => {
26
25
  if (lock === null) {
27
- if (onTaken) {
28
- const exit = await Runtime.runPromiseExit(runtime)(onTaken)
29
- if (exit._tag === 'Failure') {
30
- return exit
26
+ if (onTaken !== undefined) {
27
+ const onTakenExit = await Effect.runPromiseExitWith(services)(onTaken)
28
+ if (onTakenExit._tag === 'Failure') {
29
+ return onTakenExit
31
30
  }
32
31
  }
33
32
  return
34
33
  }
35
34
 
36
35
  // TODO also propagate Effect interruption to the execution
37
- return Runtime.runPromiseExit(runtime)(eff)
36
+ return Effect.runPromiseExitWith(services)(eff)
38
37
  }) as unknown as Promise<Exit.Exit<A, E>>
39
38
  },
40
39
  catch: (err) => err as any as E,
@@ -48,11 +47,11 @@ export const withLock =
48
47
  })
49
48
 
50
49
  export const waitForDeferredLock = (deferred: Deferred.Deferred<void>, lockName: string) =>
51
- Effect.async<void>((cb, signal) => {
52
- if (signal.aborted) return
50
+ Effect.callback<void>((cb, signal) => {
51
+ if (signal.aborted === true) return
53
52
 
54
53
  navigator.locks
55
- .request(lockName, { signal, mode: 'exclusive', ifAvailable: false }, (_lock) => {
54
+ .request(lockName, { signal, mode: 'exclusive', ifAvailable: false }, (_lock: Lock | null) => {
56
55
  // immediately continuing calling Effect since we have the lock
57
56
  cb(Effect.void)
58
57
 
@@ -71,8 +70,8 @@ export const waitForDeferredLock = (deferred: Deferred.Deferred<void>, lockName:
71
70
  })
72
71
 
73
72
  export const tryGetDeferredLock = (deferred: Deferred.Deferred<void>, lockName: string) =>
74
- Effect.async<boolean>((cb, signal) => {
75
- navigator.locks.request(lockName, { mode: 'exclusive', ifAvailable: true }, (lock) => {
73
+ Effect.callback<boolean>((cb, signal) => {
74
+ navigator.locks.request(lockName, { mode: 'exclusive', ifAvailable: true }, (lock: Lock | null) => {
76
75
  cb(Effect.succeed(lock !== null))
77
76
 
78
77
  // the code below is still running
@@ -95,8 +94,8 @@ export const tryGetDeferredLock = (deferred: Deferred.Deferred<void>, lockName:
95
94
  })
96
95
 
97
96
  export const stealDeferredLock = (deferred: Deferred.Deferred<void>, lockName: string) =>
98
- Effect.async<boolean>((cb, signal) => {
99
- navigator.locks.request(lockName, { mode: 'exclusive', steal: true }, (lock) => {
97
+ Effect.callback<boolean>((cb, signal) => {
98
+ navigator.locks.request(lockName, { mode: 'exclusive', steal: true }, (lock: Lock | null) => {
100
99
  cb(Effect.succeed(lock !== null))
101
100
 
102
101
  // the code below is still running
@@ -116,24 +115,24 @@ export const stealDeferredLock = (deferred: Deferred.Deferred<void>, lockName: s
116
115
  })
117
116
 
118
117
  export const waitForLock = (lockName: string) =>
119
- Effect.async<void>((cb, signal) => {
120
- if (signal.aborted) return
118
+ Effect.callback<void>((cb, signal) => {
119
+ if (signal.aborted === true) return
121
120
 
122
- navigator.locks.request(lockName, { mode: 'shared', signal, ifAvailable: false }, (_lock) => {
123
- cb(Effect.succeed(void 0))
121
+ navigator.locks.request(lockName, { mode: 'shared', signal, ifAvailable: false }, (_lock: Lock | null) => {
122
+ cb(Effect.void)
124
123
  })
125
124
  })
126
125
 
127
126
  /** Attempts to get the lock if available and waits for it to be stolen */
128
127
  export const getLockAndWaitForSteal = (lockName: string) =>
129
- Effect.async<void>((cb, signal) => {
130
- if (signal.aborted) return
128
+ Effect.callback<void>((cb, signal) => {
129
+ if (signal.aborted === true) return
131
130
 
132
131
  navigator.locks
133
- .request(lockName, { mode: 'exclusive', ifAvailable: true }, async (lock) => {
132
+ .request(lockName, { mode: 'exclusive', ifAvailable: true }, async (lock: Lock | null) => {
134
133
  if (lock === null) {
135
134
  // Lock wasn't available, resolve immediately
136
- cb(Effect.succeed(void 0))
135
+ cb(Effect.void)
137
136
  return
138
137
  }
139
138
 
@@ -148,10 +147,10 @@ export const getLockAndWaitForSteal = (lockName: string) =>
148
147
  resolve()
149
148
  })
150
149
 
151
- return Promise.race([holdLock, signal.aborted ? Promise.resolve() : holdLock]).catch(() => {})
150
+ return Promise.race([holdLock, signal.aborted === true ? Promise.resolve() : holdLock]).catch(() => {})
152
151
  }).catch(() => {})
153
152
 
154
- cb(Effect.succeed(void 0))
153
+ cb(Effect.void)
155
154
  })
156
155
  .catch((error) => {
157
156
  if (
@@ -161,7 +160,7 @@ export const getLockAndWaitForSteal = (lockName: string) =>
161
160
  ) {
162
161
  // Given signal interruption is handled via Effect, we can ignore this case
163
162
  // or the case when the lock is stolen
164
- cb(Effect.succeed(void 0))
163
+ cb(Effect.void)
165
164
  } else {
166
165
  console.error('[@livestore/utils:WebLock] getLockAndWaitForSteal. error', error)
167
166
  throw error
@@ -1,7 +1,7 @@
1
1
  export type DetectedBrowser = 'Something' | 'Opera' | 'Chrome' | 'Safari' | 'Firefox' | 'Edge' | 'Browser'
2
2
 
3
3
  export const detectBrowserName: () => DetectedBrowser = () => {
4
- if (!navigator.userAgent) {
4
+ if (navigator.userAgent == null) {
5
5
  return 'Something'
6
6
  }
7
7
 
@@ -13,19 +13,19 @@ export const detectBrowserName: () => DetectedBrowser = () => {
13
13
 
14
14
  // TODO: also parse out version
15
15
 
16
- if (isOpera) {
16
+ if (isOpera === true) {
17
17
  return 'Opera'
18
18
  }
19
- if (isChrome) {
19
+ if (isChrome === true) {
20
20
  return 'Chrome'
21
21
  }
22
- if (isSafari) {
22
+ if (isSafari === true) {
23
23
  return 'Safari'
24
24
  }
25
- if (isFirefox) {
25
+ if (isFirefox === true) {
26
26
  return 'Firefox'
27
27
  }
28
- if (isEdge) {
28
+ if (isEdge === true) {
29
29
  return 'Edge'
30
30
  }
31
31
  return 'Browser'
@@ -0,0 +1,8 @@
1
+ import '../global.ts'
2
+
3
+ export { BrowserWorker, BrowserWorkerRunner } from '@effect/platform-browser'
4
+ export * as WebSocket from '../effect/WebSocket.ts'
5
+ export * as Opfs from './Opfs/mod.ts'
6
+ export * as WebChannelBrowser from './WebChannelBrowser.ts'
7
+ export * as WebError from './WebError.ts'
8
+ export * as WebLock from './WebLock.ts'
@@ -82,7 +82,7 @@ export const slug = () => {
82
82
 
83
83
  export const isCuid = (stringToCheck: string) => {
84
84
  if (typeof stringToCheck !== 'string') return false
85
- if (stringToCheck.startsWith('c')) return true
85
+ if (stringToCheck.startsWith('c') === true) return true
86
86
  return false
87
87
  }
88
88
 
@@ -90,7 +90,7 @@ export const slug = () => {
90
90
 
91
91
  export const isCuid = (stringToCheck: string) => {
92
92
  if (typeof stringToCheck !== 'string') return false
93
- if (stringToCheck.startsWith('c')) return true
93
+ if (stringToCheck.startsWith('c') === true) return true
94
94
  return false
95
95
  }
96
96
 
@@ -0,0 +1,477 @@
1
+ import { Cause, Context, Effect, type Exit, type Fiber, Graph, Option, Scope, Tracer } from 'effect'
2
+
3
+ /**
4
+ * How to use:
5
+ * 1. Call `Debug.attachSlowDebugInstrumentation` in the root/main file of your program to ensure it is loaded as soon as possible.
6
+ * 2. Call `Debug.logDebug` to log the current state of the effect system.
7
+ */
8
+
9
+ interface SpanEvent {
10
+ readonly name: string
11
+ readonly startTime: bigint
12
+ readonly attributes?: Record<string, unknown>
13
+ }
14
+
15
+ interface GraphNodeInfo {
16
+ readonly span: Tracer.AnySpan
17
+ readonly exitTag: 'Success' | 'Failure' | 'Interrupted' | undefined
18
+ readonly events: Array<SpanEvent>
19
+ }
20
+
21
+ export type MutableSpanGraph = Graph.MutableGraph<GraphNodeInfo, void>
22
+ export type MutableSpanGraphInfo = {
23
+ readonly graph: MutableSpanGraph
24
+ readonly nodeIdBySpanId: Map<string, number>
25
+ }
26
+
27
+ const graphByTraceId = new Map<string, MutableSpanGraphInfo>()
28
+
29
+ const ensureSpan = (traceId: string, spanId: string): [MutableSpanGraph, number] => {
30
+ let info = graphByTraceId.get(traceId)
31
+ if (info === undefined) {
32
+ info = {
33
+ graph: Graph.beginMutation(Graph.directed<GraphNodeInfo, void>()),
34
+ nodeIdBySpanId: new Map<string, number>(),
35
+ }
36
+ graphByTraceId.set(traceId, info)
37
+ }
38
+ let nodeId = info.nodeIdBySpanId.get(spanId)
39
+ if (nodeId === undefined) {
40
+ nodeId = Graph.addNode(info.graph, {
41
+ span: { _tag: 'ExternalSpan', spanId, traceId, sampled: false, annotations: Context.empty() },
42
+ events: [],
43
+ exitTag: undefined,
44
+ })
45
+ info.nodeIdBySpanId.set(spanId, nodeId)
46
+ }
47
+ return [info.graph, nodeId]
48
+ }
49
+
50
+ const sortSpan = (
51
+ prev: Tracer.AnySpan,
52
+ next: Tracer.AnySpan,
53
+ ): [info: Tracer.AnySpan, isUpgrade: boolean, timingUpdated: boolean] => {
54
+ if (prev._tag === 'ExternalSpan' && next._tag === 'Span') return [next, true, true]
55
+ if (prev._tag === 'Span' && next._tag === 'Span' && next.status._tag === 'Ended') return [next, false, true]
56
+ return [prev, false, false]
57
+ }
58
+
59
+ const addNode = (span: Tracer.AnySpan) => {
60
+ const [mutableGraph, nodeId] = ensureSpan(span.traceId, span.spanId)
61
+ Graph.updateNode(mutableGraph, nodeId, (previousInfo) => {
62
+ const [latestInfo, upgraded] = sortSpan(previousInfo.span, span)
63
+ if (upgraded === true && latestInfo._tag === 'Span' && Option.isSome(latestInfo.parent) === true) {
64
+ const parentNodeId = addNode(latestInfo.parent.value)
65
+ Graph.addEdge(mutableGraph, parentNodeId, nodeId, undefined)
66
+ }
67
+ return { ...previousInfo, span: latestInfo }
68
+ })
69
+ return nodeId
70
+ }
71
+
72
+ const addEvent = (traceId: string, spanId: string, event: SpanEvent) => {
73
+ const [mutableGraph, nodeId] = ensureSpan(traceId, spanId)
74
+ Graph.updateNode(mutableGraph, nodeId, (previousInfo) => {
75
+ return { ...previousInfo, events: [...previousInfo.events, event] }
76
+ })
77
+ return nodeId
78
+ }
79
+ const addNodeExit = (traceId: string, spanId: string, exit: Exit.Exit<any, any>) => {
80
+ const [mutableGraph, nodeId] = ensureSpan(traceId, spanId)
81
+ Graph.updateNode(mutableGraph, nodeId, (previousInfo) => {
82
+ const isInterruptedOnly = exit._tag === 'Failure' && Cause.hasInterruptsOnly(exit.cause)
83
+ return {
84
+ ...previousInfo,
85
+ exitTag: isInterruptedOnly === true ? ('Interrupted' as const) : exit._tag,
86
+ }
87
+ })
88
+ return nodeId
89
+ }
90
+
91
+ const createPropertyInterceptor = <T extends object, K extends keyof T>(
92
+ obj: T,
93
+ property: K,
94
+ interceptor: (value: T[K]) => void,
95
+ ): void => {
96
+ const descriptor = Object.getOwnPropertyDescriptor(obj, property)
97
+
98
+ const previousSetter = descriptor?.set
99
+
100
+ let currentValue: T[K]
101
+ const previousGetter = descriptor?.get
102
+
103
+ if (previousGetter == null) {
104
+ currentValue = obj[property]
105
+ }
106
+
107
+ Object.defineProperty(obj, property, {
108
+ get(): T[K] {
109
+ if (previousGetter !== undefined) {
110
+ return previousGetter.call(obj)
111
+ }
112
+ return currentValue
113
+ },
114
+ set(value: T[K]) {
115
+ if (previousSetter !== undefined) {
116
+ previousSetter.call(obj, value)
117
+ } else {
118
+ currentValue = value
119
+ }
120
+ interceptor(value)
121
+ },
122
+ enumerable: descriptor?.enumerable ?? true,
123
+ configurable: descriptor?.configurable ?? true,
124
+ })
125
+ }
126
+
127
+ type EffectDevtoolsHookEvent =
128
+ | {
129
+ _tag: 'FiberAllocated'
130
+ fiber: Fiber.Fiber<any, any>
131
+ }
132
+ | {
133
+ _tag: 'ScopeAllocated'
134
+ scope: Scope.Scope
135
+ }
136
+
137
+ type GlobalWithFiberCurrent = {
138
+ ['~effect/Fiber/currentFiber']: Fiber.Fiber<any, any> | undefined
139
+ 'effect/DevtoolsHook'?: {
140
+ onEvent: (event: EffectDevtoolsHookEvent) => void
141
+ }
142
+ }
143
+
144
+ type MutableTracer = {
145
+ span: Tracer.Tracer['span']
146
+ context?: Tracer.Tracer['context']
147
+ }
148
+
149
+ type EvaluatablePrimitive<X> = Tracer.EffectPrimitive<X> & {
150
+ readonly ['~effect/Effect/evaluate']: (fiber: Fiber.Fiber<any, any>) => X
151
+ }
152
+
153
+ const patchedTracer = new WeakSet<Tracer.Tracer>()
154
+ const ensureTracerPatched = (tracer: Tracer.Tracer) => {
155
+ if (patchedTracer.has(tracer) === true) {
156
+ return
157
+ }
158
+ patchedTracer.add(tracer)
159
+
160
+ // Effect v4 exposes the tracer context as readonly and caches it on fibers, so keep mutation localized here.
161
+ const mutableTracer = tracer as MutableTracer
162
+
163
+ const oldSpanConstructor: Tracer.Tracer['span'] = tracer.span
164
+ mutableTracer.span = function (this: Tracer.Tracer, options: Parameters<Tracer.Tracer['span']>[0]): Tracer.Span {
165
+ const span = oldSpanConstructor.call(this, options)
166
+ addNode(span)
167
+
168
+ const oldSpanEnd = span.end
169
+ span.end = function (this: Tracer.Span, endTime, exit) {
170
+ oldSpanEnd.call(this, endTime, exit)
171
+ addNodeExit(this.traceId, this.spanId, exit)
172
+ }
173
+
174
+ const oldSpanEvent = span.event
175
+ span.event = function (this: Tracer.Span, name, startTime, attributes) {
176
+ oldSpanEvent.call(this, name, startTime, attributes)
177
+ addEvent(this.traceId, this.spanId, { name, startTime, attributes: attributes ?? {} })
178
+ }
179
+
180
+ return span
181
+ }
182
+
183
+ const oldContext = tracer.context
184
+ mutableTracer.context = function <X>(
185
+ this: Tracer.Tracer,
186
+ primitive: Tracer.EffectPrimitive<X>,
187
+ fiber: Fiber.Fiber<any, any>,
188
+ ) {
189
+ ensureFiberPatched(fiber)
190
+ if (oldContext !== undefined) {
191
+ return oldContext(primitive, fiber)
192
+ }
193
+ return (primitive as EvaluatablePrimitive<X>)['~effect/Effect/evaluate'](fiber)
194
+ }
195
+ }
196
+
197
+ interface ScopeImpl extends Scope.Scope {
198
+ readonly state:
199
+ | {
200
+ readonly _tag: 'Open'
201
+ readonly finalizers: Map<{}, (exit: Exit.Exit<unknown, unknown>) => Effect.Effect<unknown>>
202
+ }
203
+ | {
204
+ readonly _tag: 'Closed'
205
+ readonly exit: Exit.Exit<unknown, unknown>
206
+ }
207
+ }
208
+
209
+ const knownScopes = new Map<
210
+ ScopeImpl,
211
+ { id: number; allocationFiber: Fiber.Fiber<any, any> | undefined; allocationSpan: Tracer.AnySpan | undefined }
212
+ >()
213
+ let lastScopeId = 0
214
+ const ensureScopePatched = (scope: ScopeImpl, allocationFiber: Fiber.Fiber<any, any> | undefined) => {
215
+ if (scope.state._tag === 'Closed') return
216
+ if (knownScopes.has(scope) === true) return
217
+ const id = lastScopeId++
218
+ if (patchScopeClose === true) {
219
+ // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- patching Scope.close; ScopeImpl is an internal interface not exported by Effect
220
+ const oldClose = (scope as any).close
221
+ // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- patching Scope.close; ScopeImpl is an internal interface not exported by Effect
222
+ ;(scope as any).close = function (...args: any[]) {
223
+ return oldClose.apply(this, args).pipe(
224
+ Effect.withSpan(`scope.${id}.closeRunFinalizers`),
225
+ Effect.ensuring(
226
+ Effect.sync(() => {
227
+ knownScopes.delete(scope)
228
+ }),
229
+ ),
230
+ )
231
+ }
232
+ } else {
233
+ cleanupScopes()
234
+ }
235
+ const allocationSpan = allocationFiber?.currentSpan
236
+ knownScopes.set(scope, { id, allocationFiber, allocationSpan })
237
+ }
238
+ const cleanupScopes = () => {
239
+ for (const [scope] of knownScopes) {
240
+ if (scope.state._tag === 'Closed') knownScopes.delete(scope)
241
+ }
242
+ }
243
+
244
+ const knownFibers = new Set<Fiber.Fiber<any, any>>()
245
+ const ensureFiberPatched = (fiber: Fiber.Fiber<any, any>) => {
246
+ // patch tracer
247
+ ensureTracerPatched(fiber.getRef(Tracer.Tracer))
248
+ // patch scope
249
+ const currentScope = Context.getOrElse(fiber.context, Scope.Scope, () => undefined)
250
+ // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- casting Scope to ScopeImpl; internal Effect type not publicly exported
251
+ if (currentScope !== undefined) ensureScopePatched(currentScope as any as ScopeImpl, undefined)
252
+ // patch fiber
253
+ if (knownFibers.has(fiber) === true) return
254
+ knownFibers.add(fiber)
255
+ fiber.addObserver((exit) => {
256
+ knownFibers.delete(fiber)
257
+ onFiberCompleted?.(fiber, exit)
258
+ })
259
+ }
260
+
261
+ let patchScopeClose = false
262
+ let onFiberResumed: undefined | ((fiber: Fiber.Fiber<any, any>) => void)
263
+ let onFiberSuspended: undefined | ((fiber: Fiber.Fiber<any, any>) => void)
264
+ let onFiberCompleted: undefined | ((fiber: Fiber.Fiber<any, any>, exit: Exit.Exit<any, any>) => void)
265
+ export const attachSlowDebugInstrumentation = (options: {
266
+ /** If set to true, the scope prototype will be patched to attach a span to visualize pending scope closing */
267
+ readonly patchScopeClose?: boolean
268
+ /** An optional callback that will be called when any fiber resumes performing a run loop */
269
+ readonly onFiberResumed?: (fiber: Fiber.Fiber<any, any>) => void
270
+ /** An optional callback that will be called when any fiber stops performing a run loop */
271
+ readonly onFiberSuspended?: (fiber: Fiber.Fiber<any, any>) => void
272
+ /** An optional callback that will be called when any fiber completes with a exit */
273
+ readonly onFiberCompleted?: (fiber: Fiber.Fiber<any, any>, exit: Exit.Exit<any, any>) => void
274
+ }): void => {
275
+ // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- accessing Effect's global fiber tracking via well-known symbol keys
276
+ const _globalThis = globalThis as any as GlobalWithFiberCurrent
277
+ if (_globalThis['effect/DevtoolsHook'] !== undefined) {
278
+ return console.error(
279
+ 'attachDebugInstrumentation has already been called! To show the tree, call attachDebugInstrumentation() in the root/main file of your program to ensure it is loaded as soon as possible.',
280
+ )
281
+ }
282
+ patchScopeClose = options.patchScopeClose ?? false
283
+ onFiberResumed = options.onFiberResumed
284
+ onFiberSuspended = options.onFiberSuspended
285
+ onFiberCompleted = options.onFiberCompleted
286
+ let lastFiber: undefined | Fiber.Fiber<any, any>
287
+ // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- accessing Effect's global fiber tracking via well-known symbol keys
288
+ createPropertyInterceptor(globalThis as any as GlobalWithFiberCurrent, '~effect/Fiber/currentFiber', (value) => {
289
+ if (value !== undefined && knownFibers.has(value) === true) onFiberResumed?.(value)
290
+ if (value !== undefined) ensureFiberPatched(value)
291
+ if (value == null && lastFiber !== undefined && knownFibers.has(lastFiber) === true) onFiberSuspended?.(lastFiber)
292
+ lastFiber = value
293
+ })
294
+ _globalThis['effect/DevtoolsHook'] = {
295
+ onEvent: (event) => {
296
+ console.log('onEvent', event)
297
+ switch (event._tag) {
298
+ case 'ScopeAllocated':
299
+ // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- casting Scope to ScopeImpl; internal Effect type not publicly exported
300
+ ensureScopePatched(event.scope as any as ScopeImpl, _globalThis['~effect/Fiber/currentFiber'])
301
+ break
302
+ case 'FiberAllocated':
303
+ ensureFiberPatched(event.fiber)
304
+ break
305
+ }
306
+ },
307
+ }
308
+ }
309
+
310
+ const formatDuration = (startTime: bigint, endTime: bigint | undefined): string => {
311
+ if (endTime === undefined) return '[running]'
312
+ const durationMs = Number(endTime - startTime) / 1000000 // Convert nanoseconds to milliseconds
313
+ if (durationMs < 1000) return `${durationMs.toFixed(0)}ms`
314
+ if (durationMs < 60000) return `${(durationMs / 1000).toFixed(2)}s`
315
+ return `${(durationMs / 60000).toFixed(2)}m`
316
+ }
317
+
318
+ const getSpanName = (span: Tracer.AnySpan): string => {
319
+ if (span._tag === 'ExternalSpan') return `[external] ${span.spanId}`
320
+ return span.name
321
+ }
322
+
323
+ const getSpanStatus = (info: GraphNodeInfo): string => {
324
+ if (info.span._tag === 'ExternalSpan') return '?'
325
+ if (info.exitTag === 'Success') return '✓'
326
+ if (info.exitTag === 'Failure') return '✗'
327
+ if (info.exitTag === 'Interrupted') return '!'
328
+ return '⋮'
329
+ }
330
+
331
+ const getSpanDuration = (span: Tracer.AnySpan): string => {
332
+ if (span._tag === 'ExternalSpan') return ''
333
+ const endTime = span.status._tag === 'Ended' ? span.status.endTime : undefined
334
+ return formatDuration(span.status.startTime, endTime)
335
+ }
336
+
337
+ const filterGraphKeepAncestors = <N, E>(
338
+ graph: Graph.Graph<N, E, 'directed'>,
339
+ predicate: (nodeData: N, nodeId: number) => boolean,
340
+ ): Graph.Graph<N, E, 'directed'> => {
341
+ // Find all root nodes (nodes with no incoming edges)
342
+ const rootNodes = Array.from(Graph.indices(Graph.externals(graph, { direction: 'incoming' })))
343
+ const shouldInclude = new Set<number>()
344
+
345
+ // Use postorder DFS to evaluate children before parents
346
+ for (const nodeId of Graph.indices(Graph.dfsPostOrder(graph, { start: rootNodes, direction: 'outgoing' }))) {
347
+ const node = Graph.getNode(graph, nodeId)
348
+ if (Option.isNone(node) === true) continue
349
+
350
+ const matchesPredicate = predicate(node.value, nodeId)
351
+ if (matchesPredicate === true) {
352
+ shouldInclude.add(nodeId)
353
+ } else {
354
+ const children = Graph.neighborsDirected(graph, nodeId, 'outgoing')
355
+ const hasMatchingChildren = children.some((childId) => shouldInclude.has(childId))
356
+ if (hasMatchingChildren === true) shouldInclude.add(nodeId)
357
+ }
358
+ }
359
+
360
+ // Create a filtered copy of the graph
361
+ return Graph.mutate(graph, (mutable) => {
362
+ const nodeIds = Array.from(Graph.indices(Graph.nodes(mutable)))
363
+ for (const nodeId of nodeIds) {
364
+ if (shouldInclude.has(nodeId) === true) continue
365
+ Graph.removeNode(mutable, nodeId)
366
+ }
367
+ })
368
+ }
369
+
370
+ const renderSpanNode = (graph: Graph.Graph<GraphNodeInfo, void, 'directed'>, nodeId: number): string[] => {
371
+ const node = Graph.getNode(graph, nodeId)
372
+ if (Option.isNone(node) === true) return []
373
+ const info = node.value
374
+ const status = getSpanStatus(info)
375
+ const name = getSpanName(info.span)
376
+ const duration = getSpanDuration(info.span)
377
+ const durationStr = duration !== undefined ? ` ${duration}` : ''
378
+
379
+ const fiberIds = Array.from(knownFibers)
380
+ .filter(
381
+ (fiber) => fiber.currentSpan?.spanId === info.span.spanId && fiber.currentSpan?.traceId === info.span.traceId,
382
+ )
383
+ .map((fiber) => `#${fiber.id}`)
384
+ .join(', ')
385
+ const runningOnFibers = fiberIds.length > 0 ? ` [fibers ${fiberIds}]` : ''
386
+
387
+ return [` ${status} ${name}${durationStr}${runningOnFibers}`]
388
+ }
389
+
390
+ const renderTree = <N, E>(
391
+ graph: Graph.Graph<N, E, 'directed'>,
392
+ nodeIds: Array<number>,
393
+ renderNode: (graph: Graph.Graph<N, E, 'directed'>, nodeId: number) => string[],
394
+ ): string[] => {
395
+ let lines: string[] = []
396
+ for (let childIndex = 0; childIndex < nodeIds.length; childIndex++) {
397
+ const isLastChild = childIndex === nodeIds.length - 1
398
+ const childLines = renderNode(graph, nodeIds[childIndex]!).concat(
399
+ renderTree(graph, Graph.neighborsDirected(graph, nodeIds[childIndex]!, 'outgoing'), renderNode),
400
+ )
401
+ lines = [
402
+ ...lines,
403
+ ...childLines.map((l, lineIndex) => {
404
+ if (lineIndex === 0) {
405
+ return (isLastChild === true ? ' └─' : ' ├─') + l
406
+ }
407
+ return (isLastChild === true ? ' ' : ' │') + l
408
+ }),
409
+ ]
410
+ }
411
+
412
+ return lines
413
+ }
414
+
415
+ export interface LogDebugOptions {
416
+ readonly regex?: RegExp
417
+ readonly title?: string
418
+ }
419
+
420
+ export const logDebug = (options: LogDebugOptions = {}) => {
421
+ // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- accessing Effect's global fiber tracking via well-known symbol keys
422
+ const _globalThis = globalThis as any as GlobalWithFiberCurrent
423
+ if (_globalThis['effect/DevtoolsHook'] == null) {
424
+ return console.error(
425
+ 'attachDebugInstrumentation has not been called! To show the tree, call attachDebugInstrumentation() in the root/main file of your program to ensure it is loaded as soon as possible.',
426
+ )
427
+ }
428
+
429
+ let lines: Array<string> = [`----------------${options.title ?? ''}----------------`]
430
+
431
+ // fibers
432
+ lines = [...lines, 'Active Fibers:']
433
+ for (const fiber of knownFibers) {
434
+ lines = [...lines, `- #${fiber.id}`]
435
+ }
436
+ if (knownFibers.size === 0) {
437
+ lines = [...lines, '- No active effect fibers']
438
+ }
439
+ lines = [...lines, '']
440
+
441
+ // spans
442
+ for (const [traceId, info] of graphByTraceId) {
443
+ const graph = Graph.endMutation(info.graph)
444
+ const filteredGraph =
445
+ options.regex !== undefined
446
+ ? filterGraphKeepAncestors(graph, (nodeData, _nodeId) => {
447
+ const name = getSpanName(nodeData.span)
448
+ return options.regex!.test(name)
449
+ })
450
+ : graph
451
+ const filteredRootNodes = Array.from(Graph.indices(Graph.externals(filteredGraph, { direction: 'incoming' })))
452
+
453
+ lines = [...lines, `Spans Trace ${traceId}:`, ...renderTree(filteredGraph, filteredRootNodes, renderSpanNode)]
454
+ }
455
+ lines = [...lines, '? external span - ✓ success - ✗ failure - ! interrupted', '']
456
+
457
+ // scopes
458
+ lines = [...lines, 'Open Scopes:']
459
+ for (const [scope, info] of knownScopes) {
460
+ const fiberIds = Array.from(knownFibers)
461
+ .filter((fiber) => Context.getOrElse(fiber.context, Scope.Scope, () => undefined) === scope)
462
+ .map((fiber) => `#${fiber.id}`)
463
+ .join(', ')
464
+ const usedByFibers = fiberIds.length > 0 ? ` [used by: ${fiberIds}]` : ''
465
+ const allocationFiber =
466
+ info.allocationFiber !== undefined ? ` [allocated in fiber #${info.allocationFiber.id}]` : ''
467
+ const allocationSpan =
468
+ info.allocationSpan !== undefined ? ` [allocated in span: ${getSpanName(info.allocationSpan)}]` : ''
469
+ lines = [...lines, `- #${info.id}${usedByFibers}${allocationFiber}${allocationSpan}`]
470
+ }
471
+ if (knownScopes.size === 0) {
472
+ lines = [...lines, '- No active scopes']
473
+ }
474
+ lines = [...lines, '']
475
+
476
+ console.log(lines.join('\n'))
477
+ }