@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
@@ -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
 
@@ -15,7 +15,7 @@ export const takeBetween = <A>(
15
15
  bucket: BucketQueue<A>,
16
16
  min: number,
17
17
  max: number,
18
- ): STM.STM<ReadonlyArray<A>, never, never> =>
18
+ ): STM.STM<ReadonlyArray<A>> =>
19
19
  STM.gen(function* () {
20
20
  const bucketValue = yield* TRef.get(bucket)
21
21
  if (bucketValue.length < min) {
@@ -0,0 +1,470 @@
1
+ import * as Cause from 'effect/Cause'
2
+ import * as Context from 'effect/Context'
3
+ import * as Effect from 'effect/Effect'
4
+ import type * as Exit from 'effect/Exit'
5
+ import type * as Fiber from 'effect/Fiber'
6
+ import * as Graph from 'effect/Graph'
7
+ import * as Option from 'effect/Option'
8
+ import * as RuntimeFlags from 'effect/RuntimeFlags'
9
+ import * as Scope from 'effect/Scope'
10
+ import type * as Tracer from 'effect/Tracer'
11
+
12
+ /**
13
+ * How to use:
14
+ * 1. Call `Debug.attachSlowDebugInstrumentation` in the root/main file of your program to ensure it is loaded as soon as possible.
15
+ * 2. Call `Debug.logDebug` to log the current state of the effect system.
16
+ */
17
+
18
+ interface SpanEvent {
19
+ readonly name: string
20
+ readonly startTime: bigint
21
+ readonly attributes?: Record<string, unknown>
22
+ }
23
+
24
+ interface GraphNodeInfo {
25
+ readonly span: Tracer.AnySpan
26
+ readonly exitTag: 'Success' | 'Failure' | 'Interrupted' | undefined
27
+ readonly events: Array<SpanEvent>
28
+ }
29
+
30
+ export type MutableSpanGraph = Graph.MutableGraph<GraphNodeInfo, void>
31
+ export type MutableSpanGraphInfo = {
32
+ readonly graph: MutableSpanGraph
33
+ readonly nodeIdBySpanId: Map<string, number>
34
+ }
35
+
36
+ const graphByTraceId = new Map<string, MutableSpanGraphInfo>()
37
+
38
+ const ensureSpan = (traceId: string, spanId: string): [MutableSpanGraph, number] => {
39
+ let info = graphByTraceId.get(traceId)
40
+ if (info === undefined) {
41
+ info = {
42
+ graph: Graph.beginMutation(Graph.directed<GraphNodeInfo, void>()),
43
+ nodeIdBySpanId: new Map<string, number>(),
44
+ }
45
+ graphByTraceId.set(traceId, info)
46
+ }
47
+ let nodeId = info.nodeIdBySpanId.get(spanId)
48
+ if (nodeId === undefined) {
49
+ nodeId = Graph.addNode(info.graph, {
50
+ span: { _tag: 'ExternalSpan', spanId, traceId, sampled: false, context: Context.empty() },
51
+ events: [],
52
+ exitTag: undefined,
53
+ })
54
+ info.nodeIdBySpanId.set(spanId, nodeId)
55
+ }
56
+ return [info.graph, nodeId]
57
+ }
58
+
59
+ const sortSpan = (
60
+ prev: Tracer.AnySpan,
61
+ next: Tracer.AnySpan,
62
+ ): [info: Tracer.AnySpan, isUpgrade: boolean, timingUpdated: boolean] => {
63
+ if (prev._tag === 'ExternalSpan' && next._tag === 'Span') return [next, true, true]
64
+ if (prev._tag === 'Span' && next._tag === 'Span' && next.status._tag === 'Ended') return [next, false, true]
65
+ return [prev, false, false]
66
+ }
67
+
68
+ const addNode = (span: Tracer.AnySpan) => {
69
+ const [mutableGraph, nodeId] = ensureSpan(span.traceId, span.spanId)
70
+ Graph.updateNode(mutableGraph, nodeId, (previousInfo) => {
71
+ const [latestInfo, upgraded] = sortSpan(previousInfo.span, span)
72
+ if (upgraded === true && latestInfo._tag === 'Span' && Option.isSome(latestInfo.parent) === true) {
73
+ const parentNodeId = addNode(latestInfo.parent.value)
74
+ Graph.addEdge(mutableGraph, parentNodeId, nodeId, undefined)
75
+ }
76
+ return { ...previousInfo, span: latestInfo }
77
+ })
78
+ return nodeId
79
+ }
80
+
81
+ const addEvent = (traceId: string, spanId: string, event: SpanEvent) => {
82
+ const [mutableGraph, nodeId] = ensureSpan(traceId, spanId)
83
+ Graph.updateNode(mutableGraph, nodeId, (previousInfo) => {
84
+ return { ...previousInfo, events: [...previousInfo.events, event] }
85
+ })
86
+ return nodeId
87
+ }
88
+ const addNodeExit = (traceId: string, spanId: string, exit: Exit.Exit<any, any>) => {
89
+ const [mutableGraph, nodeId] = ensureSpan(traceId, spanId)
90
+ Graph.updateNode(mutableGraph, nodeId, (previousInfo) => {
91
+ const isInterruptedOnly = exit._tag === 'Failure' && Cause.isInterruptedOnly(exit.cause)
92
+ return {
93
+ ...previousInfo,
94
+ exitTag: isInterruptedOnly === true ? ('Interrupted' as const) : exit._tag,
95
+ }
96
+ })
97
+ return nodeId
98
+ }
99
+
100
+ const createPropertyInterceptor = <T extends object, K extends keyof T>(
101
+ obj: T,
102
+ property: K,
103
+ interceptor: (value: T[K]) => void,
104
+ ): void => {
105
+ const descriptor = Object.getOwnPropertyDescriptor(obj, property)
106
+
107
+ const previousSetter = descriptor?.set
108
+
109
+ let currentValue: T[K]
110
+ const previousGetter = descriptor?.get
111
+
112
+ if (previousGetter == null) {
113
+ currentValue = obj[property]
114
+ }
115
+
116
+ Object.defineProperty(obj, property, {
117
+ get(): T[K] {
118
+ if (previousGetter !== undefined) {
119
+ return previousGetter.call(obj)
120
+ }
121
+ return currentValue
122
+ },
123
+ set(value: T[K]) {
124
+ if (previousSetter !== undefined) {
125
+ previousSetter.call(obj, value)
126
+ } else {
127
+ currentValue = value
128
+ }
129
+ interceptor(value)
130
+ },
131
+ enumerable: descriptor?.enumerable ?? true,
132
+ configurable: descriptor?.configurable ?? true,
133
+ })
134
+ }
135
+
136
+ type EffectDevtoolsHookEvent =
137
+ | {
138
+ _tag: 'FiberAllocated'
139
+ fiber: Fiber.RuntimeFiber<any, any>
140
+ }
141
+ | {
142
+ _tag: 'ScopeAllocated'
143
+ scope: Scope.Scope
144
+ }
145
+
146
+ type GlobalWithFiberCurrent = {
147
+ 'effect/FiberCurrent': Fiber.RuntimeFiber<any, any> | undefined
148
+ 'effect/DevtoolsHook'?: {
149
+ onEvent: (event: EffectDevtoolsHookEvent) => void
150
+ }
151
+ }
152
+
153
+ const patchedTracer = new WeakSet<Tracer.Tracer>()
154
+ const ensureTracerPatched = (currentTracer: Tracer.Tracer) => {
155
+ if (patchedTracer.has(currentTracer) === true) {
156
+ return
157
+ }
158
+ patchedTracer.add(currentTracer)
159
+
160
+ const oldSpanConstructor = currentTracer.span
161
+ currentTracer.span = function (...args) {
162
+ const span = oldSpanConstructor.apply(this, args)
163
+ addNode(span)
164
+
165
+ const oldSpanEnd = span.end
166
+ span.end = function (endTime, exit, ...args) {
167
+ oldSpanEnd.apply(this, [endTime, exit, ...args])
168
+ addNodeExit(this.traceId, this.spanId, exit)
169
+ }
170
+
171
+ const oldSpanEvent = span.event
172
+ span.event = function (name, startTime, attributes, ...args) {
173
+ oldSpanEvent.apply(this, [name, startTime, attributes, ...args])
174
+ addEvent(this.traceId, this.spanId, { name, startTime, attributes: attributes ?? {} })
175
+ }
176
+
177
+ return span
178
+ }
179
+
180
+ const oldContext = currentTracer.context
181
+ currentTracer.context = function (f, fiber, ...args) {
182
+ const context = oldContext.apply(this, [f, fiber, ...args])
183
+ ensureFiberPatched(fiber)
184
+ // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- Effect Tracer.context return type is opaque; patching requires cast
185
+ return context as any
186
+ }
187
+ }
188
+
189
+ interface ScopeImpl extends Scope.Scope {
190
+ readonly state:
191
+ | {
192
+ readonly _tag: 'Open'
193
+ readonly finalizers: Map<{}, Scope.Scope.Finalizer>
194
+ }
195
+ | {
196
+ readonly _tag: 'Closed'
197
+ readonly exit: Exit.Exit<unknown, unknown>
198
+ }
199
+ }
200
+
201
+ const knownScopes = new Map<
202
+ ScopeImpl,
203
+ { id: number; allocationFiber: Fiber.RuntimeFiber<any, any> | undefined; allocationSpan: Tracer.AnySpan | undefined }
204
+ >()
205
+ let lastScopeId = 0
206
+ const ensureScopePatched = (scope: ScopeImpl, allocationFiber: Fiber.RuntimeFiber<any, any> | undefined) => {
207
+ if (scope.state._tag === 'Closed') return
208
+ if (knownScopes.has(scope) === true) return
209
+ const id = lastScopeId++
210
+ if (patchScopeClose === true) {
211
+ // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- patching Scope.close; ScopeImpl is an internal interface not exported by Effect
212
+ const oldClose = (scope as any).close
213
+ // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- patching Scope.close; ScopeImpl is an internal interface not exported by Effect
214
+ ;(scope as any).close = function (...args: any[]) {
215
+ return oldClose.apply(this, args).pipe(
216
+ Effect.withSpan(`scope.${id}.closeRunFinalizers`),
217
+ Effect.ensuring(
218
+ Effect.sync(() => {
219
+ knownScopes.delete(scope)
220
+ }),
221
+ ),
222
+ )
223
+ }
224
+ } else {
225
+ cleanupScopes()
226
+ }
227
+ const allocationSpan = allocationFiber?.currentSpan
228
+ knownScopes.set(scope, { id, allocationFiber, allocationSpan })
229
+ }
230
+ const cleanupScopes = () => {
231
+ for (const [scope] of knownScopes) {
232
+ if (scope.state._tag === 'Closed') knownScopes.delete(scope)
233
+ }
234
+ }
235
+
236
+ const knownFibers = new Set<Fiber.RuntimeFiber<any, any>>()
237
+ const ensureFiberPatched = (fiber: Fiber.RuntimeFiber<any, any>) => {
238
+ // patch tracer
239
+ ensureTracerPatched(fiber.currentTracer)
240
+ // patch scope
241
+ const currentScope = Context.getOrElse(fiber.currentContext, Scope.Scope, () => undefined)
242
+ // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- casting Scope to ScopeImpl; internal Effect type not publicly exported
243
+ if (currentScope !== undefined) ensureScopePatched(currentScope as any as ScopeImpl, undefined)
244
+ // patch fiber
245
+ if (knownFibers.has(fiber) === true) return
246
+ knownFibers.add(fiber)
247
+ fiber.addObserver((exit) => {
248
+ knownFibers.delete(fiber)
249
+ onFiberCompleted?.(fiber, exit)
250
+ })
251
+ }
252
+
253
+ let patchScopeClose = false
254
+ let onFiberResumed: undefined | ((fiber: Fiber.RuntimeFiber<any, any>) => void)
255
+ let onFiberSuspended: undefined | ((fiber: Fiber.RuntimeFiber<any, any>) => void)
256
+ let onFiberCompleted: undefined | ((fiber: Fiber.RuntimeFiber<any, any>, exit: Exit.Exit<any, any>) => void)
257
+ export const attachSlowDebugInstrumentation = (options: {
258
+ /** If set to true, the scope prototype will be patched to attach a span to visualize pending scope closing */
259
+ readonly patchScopeClose?: boolean
260
+ /** An optional callback that will be called when any fiber resumes performing a run loop */
261
+ readonly onFiberResumed?: (fiber: Fiber.RuntimeFiber<any, any>) => void
262
+ /** An optional callback that will be called when any fiber stops performing a run loop */
263
+ readonly onFiberSuspended?: (fiber: Fiber.RuntimeFiber<any, any>) => void
264
+ /** An optional callback that will be called when any fiber completes with a exit */
265
+ readonly onFiberCompleted?: (fiber: Fiber.RuntimeFiber<any, any>, exit: Exit.Exit<any, any>) => void
266
+ }): void => {
267
+ // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- accessing Effect's global fiber tracking via well-known symbol keys
268
+ const _globalThis = globalThis as any as GlobalWithFiberCurrent
269
+ if (_globalThis['effect/DevtoolsHook'] !== undefined) {
270
+ return console.error(
271
+ '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.',
272
+ )
273
+ }
274
+ patchScopeClose = options.patchScopeClose ?? false
275
+ onFiberResumed = options.onFiberResumed
276
+ onFiberSuspended = options.onFiberSuspended
277
+ onFiberCompleted = options.onFiberCompleted
278
+ let lastFiber: undefined | Fiber.RuntimeFiber<any, any>
279
+ // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- accessing Effect's global fiber tracking via well-known symbol keys
280
+ createPropertyInterceptor(globalThis as any as GlobalWithFiberCurrent, 'effect/FiberCurrent', (value) => {
281
+ if (value !== undefined && knownFibers.has(value) === true) onFiberResumed?.(value)
282
+ if (value !== undefined) ensureFiberPatched(value)
283
+ if (value == null && lastFiber !== undefined && knownFibers.has(lastFiber) === true) onFiberSuspended?.(lastFiber)
284
+ lastFiber = value
285
+ })
286
+ _globalThis['effect/DevtoolsHook'] = {
287
+ onEvent: (event) => {
288
+ console.log('onEvent', event)
289
+ switch (event._tag) {
290
+ case 'ScopeAllocated':
291
+ // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- casting Scope to ScopeImpl; internal Effect type not publicly exported
292
+ ensureScopePatched(event.scope as any as ScopeImpl, _globalThis['effect/FiberCurrent'])
293
+ break
294
+ case 'FiberAllocated':
295
+ ensureFiberPatched(event.fiber)
296
+ break
297
+ }
298
+ },
299
+ }
300
+ }
301
+
302
+ const formatDuration = (startTime: bigint, endTime: bigint | undefined): string => {
303
+ if (endTime === undefined) return '[running]'
304
+ const durationMs = Number(endTime - startTime) / 1000000 // Convert nanoseconds to milliseconds
305
+ if (durationMs < 1000) return `${durationMs.toFixed(0)}ms`
306
+ if (durationMs < 60000) return `${(durationMs / 1000).toFixed(2)}s`
307
+ return `${(durationMs / 60000).toFixed(2)}m`
308
+ }
309
+
310
+ const getSpanName = (span: Tracer.AnySpan): string => {
311
+ if (span._tag === 'ExternalSpan') return `[external] ${span.spanId}`
312
+ return span.name
313
+ }
314
+
315
+ const getSpanStatus = (info: GraphNodeInfo): string => {
316
+ if (info.span._tag === 'ExternalSpan') return '?'
317
+ if (info.exitTag === 'Success') return '✓'
318
+ if (info.exitTag === 'Failure') return '✗'
319
+ if (info.exitTag === 'Interrupted') return '!'
320
+ return '⋮'
321
+ }
322
+
323
+ const getSpanDuration = (span: Tracer.AnySpan): string => {
324
+ if (span._tag === 'ExternalSpan') return ''
325
+ const endTime = span.status._tag === 'Ended' ? span.status.endTime : undefined
326
+ return formatDuration(span.status.startTime, endTime)
327
+ }
328
+
329
+ const filterGraphKeepAncestors = <N, E>(
330
+ graph: Graph.Graph<N, E>,
331
+ predicate: (nodeData: N, nodeId: number) => boolean,
332
+ ): Graph.Graph<N, E> => {
333
+ // Find all root nodes (nodes with no incoming edges)
334
+ const rootNodes = Array.from(Graph.indices(Graph.externals(graph, { direction: 'incoming' })))
335
+ const shouldInclude = new Set<number>()
336
+
337
+ // Use postorder DFS to evaluate children before parents
338
+ for (const nodeId of Graph.indices(Graph.dfsPostOrder(graph, { start: rootNodes, direction: 'outgoing' }))) {
339
+ const node = Graph.getNode(graph, nodeId)
340
+ if (Option.isNone(node) === true) continue
341
+
342
+ const matchesPredicate = predicate(node.value, nodeId)
343
+ if (matchesPredicate === true) {
344
+ shouldInclude.add(nodeId)
345
+ } else {
346
+ const children = Graph.neighborsDirected(graph, nodeId, 'outgoing')
347
+ const hasMatchingChildren = children.some((childId) => shouldInclude.has(childId))
348
+ if (hasMatchingChildren === true) shouldInclude.add(nodeId)
349
+ }
350
+ }
351
+
352
+ // Create a filtered copy of the graph
353
+ return Graph.mutate(graph, (mutable) => {
354
+ for (const [nodeId] of mutable.nodes) {
355
+ if (shouldInclude.has(nodeId) === true) continue
356
+ Graph.removeNode(mutable, nodeId)
357
+ }
358
+ })
359
+ }
360
+
361
+ const renderSpanNode = (graph: Graph.Graph<GraphNodeInfo, void>, nodeId: number): string[] => {
362
+ const node = Graph.getNode(graph, nodeId)
363
+ if (Option.isNone(node) === true) return []
364
+ const info = node.value
365
+ const status = getSpanStatus(info)
366
+ const name = getSpanName(info.span)
367
+ const duration = getSpanDuration(info.span)
368
+ const durationStr = duration !== undefined ? ` ${duration}` : ''
369
+
370
+ const fiberIds = Array.from(knownFibers)
371
+ .filter(
372
+ (fiber) => fiber.currentSpan?.spanId === info.span.spanId && fiber.currentSpan?.traceId === info.span.traceId,
373
+ )
374
+ .map((fiber) => `#${fiber.id().id}`)
375
+ .join(', ')
376
+ const runningOnFibers = fiberIds.length > 0 ? ` [fibers ${fiberIds}]` : ''
377
+
378
+ return [` ${status} ${name}${durationStr}${runningOnFibers}`]
379
+ }
380
+
381
+ const renderTree = <N, E, T extends Graph.Kind>(
382
+ graph: Graph.Graph<N, E, T>,
383
+ nodeIds: Array<number>,
384
+ renderNode: (graph: Graph.Graph<N, E, T>, nodeId: number) => string[],
385
+ ): string[] => {
386
+ let lines: string[] = []
387
+ for (let childIndex = 0; childIndex < nodeIds.length; childIndex++) {
388
+ const isLastChild = childIndex === nodeIds.length - 1
389
+ const childLines = renderNode(graph, nodeIds[childIndex]!).concat(
390
+ renderTree(graph, Graph.neighborsDirected(graph, nodeIds[childIndex]!, 'outgoing'), renderNode),
391
+ )
392
+ lines = [
393
+ ...lines,
394
+ ...childLines.map((l, lineIndex) => {
395
+ if (lineIndex === 0) {
396
+ return (isLastChild === true ? ' └─' : ' ├─') + l
397
+ }
398
+ return (isLastChild === true ? ' ' : ' │') + l
399
+ }),
400
+ ]
401
+ }
402
+
403
+ return lines
404
+ }
405
+
406
+ export interface LogDebugOptions {
407
+ readonly regex?: RegExp
408
+ readonly title?: string
409
+ }
410
+
411
+ export const logDebug = (options: LogDebugOptions = {}) => {
412
+ // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- accessing Effect's global fiber tracking via well-known symbol keys
413
+ const _globalThis = globalThis as any as GlobalWithFiberCurrent
414
+ if (_globalThis['effect/DevtoolsHook'] == null) {
415
+ return console.error(
416
+ '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.',
417
+ )
418
+ }
419
+
420
+ let lines: Array<string> = [`----------------${options.title ?? ''}----------------`]
421
+
422
+ // fibers
423
+ lines = [...lines, 'Active Fibers:']
424
+ for (const fiber of knownFibers) {
425
+ // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- accessing fiber.currentRuntimeFlags; internal Effect runtime property
426
+ const interruptible = RuntimeFlags.interruptible((fiber as any).currentRuntimeFlags)
427
+ lines = [...lines, `- #${fiber.id().id}${interruptible === false ? ' [uninterruptible]' : ''}`]
428
+ }
429
+ if (knownFibers.size === 0) {
430
+ lines = [...lines, '- No active effect fibers']
431
+ }
432
+ lines = [...lines, '']
433
+
434
+ // spans
435
+ for (const [traceId, info] of graphByTraceId) {
436
+ const graph = Graph.endMutation(info.graph)
437
+ const filteredGraph =
438
+ options.regex !== undefined
439
+ ? filterGraphKeepAncestors(graph, (nodeData, _nodeId) => {
440
+ const name = getSpanName(nodeData.span)
441
+ return options.regex!.test(name)
442
+ })
443
+ : graph
444
+ const filteredRootNodes = Array.from(Graph.indices(Graph.externals(filteredGraph, { direction: 'incoming' })))
445
+
446
+ lines = [...lines, `Spans Trace ${traceId}:`, ...renderTree(filteredGraph, filteredRootNodes, renderSpanNode)]
447
+ }
448
+ lines = [...lines, '? external span - ✓ success - ✗ failure - ! interrupted', '']
449
+
450
+ // scopes
451
+ lines = [...lines, 'Open Scopes:']
452
+ for (const [scope, info] of knownScopes) {
453
+ const fiberIds = Array.from(knownFibers)
454
+ .filter((fiber) => Context.getOrElse(fiber.currentContext, Scope.Scope, () => undefined) === scope)
455
+ .map((fiber) => `#${fiber.id().id}`)
456
+ .join(', ')
457
+ const usedByFibers = fiberIds.length > 0 ? ` [used by: ${fiberIds}]` : ''
458
+ const allocationFiber =
459
+ info.allocationFiber !== undefined ? ` [allocated in fiber #${info.allocationFiber.id().id}]` : ''
460
+ const allocationSpan =
461
+ info.allocationSpan !== undefined ? ` [allocated in span: ${getSpanName(info.allocationSpan)}]` : ''
462
+ lines = [...lines, `- #${info.id}${usedByFibers}${allocationFiber}${allocationSpan}`]
463
+ }
464
+ if (knownScopes.size === 0) {
465
+ lines = [...lines, '- No active scopes']
466
+ }
467
+ lines = [...lines, '']
468
+
469
+ console.log(lines.join('\n'))
470
+ }