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

jevchain

v0.1.0

Published

Typed decision graphs for Jev, TypeSafe's classification model.

Readme

jevchain

Chains of decisions for Jev, TypeSafe's classification model.

Jev doesn't generate text. You give it some state and typed questions, and it answers with calibrated probabilities in tens of milliseconds. jevchain composes those answers into graphs: route on a choice, gate on a probability, fan out in parallel, cascade from cheap to thorough. Every run leaves a complete, serializable trace of every decision it made.

npm install jevchain
export TYPESAFE_API_KEY=...
import { createJev, route, gate, choice, noul, emit } from "jevchain";

const desk = route("front-desk", {
  ask: choice("Which team should handle this ticket?", {
    repair: "an ordinary mechanical or electrical fault",
    billing: "payments, refunds, warranties",
    paranormal: "behaviour no appliance can do",
  }),
  lowConfidence: { below: 0.4, then: emit("a human will read this. probably dave.") },
  branches: {
    repair: emit("technician booked"),
    billing: emit("forwarded to billing"),
    paranormal: gate("anyone-in-danger", {
      ask: noul("Is anyone in physical danger right now?"),
      pass: { min: 0.5 },
      then: emit("leave the house. sending a priest."),
      otherwise: emit("booked: one (1) exorcist"),
    }),
  },
});

const jev = createJev();
const { output, trace } = await jev.run(desk, "My toaster whispers my name at 3am.");
// output: "booked: one (1) exorcist"
// trace.spans[0].decision.summary:
//   'Went to "paranormal" with 98%, a landslide over "repair" at 2% (confidence 0.97).'

Primitives

| Builder | What it does | Output type | | --- | --- | --- | | ask(id, { questions }) | One Jev call, any number of typed questions | { [key]: Answer }, with choice labels as literal unions | | route(id, { ask: choice(...), branches }) | Branch on a choice. Every label needs a branch, checked at compile time | union of branch outputs | | gate(id, { ask, pass, then, otherwise?, unsure? }) | Continue if a probability or score clears a bar. No otherwise means halt | union of taken paths | | parallel(id, { branches, join? }) | Run branches concurrently, then join | { [branch]: output } or your join's result | | cascade(id, { tiers, fallback }) | Try cheap tiers first, escalate on low confidence, then fall back to anything (an LLM, a human) | { resolvedBy, tier, answer } \| { resolvedBy: "fallback", output } | | step(id, fn) | Your code: transform, fetch, call a tool | whatever fn returns | | emit(value) | A constant or {{input.x}} template. Good leaves | the value | | chain(id, ...nodes) | Sequence, each output feeding the next input | last node's output |

Questions are TypeSafe's three primitives: choice(instructions, labels), score(instructions, levels), and noul(instructions) (a yes/no that returns p(yes)).

Types do the boring checking

const read = ask("read", { questions: { mood: choice("Mood?", ["cursed", "blessed"]) } });
type Mood = OutputOf<typeof read>["mood"]["choice"]; // "cursed" | "blessed"

route("r", {
  ask: choice("?", ["billing", "bug", "vibes"]),
  branches: { billing: a, bug: b }, // ✗ Property 'vibes' is missing
});

chain("c", step("n", () => 1), step("s", (x: string) => x)); // ✗ number isn't string

Running

const jev = createJev({
  apiKey,                // default: process.env.TYPESAFE_API_KEY
  model: "jev-latest",   // or pin "jev-1.13.0"
  timeoutMs: 10_000,     // per attempt
  retry: { maxRetries: 2 },  // 408/409/429/5xx/529, exponential backoff, honors retry-after
  maxConcurrency: 8,     // in-flight HTTP requests
  batch: true,           // merge same-state asks issued in the same tick into one request
});

const result = await jev.run(chain, input, { signal, timeoutMs: 5_000 });
// result.status: "ok" | "halted" | "error" | "aborted"; run() doesn't throw for runtime failures

Streaming

const s = jev.stream(chain, input);
for await (const event of s) ui.apply(event); // run:start, span:start, jev:call, decision, retry, log, span:end, run:end
const { trace } = await s.result;

reduceTrace(trace, event) folds events into a trace. The runtime builds its own trace with this same function, so a live UI and the final trace always agree.

Traces

A trace is plain JSON, with a span per node visited:

  • inputs and outputs
  • every Jev call: state, questions, full probability distributions, model, tokens, cost, latency, attempts, request id, batch info
  • the decision, with every candidate edge and its deciding number (taken or not), the threshold, confidence, and a templated plain-English summary
  • retries, logs, errors

Helpers: explainTrace, diffTraces (where did two runs diverge?), graphOf + overlayTrace (draw it).

JSON

const doc = toJSON(chain, { name: "Haunted desk" });          // functions become { $ref }
const again = fromJSON(doc, { handlers: { lookup: myLookupFn } });
const code = toTypeScript(doc);                                 // back to builder code

Errors

Everything extends JevChainError with a stable code: JevAuthError, JevValidationError, JevRateLimitError (retryAfterMs), JevServerError, JevTimeoutError, JevConnectionError, JevAbortError, JevResponseError, ChainConfigError (with a list of issues), and NodeError (with the failing nodeId).

License

MIT