git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
758 lines
21 KiB
Markdown
758 lines
21 KiB
Markdown
# @ruvector/rudag
|
|
|
|
[](https://www.npmjs.com/package/@ruvector/rudag)
|
|
[](https://www.npmjs.com/package/@ruvector/rudag)
|
|
[](https://github.com/ruvnet/ruvector/blob/main/LICENSE)
|
|
[](https://www.typescriptlang.org/)
|
|
[](https://webassembly.org/)
|
|
[](https://nodejs.org/)
|
|
[](https://bundlephobia.com/package/@ruvector/rudag)
|
|
|
|
**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
|
|
|
|
```bash
|
|
npm install @ruvector/rudag
|
|
```
|
|
|
|
|
|
```typescript
|
|
// 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.
|
|
|
|
```typescript
|
|
// 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:
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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:
|
|
|
|
```typescript
|
|
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:**
|
|
```typescript
|
|
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):**
|
|
```typescript
|
|
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:**
|
|
```typescript
|
|
const dag = new RuDag({ storage: null, autoSave: false });
|
|
```
|
|
|
|
### Serialization
|
|
|
|
```typescript
|
|
// 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:
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
```typescript
|
|
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:
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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)
|
|
|
|
```typescript
|
|
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)
|
|
|
|
```typescript
|
|
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)
|
|
|
|
```typescript
|
|
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)
|
|
|
|
```typescript
|
|
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)
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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)
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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
|