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