npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

livetrace

v0.1.2

Published

Real-time Effect span streaming to frontend UIs. Stream traces from any backend to React with zero overhead.

Readme

livetrace

Real-time Effect span streaming to frontend UIs. Stream traces from any backend to React with zero overhead.

npm version License: Apache-2.0 CI

livetrace turns the Effect 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.

  • Drop-in Tracer decorator. Composes with @effect/opentelemetry. Both OTel and live traces emit in parallel.
  • Wire format is plain JSON. Backends in Go, Python, Rust can produce events; React consumes them the same way.
  • Pluggable transport. SSE, WebSocket, and @durable-streams/client-backed resumable streams ship in the box. Console for dev.
  • Zero-Effect frontend. livetrace/react is just useSyncExternalStore + a reducer. Works in any React 18+ app.

→ Landing page & live demo: livetrace.necmttn.com


Install

bun add livetrace effect
# or
npm install livetrace effect

React consumers also need react@>=18.

Quick start (Effect backend)

import { Effect, Layer } from "effect";
import {
    LiveTraceLayer,
    TraceSinkLive,
    SSETransportLayer,
    withTrace,
    step,
    liveTraceLogger,
} from "livetrace";
import { SSETransportLayer as SSE } from "livetrace/transports/sse";

// 1. Compose the layer
const TraceLive = LiveTraceLayer.pipe(
    Layer.provide(TraceSinkLive({ flushIntervalMs: 100 })),
    Layer.provide(SSE),
);

// 2. Wrap a workflow
const processDocument = (docId: string) =>
    Effect.gen(function* () {
        yield* Effect.logInfo(`Starting ${docId}`);
        yield* step("Parse")(parsePdf(docId));
        yield* step("Embed")(embedChunks(docId));
        yield* step("Index")(indexVectors(docId));
    }).pipe(
        withTrace({
            traceId: `doc:${docId}`,
            label: "Document processing",
            scope: { type: "user", id: "alice" },
        }),
    );

// 3. Run with the trace layer + the live logger
Effect.runPromise(processDocument("report.pdf").pipe(Effect.provide(TraceLive)));

React frontend

import { useActiveTraces, useTrace, useTraceSteps } from "livetrace/react";

function ActivityPanel() {
    const traces = useActiveTraces();
    return (
        <div>
            {traces.map((t) => (
                <TraceCard key={t.traceId} traceId={t.traceId} />
            ))}
        </div>
    );
}

function TraceCard({ traceId }: { traceId: string }) {
    const trace = useTrace(traceId);
    const steps = useTraceSteps(traceId);
    if (!trace) return null;

    return (
        <div>
            <h3>
                {trace.label} <span>{trace.status}</span>
            </h3>
            <ol>
                {steps.map((s) => (
                    <li key={s.spanId}>
                        {s.name} · {s.status}
                        {s.durationMs != null && ` · ${s.durationMs.toFixed(0)}ms`}
                    </li>
                ))}
            </ol>
        </div>
    );
}

Connect the store to a transport (SSE shown):

import { getTraceStore } from "livetrace/react";
import type { TraceEvent } from "livetrace/types";

const es = new EventSource(`/traces/user/${userId}`);
es.onmessage = (msg) => {
    const batch: TraceEvent[] = JSON.parse(msg.data);
    getTraceStore().dispatchBatch(batch);
};

SSE server (Bun / Node)

import { getSseBroker } from "livetrace/transports/sse";

// Bun's built-in server
Bun.serve({
    fetch(req) {
        const url = new URL(req.url);
        const match = url.pathname.match(/^\/traces\/(team|org|user)\/(.+)$/);
        if (!match) return new Response("not found", { status: 404 });
        const [, type, id] = match;

        return new Response(
            new ReadableStream({
                start(controller) {
                    const unsub = getSseBroker().subscribe(
                        { type: type as "user", id: id! },
                        (events) => {
                            controller.enqueue(
                                new TextEncoder().encode(`data: ${JSON.stringify(events)}\n\n`),
                            );
                        },
                    );
                    req.signal.addEventListener("abort", () => {
                        unsub();
                        controller.close();
                    });
                },
            }),
            {
                headers: {
                    "Content-Type": "text/event-stream",
                    "Cache-Control": "no-cache",
                    Connection: "keep-alive",
                },
            },
        );
    },
});

Composing with OpenTelemetry

LiveTraceLayer wraps the current tracer instead of replacing it. Build OTel outermost (so it sets the tracer first), then livetrace wraps it.

const Env = ServerLive.pipe(
    Layer.provideMerge(ServicesLive),
    Layer.provideMerge(LiveTraceLayer),  // inner: wraps OTel
    Layer.provideMerge(TelemetryLive),   // outer: sets OTel tracer first
);

OTel still receives every span. Live-traces only annotates spans inside withTrace scopes and streams them out - there's no double-export.

Wire format

Events are a discriminated union on _tag:

type TraceEvent = TraceStart | SpanStart | SpanEnd | SpanEvent | TraceEnd;

See src/types.ts for the full schema. The livetrace/types sub-export is dependency-free - any backend can emit these as JSON.

Why?

OpenTelemetry is built for ops dashboards. livetrace 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.

License

Apache-2.0 © Necmettin Karakaya