Files

@ruvector/rudag

npm version npm downloads license TypeScript WebAssembly Node.js Bundle Size

Smart task scheduling with self-learning optimization — powered by Rust/WASM

"What order should I run these tasks? Which one is slowing everything down?"

rudag answers these questions instantly. It's a Directed Acyclic Graph (DAG) library that helps you manage dependencies, find bottlenecks, and optimize execution — all with self-learning intelligence that gets smarter over time.

Installation

npm install @ruvector/rudag
// 3 lines to find your bottleneck
const dag = new RuDag({ name: 'my-pipeline' });
await dag.init();
const { path, cost } = dag.criticalPath();  // → "Task A → Task C takes 8 seconds"

The Problem

You have tasks with dependencies. Task C needs A and B to finish first:

   ┌─────────────┐     ┌─────────────┐
   │ Task A: 5s  │     │ Task B: 3s  │
   └──────┬──────┘     └──────┬──────┘
          │                   │
          └────────┬──────────┘
                   ▼
          ┌─────────────┐
          │ Task C: 2s  │
          └──────┬──────┘
                 ▼
          ┌─────────────┐
          │ Task D: 1s  │
          └─────────────┘

You need answers:

Question rudag Method Answer
What order to run tasks? topoSort() [A, B, C, D]
How long will it all take? criticalPath() A→C→D = 8s (B runs parallel)
What should I optimize? attention() Task A scores highest — fix that first!

Where You'll Use This

Use Case Example
🗄️ Query Optimization Find which table scan is the bottleneck
🔨 Build Systems Compile dependencies in the right order
📦 Package Managers Resolve and install dependencies
🔄 CI/CD Pipelines Orchestrate test → build → deploy
📊 ETL Pipelines Schedule extract → transform → load
🎮 Game AI Plan action sequences with prerequisites
📋 Workflow Engines Manage approval chains and state machines

Why rudag?

Without rudag With rudag
Write graph algorithms from scratch One-liner: dag.criticalPath()
Slow JavaScript loops Rust/WASM - 10-100x faster
Data lost on page refresh Auto-saves to IndexedDB
Hard to find bottlenecks Attention scores highlight important nodes
Complex setup npm install and go

Comparison with Alternatives

Feature rudag graphlib dagre d3-dag
Performance WASM (10-100x faster) JS JS JS
Critical Path Built-in Manual Manual Manual
Attention/Scoring ML-inspired
Cycle Detection Automatic
Topological Sort
Persistence IndexedDB + Files
Browser + Node.js Both Both Both ⚠️ Browser
TypeScript Native ⚠️ @types ⚠️ @types Native
Bundle Size ~50KB (WASM) ~15KB ~30KB ~20KB
Self-Learning
Serialization JSON + Binary JSON JSON
CLI Tool

When to Use What

Use Case Recommendation
Query optimization / Task scheduling rudag - Critical path + attention scoring
Graph visualization / Layout dagre - Designed for layout algorithms
Simple dependency tracking graphlib - Lightweight, no WASM overhead
D3 integration d3-dag - Native D3 compatibility
Large graphs (10k+ nodes) rudag - WASM performance advantage
Offline-first apps rudag - Built-in persistence

Key Capabilities

🧠 Self-Learning Optimization

rudag uses ML-inspired attention mechanisms to learn which nodes matter most. The more you use it, the smarter it gets at identifying bottlenecks and suggesting optimizations.

// Get importance scores for each node
const scores = dag.attention(AttentionMechanism.CRITICAL_PATH);
// Nodes on the critical path score higher → optimize these first!

WASM-Accelerated Performance

Core algorithms run in Rust compiled to WebAssembly - the same technology powering Figma, Google Earth, and AutoCAD in the browser. Get native-like speed without leaving JavaScript.

🔄 Automatic Cycle Detection

DAGs can't have cycles by definition. rudag automatically prevents invalid edges that would create loops:

dag.addEdge(a, b);  // ✅ OK
dag.addEdge(b, c);  // ✅ OK
dag.addEdge(c, a);  // ❌ Returns false - would create cycle!

📊 Critical Path Analysis

