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

graph-suggestions

v2.0.0

Published

Provides "people you should follow" or other graph-based recommendations

Readme

graph-suggestions

CI npm

Turn connections into “people to follow” recommendations. Supply functions that retrieve a node's neighbors; graph-suggestions explores the local graph and ranks candidates using a finite personalized PageRank calculation. It filters out the starting node and its existing forward connections for you.

The same interface works for people, products, or documents. Your graph stays in your database: the library needs only adjacency lists, with bounded concurrent reads and a per-call cache. Version 2 has no runtime dependencies, supports Promises and callbacks, and includes TypeScript declarations.

Install

npm install graph-suggestions

Version 2 requires Node.js 22 or newer. CommonJS require() and Node.js ESM named imports both work.

A complete recommendation

Save this as example.mjs, then run node example.mjs:

import { suggest } from 'graph-suggestions';

const following = new Map([
  ['Andrew', ['Ben']],
  ['Ben', ['Andrew', 'Chuck']],
  ['Chuck', ['Ben']],
  ['Dennis', ['Andrew']],
]);

const results = await suggest('Andrew', {
  forwardOnly: true,
  forwardConnections: async id => following.get(id) ?? [],
});

console.log(results);
// [ { nodeID: 'Chuck', score: 0.0625 } ]

Andrew already follows Ben. Chuck becomes a candidate because Ben follows Chuck. A score is the probability at that node after the configured number of walk steps; higher ranks first. Scores are not normalized over the filtered results.

Follow connections in both directions

For a directed graph, reverse connections let the walk discover nodes that link to the current node. For example, Dennis follows Andrew even though Andrew does not follow Dennis. Provide both functions to include that information:

const results = await suggest('Andrew', {
  forwardConnections: async id => following.get(id) ?? [],
  reverseConnections: async id => [...following]
    .filter(([, targets]) => targets.includes(id))
    .map(([source]) => source),
});

This snippet uses suggest and following from the complete example. For a symmetric friendship graph, choose forwardOnly: true to avoid reading the same relationships in both directions. On larger graphs, use an indexed reverse-edge lookup instead of scanning every node.

Options

| Option | Default | Meaning | | --- | --- | --- | | forwardConnections(id, callback) | Required | Fetch outgoing node IDs; return a Promise or call callback(error, ids) | | reverseConnections(id, callback) | Required unless forwardOnly | Fetch incoming node IDs using the same contract | | forwardOnly | false | Traverse only outgoing edges | | iterations | 3 | Number of walk steps; non-negative integer | | alpha | 0.5 | Probability of following an edge on each step, between 0 and 1 | | maxResults | 25 | Maximum returned candidates; non-negative integer | | concurrency | 10 | Maximum node lookups in flight; positive integer |

Node IDs must be strings, including IDs returned by fetchers. Empty strings and names such as constructor are valid. Return [] for a node with no connections. Each direction is read once per node per suggest() call; a later call fetches fresh data. This cache is not an atomic snapshot of a changing database.

Bidirectional traversal reads both directions concurrently, so at most 2 * concurrency provider calls may be in flight. A repeated edge acts as a repeated choice in the walk; deduplicate provider results if you want each neighbor to count once.

Callbacks and errors

Existing callback-based providers remain supported:

const { suggest } = require('graph-suggestions');

suggest('Andrew', {
  forwardOnly: true,
  forwardConnections(id, done) {
    const graph = { Andrew: ['Ben'], Ben: ['Chuck'], Chuck: [] };
    done(null, Object.hasOwn(graph, id) ? graph[id] : []);
  },
}, (error, results) => {
  if (error) {
    console.error(error);
    return;
  }
  console.log(results);
});

Invalid configuration throws synchronously. Provider errors, thrown exceptions, rejected promises, and malformed results reject the returned Promise or reach the completion callback once. Already-started reads may finish after a failure; no new reads are scheduled after the failure is observed. A provider must settle its Promise or invoke its callback; the library does not impose a timeout.

Ranking behavior

Each step follows a connection with probability alpha and returns to the starting node with probability 1 - alpha. Dead ends return their walk mass to the starting node. After iterations steps, the starting node, existing outgoing connections, and zero-score entries are removed. Results sort by descending score, then ascending string ID for ties, independent of fetch completion order.

This is a bounded local calculation, not an iteration-until-convergence solver. More steps explore farther, but can read substantially more of the graph. The heap keeps ranking work to O(V log K) for V visited candidates and K results; the traversal cache retains the adjacency lists it reads until the call finishes.

Upgrading from 1.x

Version 2 preserves suggest(id, options, callback) and adds a Promise overload. It requires Node.js 22+, validates options and string IDs, and honors explicit zero values for alpha, iterations, and maxResults by returning no candidates.

Ranking can change: dead-end probability now returns to the starting node, ties are deterministic, and repeated reads within a call use the first fetched adjacency list. Prototype-like IDs no longer collide with internal state. See HISTORY.md for the full release notes.

Development

npm ci
npm test
npm run test:coverage
npm pack --dry-run

The suite retains the original graph examples and adds failure, concurrency, cache, ID, and option regressions, plus comparisons against an independent dense random-walk calculation. CI runs on Node.js 22, 24, and 26. No database is required.

License

MIT.