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