Instantly find the longest path through your graph - the sequence of tasks that determines total execution time. This is what you need to optimize first.

💾 Zero-Config Persistence

Your DAGs automatically save to IndexedDB in browsers or files in Node.js. No database setup, no configuration - just works.

🔌 Serialization & Interop

Export to JSON (human-readable) or binary (compact, fast). Share DAGs between services, store in databases, or send over the network.

Quick Start

import { RuDag, DagOperator } from '@ruvector/rudag';

// Create a DAG (auto-persists to IndexedDB in browser)
const dag = new RuDag({ name: 'my-query' });
await dag.init();

// Add nodes with operators and costs
const scan = dag.addNode(DagOperator.SCAN, 100);      // Read table: 100ms
const filter = dag.addNode(DagOperator.FILTER, 10);   // Filter rows: 10ms
const project = dag.addNode(DagOperator.PROJECT, 5);  // Select columns: 5ms

// Connect nodes (creates edges)
dag.addEdge(scan, filter);
dag.addEdge(filter, project);

// Analyze the DAG
const topo = dag.topoSort();           // [0, 1, 2] - execution order
const { path, cost } = dag.criticalPath();  // Slowest path: 115ms

console.log(`Critical path: ${path.join(' → ')} (${cost}ms)`);
// Output: Critical path: 0 → 1 → 2 (115ms)

// Cleanup when done
dag.dispose();

Features

Core Operations

Feature Description
addNode(operator, cost) Add a node with operator type and execution cost
addEdge(from, to) Connect nodes (rejects cycles automatically)
topoSort() Get nodes in topological order
criticalPath() Find the longest/most expensive path
attention(mechanism) Score nodes by importance

Operators

import { DagOperator } from '@ruvector/rudag';

DagOperator.SCAN       // 0 - Table scan
DagOperator.FILTER     // 1 - WHERE clause
DagOperator.PROJECT    // 2 - SELECT columns
DagOperator.JOIN       // 3 - Table join
DagOperator.AGGREGATE  // 4 - GROUP BY
DagOperator.SORT       // 5 - ORDER BY
DagOperator.LIMIT      // 6 - LIMIT/TOP
DagOperator.UNION      // 7 - UNION
DagOperator.CUSTOM     // 255 - Custom operator

Attention Mechanisms

Score nodes by their importance using ML-inspired attention:

import { AttentionMechanism } from '@ruvector/rudag';

// Score by position in execution order
const topoScores = dag.attention(AttentionMechanism.TOPOLOGICAL);

// Score by distance from critical path (most useful)
const criticalScores = dag.attention(AttentionMechanism.CRITICAL_PATH);

// Equal scores for all nodes
const uniformScores = dag.attention(AttentionMechanism.UNIFORM);

Persistence

Browser (IndexedDB) - Automatic:

const dag = new RuDag({ name: 'my-dag' }); // Auto-saves to IndexedDB
await dag.init();

// Later: reload from storage
const loaded = await RuDag.load('dag-123456-abc');

// List all stored DAGs
const allDags = await RuDag.listStored();

// Delete a DAG
await RuDag.deleteStored('dag-123456-abc');

Node.js (File System):

import { NodeDagManager } from '@ruvector/rudag/node';

const manager = new NodeDagManager('./.rudag');
await manager.init();

const dag = await manager.createDag('pipeline');
// ... build the DAG ...
await manager.saveDag(dag);

// Later: reload
const loaded = await manager.loadDag('pipeline-id');

Disable Persistence:

const dag = new RuDag({ storage: null, autoSave: false });

Serialization

// Binary (compact, fast)
const bytes = dag.toBytes();
const restored = await RuDag.fromBytes(bytes);

// JSON (human-readable)
const json = dag.toJSON();
const restored = await RuDag.fromJSON(json);

CLI Tool

After installing globally or in your project:

# If installed globally: npm install -g @ruvector/rudag
rudag create my-query > my-query.dag

# Or run directly with npx (no install needed)
npx @ruvector/rudag create my-query > my-query.dag

Commands

# Create a sample DAG
rudag create my-query > my-query.dag

