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