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