@livestore/utils 0.4.0-dev.21 → 0.4.0-dev.23

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