live-traces 0.1.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/README.md +193 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/react/hooks.d.ts +22 -0
- package/dist/react/hooks.d.ts.map +1 -0
- package/dist/react/hooks.js +68 -0
- package/dist/react/hooks.js.map +1 -0
- package/dist/react/index.d.ts +26 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +27 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/store.d.ts +77 -0
- package/dist/react/store.d.ts.map +1 -0
- package/dist/react/store.js +273 -0
- package/dist/react/store.js.map +1 -0
- package/dist/src/LiveTrace.d.ts +55 -0
- package/dist/src/LiveTrace.d.ts.map +1 -0
- package/dist/src/LiveTrace.js +66 -0
- package/dist/src/LiveTrace.js.map +1 -0
- package/dist/src/Logger.d.ts +3 -0
- package/dist/src/Logger.d.ts.map +1 -0
- package/dist/src/Logger.js +30 -0
- package/dist/src/Logger.js.map +1 -0
- package/dist/src/Schema.d.ts +97 -0
- package/dist/src/Schema.d.ts.map +1 -0
- package/dist/src/Schema.js +60 -0
- package/dist/src/Schema.js.map +1 -0
- package/dist/src/Sink.d.ts +36 -0
- package/dist/src/Sink.d.ts.map +1 -0
- package/dist/src/Sink.js +55 -0
- package/dist/src/Sink.js.map +1 -0
- package/dist/src/Tracer.d.ts +24 -0
- package/dist/src/Tracer.d.ts.map +1 -0
- package/dist/src/Tracer.js +154 -0
- package/dist/src/Tracer.js.map +1 -0
- package/dist/src/WrappedSpan.d.ts +44 -0
- package/dist/src/WrappedSpan.d.ts.map +1 -0
- package/dist/src/WrappedSpan.js +104 -0
- package/dist/src/WrappedSpan.js.map +1 -0
- package/dist/src/transports/sse.d.ts +26 -0
- package/dist/src/transports/sse.d.ts.map +1 -0
- package/dist/src/transports/sse.js +118 -0
- package/dist/src/transports/sse.js.map +1 -0
- package/dist/src/types.d.ts +73 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +69 -0
- package/dist/src/types.js.map +1 -0
- package/index.ts +58 -0
- package/package.json +87 -0
- package/react/hooks.ts +73 -0
- package/react/index.ts +30 -0
- package/react/store.ts +357 -0
- package/src/LiveTrace.ts +99 -0
- package/src/Logger.ts +33 -0
- package/src/Schema.ts +70 -0
- package/src/Sink.ts +108 -0
- package/src/Tracer.ts +176 -0
- package/src/WrappedSpan.ts +138 -0
- package/src/__tests__/tracer.test.ts +238 -0
- package/src/transports/sse.ts +127 -0
- package/src/types.ts +151 -0
package/README.md
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# live-traces
|
|
2
|
+
|
|
3
|
+
> Real-time Effect span streaming to frontend UIs. Stream traces from any backend to React with zero overhead.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/live-traces)
|
|
6
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
7
|
+
[](https://github.com/necmttn/live-traces/actions/workflows/ci.yml)
|
|
8
|
+
|
|
9
|
+
`live-traces` turns the [Effect](https://effect.website) tracer into a live UI feed. Wrap a workflow in `withTrace`, mount the React hooks, and the user sees every span - start, end, log event - as it happens.
|
|
10
|
+
|
|
11
|
+
- **Drop-in Tracer decorator.** Composes with `@effect/opentelemetry`. Both OTel and live traces emit in parallel.
|
|
12
|
+
- **Wire format is plain JSON.** Backends in Go, Python, Rust can produce events; React consumes them the same way.
|
|
13
|
+
- **Pluggable transport.** SSE included. Console for dev. WebSocket / durable queues are 30 lines.
|
|
14
|
+
- **Zero-Effect frontend.** `live-traces/react` is just `useSyncExternalStore` + a reducer. Works in any React 18+ app.
|
|
15
|
+
|
|
16
|
+
→ Landing page & live demo: **[live-traces.necmttn.com](https://live-traces.necmttn.com)**
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
bun add live-traces effect
|
|
24
|
+
# or
|
|
25
|
+
npm install live-traces effect
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
React consumers also need `react@>=18`.
|
|
29
|
+
|
|
30
|
+
## Quick start (Effect backend)
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { Effect, Layer } from "effect";
|
|
34
|
+
import {
|
|
35
|
+
LiveTraceLayer,
|
|
36
|
+
TraceSinkLive,
|
|
37
|
+
SSETransportLayer,
|
|
38
|
+
withTrace,
|
|
39
|
+
step,
|
|
40
|
+
liveTraceLogger,
|
|
41
|
+
} from "live-traces";
|
|
42
|
+
import { SSETransportLayer as SSE } from "live-traces/transports/sse";
|
|
43
|
+
|
|
44
|
+
// 1. Compose the layer
|
|
45
|
+
const TraceLive = LiveTraceLayer.pipe(
|
|
46
|
+
Layer.provide(TraceSinkLive({ flushIntervalMs: 100 })),
|
|
47
|
+
Layer.provide(SSE),
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
// 2. Wrap a workflow
|
|
51
|
+
const processDocument = (docId: string) =>
|
|
52
|
+
Effect.gen(function* () {
|
|
53
|
+
yield* Effect.logInfo(`Starting ${docId}`);
|
|
54
|
+
yield* step("Parse")(parsePdf(docId));
|
|
55
|
+
yield* step("Embed")(embedChunks(docId));
|
|
56
|
+
yield* step("Index")(indexVectors(docId));
|
|
57
|
+
}).pipe(
|
|
58
|
+
withTrace({
|
|
59
|
+
traceId: `doc:${docId}`,
|
|
60
|
+
label: "Document processing",
|
|
61
|
+
scope: { type: "user", id: "alice" },
|
|
62
|
+
}),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// 3. Run with the trace layer + the live logger
|
|
66
|
+
Effect.runPromise(processDocument("report.pdf").pipe(Effect.provide(TraceLive)));
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## React frontend
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import { useActiveTraces, useTrace, useTraceSteps } from "live-traces/react";
|
|
73
|
+
|
|
74
|
+
function ActivityPanel() {
|
|
75
|
+
const traces = useActiveTraces();
|
|
76
|
+
return (
|
|
77
|
+
<div>
|
|
78
|
+
{traces.map((t) => (
|
|
79
|
+
<TraceCard key={t.traceId} traceId={t.traceId} />
|
|
80
|
+
))}
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function TraceCard({ traceId }: { traceId: string }) {
|
|
86
|
+
const trace = useTrace(traceId);
|
|
87
|
+
const steps = useTraceSteps(traceId);
|
|
88
|
+
if (!trace) return null;
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div>
|
|
92
|
+
<h3>
|
|
93
|
+
{trace.label} <span>{trace.status}</span>
|
|
94
|
+
</h3>
|
|
95
|
+
<ol>
|
|
96
|
+
{steps.map((s) => (
|
|
97
|
+
<li key={s.spanId}>
|
|
98
|
+
{s.name} · {s.status}
|
|
99
|
+
{s.durationMs != null && ` · ${s.durationMs.toFixed(0)}ms`}
|
|
100
|
+
</li>
|
|
101
|
+
))}
|
|
102
|
+
</ol>
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Connect the store to a transport (SSE shown):
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
import { getTraceStore } from "live-traces/react";
|
|
112
|
+
import type { TraceEvent } from "live-traces/types";
|
|
113
|
+
|
|
114
|
+
const es = new EventSource(`/traces/user/${userId}`);
|
|
115
|
+
es.onmessage = (msg) => {
|
|
116
|
+
const batch: TraceEvent[] = JSON.parse(msg.data);
|
|
117
|
+
getTraceStore().dispatchBatch(batch);
|
|
118
|
+
};
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## SSE server (Bun / Node)
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import { getSseBroker } from "live-traces/transports/sse";
|
|
125
|
+
|
|
126
|
+
// Bun's built-in server
|
|
127
|
+
Bun.serve({
|
|
128
|
+
fetch(req) {
|
|
129
|
+
const url = new URL(req.url);
|
|
130
|
+
const match = url.pathname.match(/^\/traces\/(team|org|user)\/(.+)$/);
|
|
131
|
+
if (!match) return new Response("not found", { status: 404 });
|
|
132
|
+
const [, type, id] = match;
|
|
133
|
+
|
|
134
|
+
return new Response(
|
|
135
|
+
new ReadableStream({
|
|
136
|
+
start(controller) {
|
|
137
|
+
const unsub = getSseBroker().subscribe(
|
|
138
|
+
{ type: type as "user", id: id! },
|
|
139
|
+
(events) => {
|
|
140
|
+
controller.enqueue(
|
|
141
|
+
new TextEncoder().encode(`data: ${JSON.stringify(events)}\n\n`),
|
|
142
|
+
);
|
|
143
|
+
},
|
|
144
|
+
);
|
|
145
|
+
req.signal.addEventListener("abort", () => {
|
|
146
|
+
unsub();
|
|
147
|
+
controller.close();
|
|
148
|
+
});
|
|
149
|
+
},
|
|
150
|
+
}),
|
|
151
|
+
{
|
|
152
|
+
headers: {
|
|
153
|
+
"Content-Type": "text/event-stream",
|
|
154
|
+
"Cache-Control": "no-cache",
|
|
155
|
+
Connection: "keep-alive",
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
);
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Composing with OpenTelemetry
|
|
164
|
+
|
|
165
|
+
`LiveTraceLayer` wraps the current tracer instead of replacing it. Build OTel **outermost** (so it sets the tracer first), then live-traces wraps it.
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
const Env = ServerLive.pipe(
|
|
169
|
+
Layer.provideMerge(ServicesLive),
|
|
170
|
+
Layer.provideMerge(LiveTraceLayer), // inner: wraps OTel
|
|
171
|
+
Layer.provideMerge(TelemetryLive), // outer: sets OTel tracer first
|
|
172
|
+
);
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
OTel still receives every span. Live-traces only annotates spans inside `withTrace` scopes and streams them out - there's no double-export.
|
|
176
|
+
|
|
177
|
+
## Wire format
|
|
178
|
+
|
|
179
|
+
Events are a discriminated union on `_tag`:
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
type TraceEvent = TraceStart | SpanStart | SpanEnd | SpanEvent | TraceEnd;
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
See [`src/types.ts`](./src/types.ts) for the full schema. The `live-traces/types` sub-export is **dependency-free** - any backend can emit these as JSON.
|
|
186
|
+
|
|
187
|
+
## Why?
|
|
188
|
+
|
|
189
|
+
OpenTelemetry is built for ops dashboards. `live-traces` is built for **user-facing** progress UIs - the difference between "show this user what their AI agent is doing right now" and "Datadog has my p99". Same span data, different rendering target.
|
|
190
|
+
|
|
191
|
+
## License
|
|
192
|
+
|
|
193
|
+
Apache-2.0 © Necmettin Karakaya
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* live-traces
|
|
3
|
+
*
|
|
4
|
+
* Real-time Effect span streaming to frontend UIs.
|
|
5
|
+
*
|
|
6
|
+
* Sub-exports:
|
|
7
|
+
* - "live-traces" — Effect Tracer decorator (this file)
|
|
8
|
+
* - "live-traces/types" — Plain TS types, zero deps
|
|
9
|
+
* - "live-traces/react" — React store + hooks, no Effect dep
|
|
10
|
+
*/
|
|
11
|
+
export { LiveTraceLayer } from "./src/Tracer.js";
|
|
12
|
+
export { withTrace, step, LiveSpanRef, type LiveTraceConfig } from "./src/LiveTrace.js";
|
|
13
|
+
export { liveTraceLogger } from "./src/Logger.js";
|
|
14
|
+
export { TraceSink, TraceSinkLive, TraceTransportTag, ConsoleTransportLayer, type TraceTransport, type TraceSinkHandle, type TraceSinkConfig, } from "./src/Sink.js";
|
|
15
|
+
export { WrappedSpan, isWrappedSpan, LiveTraceSymbol } from "./src/WrappedSpan.js";
|
|
16
|
+
export { TraceEventSchema, TraceStartSchema, SpanStartSchema, SpanEndSchema, SpanEventSchema, TraceEndSchema, TraceScopeSchema, } from "./src/Schema.js";
|
|
17
|
+
export type { TraceEvent, TraceStart, SpanStart, SpanEnd, SpanEvent, TraceEnd, TraceScope } from "./src/types.js";
|
|
18
|
+
export { UI_STEP, TRACE_INTERNAL, LIVE_TRACE, LIVE_TRACE_ID, LIVE_TRACE_LABEL, LIVE_TRACE_SCOPE_TYPE, LIVE_TRACE_SCOPE_ID, LIVE_TRACE_PROVIDER, } from "./src/types.js";
|
|
19
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGjD,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAGxF,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGlD,OAAO,EACH,SAAS,EACT,aAAa,EACb,iBAAiB,EACjB,qBAAqB,EACrB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,eAAe,GACvB,MAAM,eAAe,CAAC;AAGvB,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAGnF,OAAO,EACH,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,aAAa,EACb,eAAe,EACf,cAAc,EACd,gBAAgB,GACnB,MAAM,iBAAiB,CAAC;AAGzB,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAElH,OAAO,EACH,OAAO,EACP,cAAc,EACd,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,qBAAqB,EACrB,mBAAmB,EACnB,mBAAmB,GACtB,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* live-traces
|
|
3
|
+
*
|
|
4
|
+
* Real-time Effect span streaming to frontend UIs.
|
|
5
|
+
*
|
|
6
|
+
* Sub-exports:
|
|
7
|
+
* - "live-traces" — Effect Tracer decorator (this file)
|
|
8
|
+
* - "live-traces/types" — Plain TS types, zero deps
|
|
9
|
+
* - "live-traces/react" — React store + hooks, no Effect dep
|
|
10
|
+
*/
|
|
11
|
+
// Core tracer
|
|
12
|
+
export { LiveTraceLayer } from "./src/Tracer.js";
|
|
13
|
+
// User-facing API
|
|
14
|
+
export { withTrace, step, LiveSpanRef } from "./src/LiveTrace.js";
|
|
15
|
+
// Logger (bridges Effect.log → SpanEvent inside traced scopes)
|
|
16
|
+
export { liveTraceLogger } from "./src/Logger.js";
|
|
17
|
+
// Sink + transport
|
|
18
|
+
export { TraceSink, TraceSinkLive, TraceTransportTag, ConsoleTransportLayer, } from "./src/Sink.js";
|
|
19
|
+
// WrappedSpan (for advanced use / testing)
|
|
20
|
+
export { WrappedSpan, isWrappedSpan, LiveTraceSymbol } from "./src/WrappedSpan.js";
|
|
21
|
+
// Effect Schemas
|
|
22
|
+
export { TraceEventSchema, TraceStartSchema, SpanStartSchema, SpanEndSchema, SpanEventSchema, TraceEndSchema, TraceScopeSchema, } from "./src/Schema.js";
|
|
23
|
+
export { UI_STEP, TRACE_INTERNAL, LIVE_TRACE, LIVE_TRACE_ID, LIVE_TRACE_LABEL, LIVE_TRACE_SCOPE_TYPE, LIVE_TRACE_SCOPE_ID, LIVE_TRACE_PROVIDER, } from "./src/types.js";
|
|
24
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,cAAc;AACd,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD,kBAAkB;AAClB,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAwB,MAAM,oBAAoB,CAAC;AAExF,+DAA+D;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAElD,mBAAmB;AACnB,OAAO,EACH,SAAS,EACT,aAAa,EACb,iBAAiB,EACjB,qBAAqB,GAIxB,MAAM,eAAe,CAAC;AAEvB,2CAA2C;AAC3C,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEnF,iBAAiB;AACjB,OAAO,EACH,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,aAAa,EACb,eAAe,EACf,cAAc,EACd,gBAAgB,GACnB,MAAM,iBAAiB,CAAC;AAKzB,OAAO,EACH,OAAO,EACP,cAAc,EACd,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,qBAAqB,EACrB,mBAAmB,EACnB,mBAAmB,GACtB,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { SpanNode, TraceState } from "./store.js";
|
|
2
|
+
/**
|
|
3
|
+
* Subscribe to all active traces.
|
|
4
|
+
* Returns traces sorted by startedAt descending.
|
|
5
|
+
*/
|
|
6
|
+
export declare function useActiveTraces(): TraceState[];
|
|
7
|
+
/**
|
|
8
|
+
* Subscribe to a single trace by ID.
|
|
9
|
+
* Returns undefined if the trace doesn't exist (yet).
|
|
10
|
+
*/
|
|
11
|
+
export declare function useTrace(traceId: string): TraceState | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Get the step spans (ui.step=true) for a trace, in chronological order.
|
|
14
|
+
* These are the user-visible processing stages.
|
|
15
|
+
*/
|
|
16
|
+
export declare function useTraceSteps(traceId: string): SpanNode[];
|
|
17
|
+
/**
|
|
18
|
+
* Get the root span tree for a trace.
|
|
19
|
+
* Returns the root SpanNode with nested children.
|
|
20
|
+
*/
|
|
21
|
+
export declare function useSpanTree(traceId: string): SpanNode | undefined;
|
|
22
|
+
//# sourceMappingURL=hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../react/hooks.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAiBvD;;;GAGG;AACH,wBAAgB,eAAe,IAAI,UAAU,EAAE,CAG9C;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAGhE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,EAAE,CASzD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAOjE"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React hooks for consuming trace data.
|
|
3
|
+
*
|
|
4
|
+
* Uses useSyncExternalStore for tear-free reads from the TraceStore.
|
|
5
|
+
* All hooks return STABLE references — derived data is computed in
|
|
6
|
+
* useMemo to prevent infinite render loops.
|
|
7
|
+
*
|
|
8
|
+
* No Effect dependency — works with any React 18+ app.
|
|
9
|
+
*/
|
|
10
|
+
import { useMemo, useSyncExternalStore } from "react";
|
|
11
|
+
import { getTraceStore } from "./store.js";
|
|
12
|
+
/** Stable empty array to avoid creating new references */
|
|
13
|
+
const EMPTY_STEPS = [];
|
|
14
|
+
/**
|
|
15
|
+
* Get the stable Map snapshot from the store.
|
|
16
|
+
* This is the foundation for all hooks — the Map reference only changes
|
|
17
|
+
* when the store is actually updated via dispatch().
|
|
18
|
+
*/
|
|
19
|
+
function useSnapshot() {
|
|
20
|
+
const store = getTraceStore();
|
|
21
|
+
return useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Subscribe to all active traces.
|
|
25
|
+
* Returns traces sorted by startedAt descending.
|
|
26
|
+
*/
|
|
27
|
+
export function useActiveTraces() {
|
|
28
|
+
const snapshot = useSnapshot();
|
|
29
|
+
return useMemo(() => Array.from(snapshot.values()).toSorted((a, b) => b.startedAt - a.startedAt), [snapshot]);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Subscribe to a single trace by ID.
|
|
33
|
+
* Returns undefined if the trace doesn't exist (yet).
|
|
34
|
+
*/
|
|
35
|
+
export function useTrace(traceId) {
|
|
36
|
+
const snapshot = useSnapshot();
|
|
37
|
+
return useMemo(() => snapshot.get(traceId), [snapshot, traceId]);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get the step spans (ui.step=true) for a trace, in chronological order.
|
|
41
|
+
* These are the user-visible processing stages.
|
|
42
|
+
*/
|
|
43
|
+
export function useTraceSteps(traceId) {
|
|
44
|
+
const snapshot = useSnapshot();
|
|
45
|
+
return useMemo(() => {
|
|
46
|
+
const trace = snapshot.get(traceId);
|
|
47
|
+
if (!trace)
|
|
48
|
+
return EMPTY_STEPS;
|
|
49
|
+
const steps = Array.from(trace.spans.values()).filter((s) => s.isStep);
|
|
50
|
+
if (steps.length === 0)
|
|
51
|
+
return EMPTY_STEPS;
|
|
52
|
+
return steps.toSorted((a, b) => a.startedAt - b.startedAt);
|
|
53
|
+
}, [snapshot, traceId]);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Get the root span tree for a trace.
|
|
57
|
+
* Returns the root SpanNode with nested children.
|
|
58
|
+
*/
|
|
59
|
+
export function useSpanTree(traceId) {
|
|
60
|
+
const snapshot = useSnapshot();
|
|
61
|
+
return useMemo(() => {
|
|
62
|
+
const trace = snapshot.get(traceId);
|
|
63
|
+
if (!trace || !trace.rootSpanId)
|
|
64
|
+
return undefined;
|
|
65
|
+
return trace.spans.get(trace.rootSpanId);
|
|
66
|
+
}, [snapshot, traceId]);
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../react/hooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAItD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,0DAA0D;AAC1D,MAAM,WAAW,GAAe,EAAE,CAAC;AAEnC;;;;GAIG;AACH,SAAS,WAAW;IAChB,MAAM,KAAK,GAAG,aAAa,EAAE,CAAC;IAC9B,OAAO,oBAAoB,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;AACvF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe;IAC3B,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;IAC/B,OAAO,OAAO,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;AAClH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,OAAe;IACpC,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;IAC/B,OAAO,OAAO,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;AACrE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,OAAe;IACzC,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;IAC/B,OAAO,OAAO,CAAC,GAAG,EAAE;QAChB,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACpC,IAAI,CAAC,KAAK;YAAE,OAAO,WAAW,CAAC;QAC/B,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QACvE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,WAAW,CAAC;QAC3C,OAAO,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC;IAC/D,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;AAC5B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,OAAe;IACvC,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;IAC/B,OAAO,OAAO,CAAC,GAAG,EAAE;QAChB,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACpC,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,UAAU;YAAE,OAAO,SAAS,CAAC;QAClD,OAAO,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAC7C,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;AAC5B,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* live-traces/react
|
|
3
|
+
*
|
|
4
|
+
* React store and hooks for consuming trace events.
|
|
5
|
+
* Zero Effect dependency — works with any React 18+ app.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```tsx
|
|
9
|
+
* import { useActiveTraces, useTrace, useTraceSteps } from "live-traces/react"
|
|
10
|
+
*
|
|
11
|
+
* function ActivityPanel() {
|
|
12
|
+
* const traces = useActiveTraces()
|
|
13
|
+
* return traces.map(t => <TraceCard key={t.traceId} trace={t} />)
|
|
14
|
+
* }
|
|
15
|
+
*
|
|
16
|
+
* function TraceCard({ trace }: { trace: TraceState }) {
|
|
17
|
+
* const steps = useTraceSteps(trace.traceId)
|
|
18
|
+
* return steps.map(s => <StepRow key={s.spanId} step={s} />)
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export { TraceStore, getTraceStore } from "./store.js";
|
|
23
|
+
export type { TraceState, SpanNode, SpanEventEntry, TraceStatus } from "./store.js";
|
|
24
|
+
export { useActiveTraces, useTrace, useTraceSteps, useSpanTree } from "./hooks.js";
|
|
25
|
+
export { LIVE_TRACE_PROVIDER, LIVE_TRACE_SCOPE_TYPE, LIVE_TRACE_SCOPE_ID } from "../src/types.js";
|
|
26
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../react/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACvD,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAGpF,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEnF,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* live-traces/react
|
|
3
|
+
*
|
|
4
|
+
* React store and hooks for consuming trace events.
|
|
5
|
+
* Zero Effect dependency — works with any React 18+ app.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```tsx
|
|
9
|
+
* import { useActiveTraces, useTrace, useTraceSteps } from "live-traces/react"
|
|
10
|
+
*
|
|
11
|
+
* function ActivityPanel() {
|
|
12
|
+
* const traces = useActiveTraces()
|
|
13
|
+
* return traces.map(t => <TraceCard key={t.traceId} trace={t} />)
|
|
14
|
+
* }
|
|
15
|
+
*
|
|
16
|
+
* function TraceCard({ trace }: { trace: TraceState }) {
|
|
17
|
+
* const steps = useTraceSteps(trace.traceId)
|
|
18
|
+
* return steps.map(s => <StepRow key={s.spanId} step={s} />)
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
// Store
|
|
23
|
+
export { TraceStore, getTraceStore } from "./store.js";
|
|
24
|
+
// Hooks
|
|
25
|
+
export { useActiveTraces, useTrace, useTraceSteps, useSpanTree } from "./hooks.js";
|
|
26
|
+
export { LIVE_TRACE_PROVIDER, LIVE_TRACE_SCOPE_TYPE, LIVE_TRACE_SCOPE_ID } from "../src/types.js";
|
|
27
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../react/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,QAAQ;AACR,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAGvD,QAAQ;AACR,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEnF,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trace Store — Reactive store for live trace events.
|
|
3
|
+
*
|
|
4
|
+
* Reduces a stream of TraceEvents into a tree of SpanNodes per trace.
|
|
5
|
+
* Framework-agnostic core — uses useSyncExternalStore for React binding.
|
|
6
|
+
*
|
|
7
|
+
* No Effect dependency. Consumes plain TraceEvent JSON from any source.
|
|
8
|
+
*/
|
|
9
|
+
import type { TraceEvent, TraceScope } from "../src/types.js";
|
|
10
|
+
export type TraceStatus = "running" | "completed" | "failed";
|
|
11
|
+
export interface SpanEventEntry {
|
|
12
|
+
readonly name: string;
|
|
13
|
+
readonly level?: string;
|
|
14
|
+
readonly attributes?: Record<string, unknown>;
|
|
15
|
+
readonly timestamp: number;
|
|
16
|
+
}
|
|
17
|
+
export interface SpanNode {
|
|
18
|
+
readonly spanId: string;
|
|
19
|
+
readonly parentSpanId?: string;
|
|
20
|
+
readonly name: string;
|
|
21
|
+
readonly status: "running" | "ok" | "error";
|
|
22
|
+
readonly attributes: Record<string, unknown>;
|
|
23
|
+
readonly events: SpanEventEntry[];
|
|
24
|
+
readonly children: SpanNode[];
|
|
25
|
+
readonly startedAt: number;
|
|
26
|
+
readonly durationMs?: number;
|
|
27
|
+
/** True if this span has ui.step attribute */
|
|
28
|
+
readonly isStep: boolean;
|
|
29
|
+
}
|
|
30
|
+
export interface TraceState {
|
|
31
|
+
readonly traceId: string;
|
|
32
|
+
readonly label: string;
|
|
33
|
+
readonly scope: TraceScope;
|
|
34
|
+
readonly status: TraceStatus;
|
|
35
|
+
readonly rootSpanId?: string;
|
|
36
|
+
readonly spans: Map<string, SpanNode>;
|
|
37
|
+
readonly startedAt: number;
|
|
38
|
+
readonly completedAt?: number;
|
|
39
|
+
readonly durationMs?: number;
|
|
40
|
+
readonly error?: string;
|
|
41
|
+
readonly updatedAt: number;
|
|
42
|
+
}
|
|
43
|
+
type Listener = () => void;
|
|
44
|
+
export declare class TraceStore {
|
|
45
|
+
private traces;
|
|
46
|
+
private listeners;
|
|
47
|
+
private cleanupTimer;
|
|
48
|
+
constructor();
|
|
49
|
+
/** Subscribe to store changes (for useSyncExternalStore) */
|
|
50
|
+
subscribe: (listener: Listener) => (() => void);
|
|
51
|
+
/** Get snapshot (for useSyncExternalStore) */
|
|
52
|
+
getSnapshot: () => Map<string, TraceState>;
|
|
53
|
+
/** Dispatch a single trace event */
|
|
54
|
+
dispatch(event: TraceEvent): void;
|
|
55
|
+
/** Dispatch a batch of events */
|
|
56
|
+
dispatchBatch(events: TraceEvent[]): void;
|
|
57
|
+
/** Get a single trace */
|
|
58
|
+
getTrace(traceId: string): TraceState | undefined;
|
|
59
|
+
/** Get all traces as array, sorted by startedAt descending */
|
|
60
|
+
getAllTraces(): TraceState[];
|
|
61
|
+
/** Get step spans (ui.step=true) for a trace, in order */
|
|
62
|
+
getSteps(traceId: string): SpanNode[];
|
|
63
|
+
/** Build the span tree for a trace */
|
|
64
|
+
getSpanTree(traceId: string): SpanNode | undefined;
|
|
65
|
+
/** Destroy the store (stop cleanup timer) */
|
|
66
|
+
destroy(): void;
|
|
67
|
+
private handleTraceStart;
|
|
68
|
+
private handleSpanStart;
|
|
69
|
+
private handleSpanEnd;
|
|
70
|
+
private handleSpanEvent;
|
|
71
|
+
private handleTraceEnd;
|
|
72
|
+
private cleanup;
|
|
73
|
+
private notify;
|
|
74
|
+
}
|
|
75
|
+
export declare function getTraceStore(): TraceStore;
|
|
76
|
+
export {};
|
|
77
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../react/store.ts"],"names":[],"mappings":"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"}
|