# Show DAG information
rudag info my-query.dag

# Print topological sort
rudag topo my-query.dag

# Find critical path
rudag critical my-query.dag

# Compute attention scores
rudag attention my-query.dag critical

# Convert between formats
rudag convert my-query.dag my-query.json
rudag convert my-query.json my-query.dag

# JSON output
rudag info my-query.dag --json

# Help
rudag help

Use Cases

1. SQL Query Optimizer

Build a query plan DAG and find the critical path:

import { RuDag, DagOperator } from '@ruvector/rudag';

async function analyzeQuery(sql: string) {
  const dag = new RuDag({ name: sql.slice(0, 50) });
  await dag.init();

  // Parse SQL and build DAG (simplified example)
  const scan1 = dag.addNode(DagOperator.SCAN, estimateScanCost('users'));
  const scan2 = dag.addNode(DagOperator.SCAN, estimateScanCost('orders'));
  const join = dag.addNode(DagOperator.JOIN, estimateJoinCost(1000, 5000));
  const filter = dag.addNode(DagOperator.FILTER, 10);
  const project = dag.addNode(DagOperator.PROJECT, 5);

  dag.addEdge(scan1, join);
  dag.addEdge(scan2, join);
  dag.addEdge(join, filter);
  dag.addEdge(filter, project);

  const { path, cost } = dag.criticalPath();
  console.log(`Estimated query time: ${cost}ms`);
  console.log(`Bottleneck: node ${path[0]}`); // Usually the scan or join

  return dag;
}

2. Task Scheduler

Schedule tasks respecting dependencies:

import { RuDag, DagOperator } from '@ruvector/rudag';

interface Task {
  id: string;
  duration: number;
  dependencies: string[];
}

async function scheduleTasks(tasks: Task[]) {
  const dag = new RuDag({ name: 'task-schedule', storage: null });
  await dag.init();

  const taskToNode = new Map<string, number>();

  // Add all tasks as nodes
  for (const task of tasks) {
    const nodeId = dag.addNode(DagOperator.CUSTOM, task.duration);
    taskToNode.set(task.id, nodeId);
  }

  // Add dependencies as edges
  for (const task of tasks) {
    const toNode = taskToNode.get(task.id)!;
    for (const dep of task.dependencies) {
      const fromNode = taskToNode.get(dep)!;
      dag.addEdge(fromNode, toNode);
    }
  }

  // Get execution order
  const order = dag.topoSort();
  const schedule = order.map(nodeId => {
    const task = tasks.find(t => taskToNode.get(t.id) === nodeId)!;
    return task.id;
  });

  // Total time (critical path)
  const { cost } = dag.criticalPath();
  console.log(`Total time with parallelization: ${cost}ms`);

  dag.dispose();
  return schedule;
}

3. Build System

import { RuDag, DagOperator } from '@ruvector/rudag';

const dag = new RuDag({ name: 'build' });
await dag.init();

// Define build steps
const compile = dag.addNode(DagOperator.CUSTOM, 5000);   // 5s
const test = dag.addNode(DagOperator.CUSTOM, 10000);     // 10s
const lint = dag.addNode(DagOperator.CUSTOM, 2000);      // 2s
const bundle = dag.addNode(DagOperator.CUSTOM, 3000);    // 3s
const deploy = dag.addNode(DagOperator.CUSTOM, 1000);    // 1s

dag.addEdge(compile, test);
dag.addEdge(compile, lint);
dag.addEdge(test, bundle);
dag.addEdge(lint, bundle);
dag.addEdge(bundle, deploy);

// Parallel execution order
const order = dag.topoSort(); // [compile, test|lint (parallel), bundle, deploy]

// Critical path: compile → test → bundle → deploy = 19s
const { cost } = dag.criticalPath();
console.log(`Minimum build time: ${cost}ms`);

4. Data Pipeline (ETL)

import { RuDag, DagOperator, AttentionMechanism } from '@ruvector/rudag';

const pipeline = new RuDag({ name: 'etl-pipeline' });
await pipeline.init();

