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

@gingur/auto-graph

v0.3.1

Published

Promise-first dependency graph runner using fluent generics for type chaining

Readme

auto-graph

A promise-first dependency graph runner with fluent generics for type-safe task chaining. Build complex workflows with automatic parallelization and intelligent dependency resolution.

Features

  • 🚀 Automatic Parallelization - Independent tasks run concurrently
  • 🔗 Smart Dependency Resolution - Tasks execute as soon as their dependencies are ready
  • 🎯 Fluent Generics - Type information flows through the chain for full type safety
  • 💾 Incremental Computation - Cache results to skip expensive recomputation
  • 🔄 Immutable API - Each .add() returns a new graph instance
  • Promise-First - Native async/await support throughout

Installation

npm install @gingur/auto-graph

Quick Start

import { AutoGraph } from '@gingur/auto-graph';

const graph = new AutoGraph()
  .add('fetchUser', async () => {
    const response = await fetch('/api/user');
    return response.json();
  })
  .add('fetchPosts', async () => {
    const response = await fetch('/api/posts');
    return response.json();
  })
  .add('enrichPosts', ['fetchUser', 'fetchPosts'], ({ fetchUser, fetchPosts }) => {
    return fetchPosts.map((post) => ({
      ...post,
      author: fetchUser.name,
    }));
  });

const results = await graph.run();
console.log(results.enrichPosts);

API

new AutoGraph()

Creates a new empty graph.

.add(name, fn) or .add(name, deps, fn)

Add a task to the graph. Returns a new graph instance with the task added.

// No dependencies
.add('task1', () => 42)

// With dependencies
.add('task2', ['task1'], ({ task1 }) => task1 * 2)

.run(cache?)

Execute the graph and return all task results.

const results = await graph.run();

// With cache to skip expensive tasks
const results = await graph.run({ expensiveTask: cachedValue });

Fluent Generics & Type Safety

The library uses fluent generics to maintain type information as you chain .add() calls. Each task's return type is automatically inferred and available to dependent tasks:

const graph = new AutoGraph()
  .add('num', () => 42) // Returns number
  .add('str', () => 'hello') // Returns string
  .add('combined', ['num', 'str'], ({ num, str }) => {
    // num is typed as number, str is typed as string
    return `${str}: ${num}`;
  });

⚠️ Gotcha: Breaking the Chain

You must chain .add() calls directly to maintain type information. Breaking the chain loses type safety:

// ❌ BAD - Type information is lost
let graph = new AutoGraph();
graph = graph.add('a', () => 1);
graph = graph.add('b', ['a'], ({ a }) => a + 1); // 'a' is not typed correctly!

// ✅ GOOD - Types flow through the chain
const graph = new AutoGraph().add('a', () => 1).add('b', ['a'], ({ a }) => a + 1); // 'a' is correctly typed as number

⚠️ Gotcha: Storing Intermediate Graphs

Storing intermediate graphs in variables can cause type issues:

// ❌ BAD - baseGraph doesn't know about tasks added later
const baseGraph = new AutoGraph().add('a', () => 1);

const extendedGraph = baseGraph.add('b', ['a'], ({ a }) => a + 1);

// This won't work as expected:
const broken = baseGraph.add('c', ['b'], ({ b }) => b + 1); // Error: 'b' doesn't exist on baseGraph!

// ✅ GOOD - Chain from the extended graph
const working = extendedGraph.add('c', ['b'], ({ b }) => b + 1); // Works correctly

Examples

Parallel Execution

Independent tasks run in parallel automatically:

const graph = new AutoGraph()
  .add('fetchUsers', async () => await db.users.findMany())
  .add('fetchProducts', async () => await db.products.findMany())
  .add('combine', ['fetchUsers', 'fetchProducts'], ({ fetchUsers, fetchProducts }) => ({
    users: fetchUsers,
    products: fetchProducts,
  }));

await graph.run(); // fetchUsers and fetchProducts run in parallel

Build Pipeline

const pipeline = new AutoGraph()
  .add('lint', async () => {
    await exec('eslint .');
    return { errors: 0 };
  })
  .add('typeCheck', async () => {
    await exec('tsc --noEmit');
    return { errors: 0 };
  })
  .add('compile', ['lint', 'typeCheck'], async () => {
    await exec('tsc');
    return { output: 'dist/' };
  })
  .add('test', ['compile'], async () => {
    await exec('npm test');
    return { passed: true };
  })
  .add('bundle', ['compile'], async () => {
    await exec('webpack');
    return { file: 'dist/bundle.js' };
  });

await pipeline.run(); // test and bundle run in parallel after compile

Incremental Computation

Skip expensive recomputation by providing cached results:

const graph = new AutoGraph()
  .add('expensiveTask', async () => {
    await heavyProcessing();
    return { result: 'data' };
  })
  .add('processResult', ['expensiveTask'], ({ expensiveTask }) => {
    return expensiveTask.result.toUpperCase();
  });

// First run
const firstRun = await graph.run();

// Second run - reuse expensive computation
const cache = { expensiveTask: firstRun.expensiveTask };
const secondRun = await graph.run(cache); // expensiveTask is not re-executed

Complex Dependencies

const graph = new AutoGraph()
  .add('a', () => 1)
  .add('b', () => 2)
  .add('c', () => 3)
  .add('d', ['a'], ({ a }) => a * 2)
  .add('e', ['b'], ({ b }) => b * 2)
  .add('f', ['c'], ({ c }) => c * 2)
  .add('g', ['d', 'e'], ({ d, e }) => d + e)
  .add('h', ['e', 'f'], ({ e, f }) => e + f)
  .add('result', ['g', 'h'], ({ g, h }) => g * h);

const results = await graph.run();
console.log(results.result); // 60

Error Handling

Build-time validation catches common mistakes:

// Duplicate task names
new AutoGraph().add('task1', () => 1).add('task1', () => 2); // Throws: Task 'task1' already exists

// Missing dependencies
new AutoGraph().add('task1', () => 1).add('task2', ['missing'], () => 2); // Throws: depends on missing task 'missing'

Runtime errors propagate normally:

const graph = new AutoGraph().add('task1', () => {
  throw new Error('Failed');
});

try {
  await graph.run();
} catch (err) {
  console.error('Task failed:', err);
}

Performance

  • Parallel Execution: Independent tasks run concurrently
  • Eager Execution: Tasks start immediately when dependencies are ready
  • No Polling: Promise-based coordination, no busy-waiting

Requirements

  • Node.js >= 22

Inspiration

This project was inspired by async.auto from the Async.js library. Thanks to all contributors for pioneering these concepts and helping improve developer experience.

License

MIT

Author

Troy Rhinehart

Links