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

@hikae/jev-algorithms

v0.3.0

Published

Runtime-agnostic algorithms built on TypeSafe's Jev structured-evaluation model.

Readme

jev-algorithms

Gallery

sort selectTopK findFirstTrue clusterByRelation

Install

npm install @hikae/jev-algorithms

Quick start

import { createTypeSafeClient, sort } from "@hikae/jev-algorithms";

const jev = createTypeSafeClient({ apiKey: process.env.TYPESAFE_API_KEY! });

const inbox = [
  { id: "1", from: "[email protected]", subject: "Contract", unread: true, body: "Please sign by Friday." },
  { id: "2", from: "[email protected]", subject: "Weekly digest", unread: false, body: "This week in..." },
];

const ordered = await sort(jev, inbox, {
  task: "for replying first",
  stateOf: (mail) => ({
    from: mail.from,
    subject: mail.subject,
    unread: mail.unread,
    preview: mail.body.slice(0, 300),
  }),
});
// [Contract mail, Weekly digest]

The client contract

interface JevClient {
  evaluate(input: { state: JevState; questions: JevQuestions }): Promise<JevAnswers>;
}

| Adapter | Factory | Notes | | --- | --- | --- | | TypeSafe API | createTypeSafeClient({ apiKey, model?, baseUrl?, fetch? }) | Runtime-agnostic default. Keep the key on the server. | | Workers AI | createWorkersAiClient(binding, model?) | Pass env.AI. Structural, no Cloudflare types required. | | In memory | createMemoryClient(responder) | Test double; records every calls evaluation. |

Question builders (noul, choice, score) and answer readers (readNoul, readChoice, readScore) are exported for building your own algorithms on the same contract.

Algorithms

Every algorithm states its request cost in terms of n items. A request carries up to 40 questions, so "one request" is rarely one comparison.

Sorting and selection

| Function | What it does | Requests | | --- | --- | --- | | sort(client, items, options) | Total order from pairwise "which ranks higher?" answers | O((n/40) log n) | | sortWith(items, compare) | Same, with an injected comparator (no client) | O((n/40) log n) | | selectTopK(client, items, k, options) | Top-k, ordered, via quickselect | O(n/40 + (k/40) log k) | | selectTopKWith(items, k, compare) | Same, with an injected comparator | O(n/40 + (k/40) log k) | | createPairComparator(client, items, options) | Build the comparator to plug Jev into any sort you own | — |

The sort is randomized quicksort whose recursion runs level by level. Nodes on a level are disjoint, so a level's comparisons are batched into ⌈n/40⌉ requests at DEFAULT_MAX_PAIRS_PER_REQUEST pairs each. The whole order costs O((n/40) log n) requests instead of n log n individual comparisons. selectTopK skips the side that cannot contain the k-th item.

Threshold search

const cutoff = await findFirstTrue(jev, emailsByRecency, {
  stateOf: (mail) => ({ ageDays: mail.ageDays }),
  instruction: "Is `candidate` older than 30 days?",
});

findFirstTrue binary-searches a monotone predicate in O(log n) calls.

Ranking from noisy comparisons

Pairwise answers can cycle (A > B > C > A). elo and bradleyTerry turn a list of Comparisons into one score per id; rankByElo and rankByBradleyTerry order them. winner is "first" | "second" | "draw", named by position so a player called "a" or "b" is never ambiguous.

Clustering

const groups = await clusterByRelation(jev, tickets, {
  relation: "duplicate support ticket",
  stateOf: (t) => ({ subject: t.subject, body: t.body.slice(0, 300) }),
});

Union-find over pairwise equivalence. Every unordered pair is compared once; threshold controls how confident Jev must be to merge, and maxComparisons caps sampling for large inputs.

Classification and scoring

  • classifyChoice(client, state, { instruction, labels, abstainBelow }) — Choice with an abstain band, so uncertain cases can be escalated instead of guessed.
  • booleanDecision(client, state, { instruction, threshold }) — Noul plus a thresholded verdict.
  • rubricScore(client, state, { instruction, levels }) — Score normalized to 0..1, with the nearest level label.

Matching

stableMatching(proposers, receivers, proposerPrefs, receiverPrefs) is a pure Gale-Shapley implementation. buildPreferences(client, choosers, candidates, options) builds each side's preference order with pairwise comparisons first.

Design notes

  • Batch by default. Every algorithm packs up to 40 questions into one evaluate call and references items through a chunk-local index so a shared item travels once.
  • Fail loud, not wrong. If Jev omits an answer, algorithms throw. They never persist a fabricated order. The one exception is up to the caller: classifyChoice can abstain by design.
  • Persistence is yours. Algorithms return orders and scores; storing them (for example, an integer priority column) is a schema decision, not a library one.

Testing and mutation testing

npm test        # build + node:test
npm run typecheck
npm run mutation # build + Stryker

Tests are judged by mutation coverage, not line coverage: a test only earns its place if it kills a mutant. Stryker runs with the command test runner against the built dist and a break threshold of 80.

Two policies keep the signal honest and the suite lean:

  • StringLiteral mutants are excluded. Prompts and error wording are content, not logic; asserting exact prompt text would make the suite brittle without catching real bugs.
  • Survivors include equivalent mutations in union-find and pivot selection, as well as coverage gaps in diagnostics and less common syntax paths. Inspect reports/mutation.json; a passing threshold does not mean every survivor is equivalent or that Jev's predictions are accurate.

License

MIT