// Extract
const extractUsers = pipeline.addNode(DagOperator.SCAN, 1000);
const extractOrders = pipeline.addNode(DagOperator.SCAN, 2000);
const extractProducts = pipeline.addNode(DagOperator.SCAN, 500);

// Transform
const cleanUsers = pipeline.addNode(DagOperator.FILTER, 100);
const joinData = pipeline.addNode(DagOperator.JOIN, 3000);
const aggregate = pipeline.addNode(DagOperator.AGGREGATE, 500);

// Load
const loadWarehouse = pipeline.addNode(DagOperator.CUSTOM, 1000);

// Wire it up
pipeline.addEdge(extractUsers, cleanUsers);
pipeline.addEdge(cleanUsers, joinData);
pipeline.addEdge(extractOrders, joinData);
pipeline.addEdge(extractProducts, joinData);
pipeline.addEdge(joinData, aggregate);
pipeline.addEdge(aggregate, loadWarehouse);

// Find bottlenecks using attention scores
const scores = pipeline.attention(AttentionMechanism.CRITICAL_PATH);
console.log('Node importance:', scores);
// Nodes on critical path have higher scores

Integration with Other Packages

With Express.js (REST API)

import express from 'express';
import { RuDag, DagOperator } from '@ruvector/rudag';
import { NodeDagManager } from '@ruvector/rudag/node';

const app = express();
const manager = new NodeDagManager('./data/dags');

app.use(express.json());

app.post('/dags', async (req, res) => {
  const dag = await manager.createDag(req.body.name);
  // ... add nodes from request ...
  await manager.saveDag(dag);
  res.json({ id: dag.getId() });
});

app.get('/dags/:id/critical-path', async (req, res) => {
  const dag = await manager.loadDag(req.params.id);
  if (!dag) return res.status(404).json({ error: 'Not found' });

  const result = dag.criticalPath();
  dag.dispose();
  res.json(result);
});

app.listen(3000);

With React (State Management)

import { useState, useEffect } from 'react';
import { RuDag, DagOperator } from '@ruvector/rudag';

function useDag(name: string) {
  const [dag, setDag] = useState<RuDag | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const init = async () => {
      const d = new RuDag({ name });
      await d.init();
      setDag(d);
      setLoading(false);
    };
    init();

    return () => dag?.dispose();
  }, [name]);

  return { dag, loading };
}

function DagViewer({ name }: { name: string }) {
  const { dag, loading } = useDag(name);
  const [criticalPath, setCriticalPath] = useState<number[]>([]);

  useEffect(() => {
    if (dag && dag.nodeCount > 0) {
      setCriticalPath(dag.criticalPath().path);
    }
  }, [dag]);

  if (loading) return <div>Loading...</div>;

  return (
    <div>
      <p>Nodes: {dag?.nodeCount}</p>
      <p>Critical Path: {criticalPath.join(' → ')}</p>
    </div>
  );
}

With D3.js (Visualization)

import * as d3 from 'd3';
import { RuDag, DagOperator } from '@ruvector/rudag';

async function visualizeDag(dag: RuDag, container: HTMLElement) {
  const nodes = dag.getNodes().map(n => ({
    id: n.id,
    label: DagOperator[n.operator],
    cost: n.cost,
  }));

  const topo = dag.topoSort();
  const { path: criticalPath } = dag.criticalPath();
  const criticalSet = new Set(criticalPath);

  // Create D3 visualization
  const svg = d3.select(container).append('svg');

  svg.selectAll('circle')
    .data(nodes)
    .enter()
    .append('circle')
    .attr('r', d => Math.sqrt(d.cost) * 2)
    .attr('fill', d => criticalSet.has(d.id) ? '#ff6b6b' : '#4dabf7')
    .attr('cx', (d, i) => 100 + topo.indexOf(d.id) * 150)
    .attr('cy', 100);
}

With Bull (Job Queue)

import Queue from 'bull';
import { RuDag, DagOperator } from '@ruvector/rudag';

const jobQueue = new Queue('dag-jobs');

async function queueDagExecution(dag: RuDag) {
  const order = dag.topoSort();
  const nodes = dag.getNodes();

  // Queue jobs in topological order with dependencies
  const jobIds: Record<number, string> = {};

  for (const nodeId of order) {
    const node = nodes.find(n => n.id === nodeId)!;

    const job = await jobQueue.add({
      nodeId,
      operator: node.operator,
      cost: node.cost,
    }, {
      // Jobs wait for their dependencies
      delay: 0,
    });

    jobIds[nodeId] = job.id as string;
  }

  return jobIds;
}

With GraphQL

import { ApolloServer, gql } from 'apollo-server';
import { RuDag, DagOperator } from '@ruvector/rudag';
import { NodeDagManager } from '@ruvector/rudag/node';

const manager = new NodeDagManager('./dags');

const typeDefs = gql`
  type Dag {
    id: String!
    name: String
    nodeCount: Int!
    edgeCount: Int!
    criticalPath: CriticalPath!
  }

  type CriticalPath {
    path: [Int!]!
    cost: Float!
  }

  type Query {
    dag(id: String!): Dag
    dags: [Dag!]!
  }
`;

const resolvers = {
  Query: {
    dag: async (_: any, { id }: { id: string }) => {
      const dag = await manager.loadDag(id);
      if (!dag) return null;

      const result = {
        id: dag.getId(),
        name: dag.getName(),
        nodeCount: dag.nodeCount,
        edgeCount: dag.edgeCount,
        criticalPath: dag.criticalPath(),
      };

      dag.dispose();
      return result;
    },
  },
};

With RxJS (Reactive Streams)

import { Subject, from } from 'rxjs';
import { mergeMap, toArray } from 'rxjs/operators';
import { RuDag, DagOperator } from '@ruvector/rudag';

async function executeWithRxJS(dag: RuDag) {
  const order = dag.topoSort();
  const nodes = dag.getNodes();

  const results$ = from(order).pipe(
    mergeMap(async (nodeId) => {
      const node = nodes.find(n => n.id === nodeId)!;

      // Simulate execution
      await new Promise(r => setTimeout(r, node.cost));

      return { nodeId, completed: true };
    }, 3), // Max 3 concurrent executions
    toArray()
  );

  return results$.toPromise();
}

Performance

Operation rudag (WASM) Pure JS
Add 10k nodes ~15ms ~150ms
Topological sort (10k) ~2ms ~50ms
Critical path (10k) ~3ms ~80ms
Serialization (10k) ~5ms ~100ms

Browser Support

  • Chrome 57+
  • Firefox 52+
  • Safari 11+
  • Edge 79+

Requires WebAssembly support.

API Reference

RuDag

class RuDag {
  constructor(options?: RuDagOptions);
  init(): Promise<this>;

  // Graph operations
  addNode(operator: DagOperator, cost: number, metadata?: object): number;
  addEdge(from: number, to: number): boolean;

  // Properties
  nodeCount: number;
  edgeCount: number;

  // Analysis
  topoSort(): number[];
  criticalPath(): { path: number[]; cost: number };
  attention(mechanism?: AttentionMechanism): number[];

  // Node access
  getNode(id: number): DagNode | undefined;
  getNodes(): DagNode[];

  // Serialization
  toBytes(): Uint8Array;
  toJSON(): string;

  // Persistence
  save(): Promise<StoredDag | null>;
  static load(id: string, storage?): Promise<RuDag | null>;
  static fromBytes(data: Uint8Array, options?): Promise<RuDag>;
  static fromJSON(json: string, options?): Promise<RuDag>;
  static listStored(storage?): Promise<StoredDag[]>;
  static deleteStored(id: string, storage?): Promise<boolean>;

  // Lifecycle
  getId(): string;
  getName(): string | undefined;
  setName(name: string): void;
  dispose(): void;
}

Options

interface RuDagOptions {
  id?: string;              // Custom ID (auto-generated if not provided)
  name?: string;            // Human-readable name
  storage?: Storage | null; // Persistence backend (null = disabled)
  autoSave?: boolean;       // Auto-save on changes (default: true)
  onSaveError?: (error) => void;  // Handle background save errors
}

License

MIT OR Apache-2.0