Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'

This commit is contained in:
ruv
2026-02-28 14:39:40 -05:00
7854 changed files with 3522914 additions and 0 deletions

View File

@@ -0,0 +1,106 @@
# @ruvector/graph-node
Native Node.js bindings for RuVector Graph Database with hypergraph support, Cypher queries, and persistence. **10x faster than WASM**.
## Features
- **Native Performance**: Direct NAPI-RS bindings - no WASM overhead
- **Hypergraph Support**: Multi-node relationships with vector embeddings
- **Cypher Queries**: Neo4j-compatible query language
- **Persistence**: ACID-compliant storage with redb backend
- **Vector Similarity Search**: Fast k-NN search on embeddings
- **Graph Traversal**: k-hop neighbor discovery
- **Transactions**: Full ACID support with begin/commit/rollback
- **Batch Operations**: High-throughput bulk inserts (131K+ ops/sec)
- **Zero-Copy**: Efficient Float32Array handling
- **TypeScript**: Full type definitions included
## Installation
```bash
npm install @ruvector/graph-node
```
## Quick Start
```javascript
const { GraphDatabase } = require('@ruvector/graph-node');
// Create an in-memory database
const db = new GraphDatabase({
distanceMetric: 'Cosine',
dimensions: 384
});
// Or create a persistent database
const persistentDb = new GraphDatabase({
distanceMetric: 'Cosine',
dimensions: 384,
storagePath: './my-graph.db'
});
// Or open an existing database
const existingDb = GraphDatabase.open('./my-graph.db');
// Create nodes
await db.createNode({
id: 'alice',
embedding: new Float32Array([1.0, 0.0, 0.0, /* ... */]),
labels: ['Person', 'Employee'],
properties: { name: 'Alice', age: '30' }
});
// Create edges
await db.createEdge({
from: 'alice',
to: 'bob',
description: 'KNOWS',
embedding: new Float32Array([0.5, 0.5, 0.0, /* ... */]),
confidence: 0.95
});
// Create hyperedges (multi-node relationships)
await db.createHyperedge({
nodes: ['alice', 'bob', 'charlie'],
description: 'COLLABORATED_ON_PROJECT',
embedding: new Float32Array([0.33, 0.33, 0.33, /* ... */]),
confidence: 0.85
});
// Query with Cypher
const results = await db.query('MATCH (n:Person) RETURN n');
// Vector similarity search
const similar = await db.searchHyperedges({
embedding: new Float32Array([0.3, 0.3, 0.3, /* ... */]),
k: 10
});
// Get statistics
const stats = await db.stats();
console.log(\`Nodes: \${stats.totalNodes}, Edges: \${stats.totalEdges}\`);
```
## Benchmarks
| Operation | Throughput | Latency |
|-----------|------------|---------|
| Node Creation | 9.17K ops/sec | 109ms |
| Batch Node Creation | 131.10K ops/sec | 7.63ms |
| Edge Creation | 9.30K ops/sec | 107ms |
| Vector Search (k=10) | 2.35K ops/sec | 42ms |
| k-hop Traversal | 10.28K ops/sec | 9.73ms |
## Platform Support
| Platform | Architecture | Status |
|----------|--------------|--------|
| Linux | x64 (glibc) | Supported |
| Linux | arm64 (glibc) | Supported |
| macOS | x64 | Supported |
| macOS | arm64 (M1/M2) | Supported |
| Windows | x64 | Supported |
## License
MIT

View File

@@ -0,0 +1,216 @@
/**
* RuVector Graph Node Benchmark
*
* Tests performance of graph operations including:
* - Node creation
* - Edge creation
* - Hyperedge creation
* - Batch inserts
* - Vector similarity search
* - k-hop neighbor traversal
* - Cypher queries
*/
const { GraphDatabase, version } = require('./index.js');
const DIMENSIONS = 384;
const NUM_NODES = 10000;
const NUM_EDGES = 50000;
const NUM_HYPEREDGES = 5000;
const SEARCH_K = 10;
function randomEmbedding(dims) {
const arr = new Float32Array(dims);
for (let i = 0; i < dims; i++) {
arr[i] = Math.random();
}
return arr;
}
function formatTime(ms) {
if (ms < 1) return `${(ms * 1000).toFixed(2)}μs`;
if (ms < 1000) return `${ms.toFixed(2)}ms`;
return `${(ms / 1000).toFixed(2)}s`;
}
function formatOps(count, ms) {
const ops = (count / ms) * 1000;
if (ops >= 1000000) return `${(ops / 1000000).toFixed(2)}M ops/sec`;
if (ops >= 1000) return `${(ops / 1000).toFixed(2)}K ops/sec`;
return `${ops.toFixed(2)} ops/sec`;
}
async function benchmark() {
console.log('╔════════════════════════════════════════════════════════════════╗');
console.log('║ RuVector Graph Node Benchmark Suite ║');
console.log('╠════════════════════════════════════════════════════════════════╣');
console.log(`║ Version: ${version().padEnd(54)}`);
console.log(`║ Dimensions: ${DIMENSIONS.toString().padEnd(51)}`);
console.log(`║ Nodes: ${NUM_NODES.toLocaleString().padEnd(56)}`);
console.log(`║ Edges: ${NUM_EDGES.toLocaleString().padEnd(56)}`);
console.log(`║ Hyperedges: ${NUM_HYPEREDGES.toLocaleString().padEnd(51)}`);
console.log('╚════════════════════════════════════════════════════════════════╝\n');
const db = new GraphDatabase({
distanceMetric: 'Cosine',
dimensions: DIMENSIONS
});
const results = [];
// Benchmark 1: Node Creation
console.log('📌 Benchmark 1: Individual Node Creation');
const nodeCount = 1000;
const nodeStart = performance.now();
for (let i = 0; i < nodeCount; i++) {
await db.createNode({
id: `node_${i}`,
embedding: randomEmbedding(DIMENSIONS),
labels: ['TestNode'],
properties: { index: String(i) }
});
}
const nodeEnd = performance.now();
const nodeTime = nodeEnd - nodeStart;
console.log(` Created ${nodeCount} nodes in ${formatTime(nodeTime)}`);
console.log(` Throughput: ${formatOps(nodeCount, nodeTime)}\n`);
results.push({ name: 'Node Creation', count: nodeCount, time: nodeTime });
// Benchmark 2: Batch Node Creation
console.log('📌 Benchmark 2: Batch Node Creation');
const batchSize = 1000;
const batchNodes = [];
for (let i = 0; i < batchSize; i++) {
batchNodes.push({
id: `batch_node_${i}`,
embedding: randomEmbedding(DIMENSIONS),
labels: ['BatchNode']
});
}
const batchNodeStart = performance.now();
await db.batchInsert({ nodes: batchNodes, edges: [] });
const batchNodeEnd = performance.now();
const batchNodeTime = batchNodeEnd - batchNodeStart;
console.log(` Inserted ${batchSize} nodes in ${formatTime(batchNodeTime)}`);
console.log(` Throughput: ${formatOps(batchSize, batchNodeTime)}\n`);
results.push({ name: 'Batch Node Creation', count: batchSize, time: batchNodeTime });
// Benchmark 3: Edge Creation
console.log('📌 Benchmark 3: Edge Creation');
const edgeCount = 1000;
const edgeStart = performance.now();
for (let i = 0; i < edgeCount; i++) {
const from = `node_${i % nodeCount}`;
const to = `node_${(i + 1) % nodeCount}`;
await db.createEdge({
from,
to,
description: 'CONNECTED_TO',
embedding: randomEmbedding(DIMENSIONS),
confidence: Math.random()
});
}
const edgeEnd = performance.now();
const edgeTime = edgeEnd - edgeStart;
console.log(` Created ${edgeCount} edges in ${formatTime(edgeTime)}`);
console.log(` Throughput: ${formatOps(edgeCount, edgeTime)}\n`);
results.push({ name: 'Edge Creation', count: edgeCount, time: edgeTime });
// Benchmark 4: Hyperedge Creation
console.log('📌 Benchmark 4: Hyperedge Creation');
const hyperedgeCount = 500;
const hyperedgeStart = performance.now();
for (let i = 0; i < hyperedgeCount; i++) {
const nodes = [];
const numNodes = 3 + Math.floor(Math.random() * 5); // 3-7 nodes per hyperedge
for (let j = 0; j < numNodes; j++) {
nodes.push(`node_${(i + j) % nodeCount}`);
}
await db.createHyperedge({
nodes,
description: `RELATIONSHIP_${i}`,
embedding: randomEmbedding(DIMENSIONS),
confidence: Math.random()
});
}
const hyperedgeEnd = performance.now();
const hyperedgeTime = hyperedgeEnd - hyperedgeStart;
console.log(` Created ${hyperedgeCount} hyperedges in ${formatTime(hyperedgeTime)}`);
console.log(` Throughput: ${formatOps(hyperedgeCount, hyperedgeTime)}\n`);
results.push({ name: 'Hyperedge Creation', count: hyperedgeCount, time: hyperedgeTime });
// Benchmark 5: Vector Similarity Search
console.log('📌 Benchmark 5: Vector Similarity Search');
const searchCount = 100;
const searchStart = performance.now();
for (let i = 0; i < searchCount; i++) {
await db.searchHyperedges({
embedding: randomEmbedding(DIMENSIONS),
k: SEARCH_K
});
}
const searchEnd = performance.now();
const searchTime = searchEnd - searchStart;
console.log(` Performed ${searchCount} searches (k=${SEARCH_K}) in ${formatTime(searchTime)}`);
console.log(` Throughput: ${formatOps(searchCount, searchTime)}\n`);
results.push({ name: 'Vector Search', count: searchCount, time: searchTime });
// Benchmark 6: k-hop Neighbor Traversal
console.log('📌 Benchmark 6: k-hop Neighbor Traversal');
const traversalCount = 100;
const traversalStart = performance.now();
for (let i = 0; i < traversalCount; i++) {
await db.kHopNeighbors(`node_${i % nodeCount}`, 2);
}
const traversalEnd = performance.now();
const traversalTime = traversalEnd - traversalStart;
console.log(` Performed ${traversalCount} 2-hop traversals in ${formatTime(traversalTime)}`);
console.log(` Throughput: ${formatOps(traversalCount, traversalTime)}\n`);
results.push({ name: 'k-hop Traversal', count: traversalCount, time: traversalTime });
// Benchmark 7: Statistics Query
console.log('📌 Benchmark 7: Statistics Query');
const statsCount = 1000;
const statsStart = performance.now();
for (let i = 0; i < statsCount; i++) {
await db.stats();
}
const statsEnd = performance.now();
const statsTime = statsEnd - statsStart;
console.log(` Performed ${statsCount} stats queries in ${formatTime(statsTime)}`);
console.log(` Throughput: ${formatOps(statsCount, statsTime)}\n`);
results.push({ name: 'Stats Query', count: statsCount, time: statsTime });
// Benchmark 8: Transaction Overhead
console.log('📌 Benchmark 8: Transaction Overhead');
const txCount = 100;
const txStart = performance.now();
for (let i = 0; i < txCount; i++) {
const txId = await db.begin();
await db.commit(txId);
}
const txEnd = performance.now();
const txTime = txEnd - txStart;
console.log(` Performed ${txCount} transactions in ${formatTime(txTime)}`);
console.log(` Throughput: ${formatOps(txCount, txTime)}\n`);
results.push({ name: 'Transaction', count: txCount, time: txTime });
// Summary
console.log('╔════════════════════════════════════════════════════════════════╗');
console.log('║ BENCHMARK SUMMARY ║');
console.log('╠════════════════════════════════════════════════════════════════╣');
for (const r of results) {
const ops = formatOps(r.count, r.time);
console.log(`${r.name.padEnd(25)} ${ops.padStart(20)} ${formatTime(r.time).padStart(12)}`);
}
console.log('╚════════════════════════════════════════════════════════════════╝');
// Final stats
const finalStats = await db.stats();
console.log(`\n📊 Final Database State:`);
console.log(` Total Nodes: ${finalStats.totalNodes.toLocaleString()}`);
console.log(` Total Edges: ${finalStats.totalEdges.toLocaleString()}`);
console.log(` Avg Degree: ${finalStats.avgDegree.toFixed(4)}`);
}
benchmark().catch(console.error);

View File

@@ -0,0 +1,370 @@
/* tslint:disable */
/* eslint-disable */
/* auto-generated by NAPI-RS */
/** Distance metric for similarity calculation */
export const enum JsDistanceMetric {
Euclidean = 'Euclidean',
Cosine = 'Cosine',
DotProduct = 'DotProduct',
Manhattan = 'Manhattan'
}
/** Graph database configuration options */
export interface JsGraphOptions {
/** Distance metric for embeddings */
distanceMetric?: JsDistanceMetric
/** Vector dimensions */
dimensions?: number
/** Storage path */
storagePath?: string
}
/** Node in the graph */
export interface JsNode {
/** Node ID */
id: string
/** Node embedding */
embedding: Float32Array
/** Node labels (e.g., ["Person", "Employee"]) */
labels?: Array<string>
/** Optional properties */
properties?: Record<string, string>
}
/** Edge between two nodes */
export interface JsEdge {
/** Source node ID */
from: string
/** Target node ID */
to: string
/** Edge description/label */
description: string
/** Edge embedding */
embedding: Float32Array
/** Confidence score (0.0-1.0) */
confidence?: number
/** Optional metadata */
metadata?: Record<string, string>
}
/** Hyperedge connecting multiple nodes */
export interface JsHyperedge {
/** Node IDs connected by this hyperedge */
nodes: Array<string>
/** Natural language description of the relationship */
description: string
/** Embedding of the hyperedge description */
embedding: Float32Array
/** Confidence weight (0.0-1.0) */
confidence?: number
/** Optional metadata */
metadata?: Record<string, string>
}
/** Query for searching hyperedges */
export interface JsHyperedgeQuery {
/** Query embedding */
embedding: Float32Array
/** Number of results to return */
k: number
}
/** Hyperedge search result */
export interface JsHyperedgeResult {
/** Hyperedge ID */
id: string
/** Similarity score */
score: number
}
/** Node result from query (without embedding) */
export interface JsNodeResult {
/** Node ID */
id: string
/** Node labels */
labels: Array<string>
/** Node properties */
properties: Record<string, string>
}
/** Edge result from query */
export interface JsEdgeResult {
/** Edge ID */
id: string
/** Source node ID */
from: string
/** Target node ID */
to: string
/** Edge type/label */
edgeType: string
/** Edge properties */
properties: Record<string, string>
}
/** Query result */
export interface JsQueryResult {
/** Nodes returned by the query */
nodes: Array<JsNodeResult>
/** Edges returned by the query */
edges: Array<JsEdgeResult>
/** Optional statistics */
stats?: JsGraphStats
}
/** Graph statistics */
export interface JsGraphStats {
/** Total number of nodes */
totalNodes: number
/** Total number of edges */
totalEdges: number
/** Average node degree */
avgDegree: number
}
/** Batch insert data */
export interface JsBatchInsert {
/** Nodes to insert */
nodes: Array<JsNode>
/** Edges to insert */
edges: Array<JsEdge>
}
/** Batch insert result */
export interface JsBatchResult {
/** IDs of inserted nodes */
nodeIds: Array<string>
/** IDs of inserted edges */
edgeIds: Array<string>
}
/** Temporal granularity */
export const enum JsTemporalGranularity {
Hourly = 'Hourly',
Daily = 'Daily',
Monthly = 'Monthly',
Yearly = 'Yearly'
}
/** Temporal hyperedge */
export interface JsTemporalHyperedge {
/** Base hyperedge */
hyperedge: JsHyperedge
/** Creation timestamp (Unix epoch seconds) */
timestamp: number
/** Optional expiration timestamp */
expiresAt?: number
/** Temporal context */
granularity: JsTemporalGranularity
}
/** Get the version of the library */
export declare function version(): string
/** Test function to verify bindings */
export declare function hello(): string
/** Streaming query result iterator */
export declare class QueryResultStream {
/**
* Get the next result from the stream
*
* # Example
* ```javascript
* const stream = await db.queryStream('MATCH (n) RETURN n');
* while (true) {
* const result = await stream.next();
* if (!result) break;
* console.log(result);
* }
* ```
*/
next(): JsQueryResult | null
}
/** Streaming hyperedge result iterator */
export declare class HyperedgeStream {
/**
* Get the next hyperedge result
*
* # Example
* ```javascript
* const stream = await db.searchHyperedgesStream(query);
* for await (const result of stream) {
* console.log(result);
* }
* ```
*/
next(): JsHyperedgeResult | null
/** Collect all remaining results */
collect(): Array<JsHyperedgeResult>
}
/** Node stream iterator */
export declare class NodeStream {
/** Get the next node */
next(): JsNode | null
/** Collect all remaining nodes */
collect(): Array<JsNode>
}
/** Graph database for complex relationship queries */
export declare class GraphDatabase {
/**
* Create a new graph database
*
* # Example
* ```javascript
* const db = new GraphDatabase({
* distanceMetric: 'Cosine',
* dimensions: 384
* });
* ```
*/
constructor(options?: JsGraphOptions | undefined | null)
/**
* Open an existing graph database from disk
*
* # Example
* ```javascript
* const db = GraphDatabase.open('./my-graph.db');
* ```
*/
static open(path: string): GraphDatabase
/**
* Check if persistence is enabled
*
* # Example
* ```javascript
* if (db.isPersistent()) {
* console.log('Data is being saved to:', db.getStoragePath());
* }
* ```
*/
isPersistent(): boolean
/** Get the storage path (if persisted) */
getStoragePath(): string | null
/**
* Create a node in the graph
*
* # Example
* ```javascript
* const nodeId = await db.createNode({
* id: 'node1',
* embedding: new Float32Array([1, 2, 3]),
* properties: { name: 'Alice', age: 30 }
* });
* ```
*/
createNode(node: JsNode): Promise<string>
/**
* Create an edge between two nodes
*
* # Example
* ```javascript
* const edgeId = await db.createEdge({
* from: 'node1',
* to: 'node2',
* description: 'knows',
* embedding: new Float32Array([0.5, 0.5, 0.5]),
* confidence: 0.95
* });
* ```
*/
createEdge(edge: JsEdge): Promise<string>
/**
* Create a hyperedge connecting multiple nodes
*
* # Example
* ```javascript
* const hyperedgeId = await db.createHyperedge({
* nodes: ['node1', 'node2', 'node3'],
* description: 'collaborated_on_project',
* embedding: new Float32Array([0.3, 0.6, 0.9]),
* confidence: 0.85,
* metadata: { project: 'AI Research' }
* });
* ```
*/
createHyperedge(hyperedge: JsHyperedge): Promise<string>
/**
* Query the graph using Cypher-like syntax
*
* # Example
* ```javascript
* const results = await db.query('MATCH (n) RETURN n LIMIT 10');
* ```
*/
query(cypher: string): Promise<JsQueryResult>
/**
* Query the graph synchronously
*
* # Example
* ```javascript
* const results = db.querySync('MATCH (n) RETURN n LIMIT 10');
* ```
*/
querySync(cypher: string): JsQueryResult
/**
* Search for similar hyperedges
*
* # Example
* ```javascript
* const results = await db.searchHyperedges({
* embedding: new Float32Array([0.5, 0.5, 0.5]),
* k: 10
* });
* ```
*/
searchHyperedges(query: JsHyperedgeQuery): Promise<Array<JsHyperedgeResult>>
/**
* Get k-hop neighbors from a starting node
*
* # Example
* ```javascript
* const neighbors = await db.kHopNeighbors('node1', 2);
* ```
*/
kHopNeighbors(startNode: string, k: number): Promise<Array<string>>
/**
* Begin a new transaction
*
* # Example
* ```javascript
* const txId = await db.begin();
* ```
*/
begin(): Promise<string>
/**
* Commit a transaction
*
* # Example
* ```javascript
* await db.commit(txId);
* ```
*/
commit(txId: string): Promise<void>
/**
* Rollback a transaction
*
* # Example
* ```javascript
* await db.rollback(txId);
* ```
*/
rollback(txId: string): Promise<void>
/**
* Batch insert nodes and edges
*
* # Example
* ```javascript
* await db.batchInsert({
* nodes: [{ id: 'n1', embedding: new Float32Array([1, 2]) }],
* edges: [{ from: 'n1', to: 'n2', description: 'knows' }]
* });
* ```
*/
batchInsert(batch: JsBatchInsert): Promise<JsBatchResult>
/**
* Subscribe to graph changes (returns a change stream)
*
* # Example
* ```javascript
* const unsubscribe = db.subscribe((change) => {
* console.log('Graph changed:', change);
* });
* ```
*/
subscribe(callback: (...args: any[]) => any): void
/**
* Get graph statistics
*
* # Example
* ```javascript
* const stats = await db.stats();
* console.log(`Nodes: ${stats.totalNodes}, Edges: ${stats.totalEdges}`);
* ```
*/
stats(): Promise<JsGraphStats>
}

View File

@@ -0,0 +1,322 @@
/* tslint:disable */
/* eslint-disable */
/* prettier-ignore */
/* auto-generated by NAPI-RS */
const { existsSync, readFileSync } = require('fs')
const { join } = require('path')
const { platform, arch } = process
let nativeBinding = null
let localFileExisted = false
let loadError = null
function isMusl() {
// For Node 10
if (!process.report || typeof process.report.getReport !== 'function') {
try {
const lddPath = require('child_process').execSync('which ldd').toString().trim()
return readFileSync(lddPath, 'utf8').includes('musl')
} catch (e) {
return true
}
} else {
const { glibcVersionRuntime } = process.report.getReport().header
return !glibcVersionRuntime
}
}
switch (platform) {
case 'android':
switch (arch) {
case 'arm64':
localFileExisted = existsSync(join(__dirname, 'index.android-arm64.node'))
try {
if (localFileExisted) {
nativeBinding = require('./index.android-arm64.node')
} else {
nativeBinding = require('@ruvector/graph-node-android-arm64')
}
} catch (e) {
loadError = e
}
break
case 'arm':
localFileExisted = existsSync(join(__dirname, 'index.android-arm-eabi.node'))
try {
if (localFileExisted) {
nativeBinding = require('./index.android-arm-eabi.node')
} else {
nativeBinding = require('@ruvector/graph-node-android-arm-eabi')
}
} catch (e) {
loadError = e
}
break
default:
throw new Error(`Unsupported architecture on Android ${arch}`)
}
break
case 'win32':
switch (arch) {
case 'x64':
localFileExisted = existsSync(
join(__dirname, 'index.win32-x64-msvc.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./index.win32-x64-msvc.node')
} else {
nativeBinding = require('@ruvector/graph-node-win32-x64-msvc')
}
} catch (e) {
loadError = e
}
break
case 'ia32':
localFileExisted = existsSync(
join(__dirname, 'index.win32-ia32-msvc.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./index.win32-ia32-msvc.node')
} else {
nativeBinding = require('@ruvector/graph-node-win32-ia32-msvc')
}
} catch (e) {
loadError = e
}
break
case 'arm64':
localFileExisted = existsSync(
join(__dirname, 'index.win32-arm64-msvc.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./index.win32-arm64-msvc.node')
} else {
nativeBinding = require('@ruvector/graph-node-win32-arm64-msvc')
}
} catch (e) {
loadError = e
}
break
default:
throw new Error(`Unsupported architecture on Windows: ${arch}`)
}
break
case 'darwin':
localFileExisted = existsSync(join(__dirname, 'index.darwin-universal.node'))
try {
if (localFileExisted) {
nativeBinding = require('./index.darwin-universal.node')
} else {
nativeBinding = require('@ruvector/graph-node-darwin-universal')
}
break
} catch {}
switch (arch) {
case 'x64':
localFileExisted = existsSync(join(__dirname, 'index.darwin-x64.node'))
try {
if (localFileExisted) {
nativeBinding = require('./index.darwin-x64.node')
} else {
nativeBinding = require('@ruvector/graph-node-darwin-x64')
}
} catch (e) {
loadError = e
}
break
case 'arm64':
localFileExisted = existsSync(
join(__dirname, 'index.darwin-arm64.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./index.darwin-arm64.node')
} else {
nativeBinding = require('@ruvector/graph-node-darwin-arm64')
}
} catch (e) {
loadError = e
}
break
default:
throw new Error(`Unsupported architecture on macOS: ${arch}`)
}
break
case 'freebsd':
if (arch !== 'x64') {
throw new Error(`Unsupported architecture on FreeBSD: ${arch}`)
}
localFileExisted = existsSync(join(__dirname, 'index.freebsd-x64.node'))
try {
if (localFileExisted) {
nativeBinding = require('./index.freebsd-x64.node')
} else {
nativeBinding = require('@ruvector/graph-node-freebsd-x64')
}
} catch (e) {
loadError = e
}
break
case 'linux':
switch (arch) {
case 'x64':
if (isMusl()) {
localFileExisted = existsSync(
join(__dirname, 'index.linux-x64-musl.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./index.linux-x64-musl.node')
} else {
nativeBinding = require('@ruvector/graph-node-linux-x64-musl')
}
} catch (e) {
loadError = e
}
} else {
localFileExisted = existsSync(
join(__dirname, 'index.linux-x64-gnu.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./index.linux-x64-gnu.node')
} else {
nativeBinding = require('@ruvector/graph-node-linux-x64-gnu')
}
} catch (e) {
loadError = e
}
}
break
case 'arm64':
if (isMusl()) {
localFileExisted = existsSync(
join(__dirname, 'index.linux-arm64-musl.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./index.linux-arm64-musl.node')
} else {
nativeBinding = require('@ruvector/graph-node-linux-arm64-musl')
}
} catch (e) {
loadError = e
}
} else {
localFileExisted = existsSync(
join(__dirname, 'index.linux-arm64-gnu.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./index.linux-arm64-gnu.node')
} else {
nativeBinding = require('@ruvector/graph-node-linux-arm64-gnu')
}
} catch (e) {
loadError = e
}
}
break
case 'arm':
if (isMusl()) {
localFileExisted = existsSync(
join(__dirname, 'index.linux-arm-musleabihf.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./index.linux-arm-musleabihf.node')
} else {
nativeBinding = require('@ruvector/graph-node-linux-arm-musleabihf')
}
} catch (e) {
loadError = e
}
} else {
localFileExisted = existsSync(
join(__dirname, 'index.linux-arm-gnueabihf.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./index.linux-arm-gnueabihf.node')
} else {
nativeBinding = require('@ruvector/graph-node-linux-arm-gnueabihf')
}
} catch (e) {
loadError = e
}
}
break
case 'riscv64':
if (isMusl()) {
localFileExisted = existsSync(
join(__dirname, 'index.linux-riscv64-musl.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./index.linux-riscv64-musl.node')
} else {
nativeBinding = require('@ruvector/graph-node-linux-riscv64-musl')
}
} catch (e) {
loadError = e
}
} else {
localFileExisted = existsSync(
join(__dirname, 'index.linux-riscv64-gnu.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./index.linux-riscv64-gnu.node')
} else {
nativeBinding = require('@ruvector/graph-node-linux-riscv64-gnu')
}
} catch (e) {
loadError = e
}
}
break
case 's390x':
localFileExisted = existsSync(
join(__dirname, 'index.linux-s390x-gnu.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./index.linux-s390x-gnu.node')
} else {
nativeBinding = require('@ruvector/graph-node-linux-s390x-gnu')
}
} catch (e) {
loadError = e
}
break
default:
throw new Error(`Unsupported architecture on Linux: ${arch}`)
}
break
default:
throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
}
if (!nativeBinding) {
if (loadError) {
throw loadError
}
throw new Error(`Failed to load native binding`)
}
const { QueryResultStream, HyperedgeStream, NodeStream, JsDistanceMetric, JsTemporalGranularity, GraphDatabase, version, hello } = nativeBinding
module.exports.QueryResultStream = QueryResultStream
module.exports.HyperedgeStream = HyperedgeStream
module.exports.NodeStream = NodeStream
module.exports.JsDistanceMetric = JsDistanceMetric
module.exports.JsTemporalGranularity = JsTemporalGranularity
module.exports.GraphDatabase = GraphDatabase
module.exports.version = version
module.exports.hello = hello

View File

@@ -0,0 +1,10 @@
{
"name": "@ruvector/graph-node-darwin-arm64",
"version": "0.1.26",
"os": ["darwin"],
"cpu": ["arm64"],
"main": "ruvector-graph.node",
"files": ["ruvector-graph.node"],
"license": "MIT",
"repository": {"type": "git", "url": "https://github.com/ruvnet/ruvector.git"}
}

View File

@@ -0,0 +1,10 @@
{
"name": "@ruvector/graph-node-darwin-x64",
"version": "0.1.26",
"os": ["darwin"],
"cpu": ["x64"],
"main": "ruvector-graph.node",
"files": ["ruvector-graph.node"],
"license": "MIT",
"repository": {"type": "git", "url": "https://github.com/ruvnet/ruvector.git"}
}

View File

@@ -0,0 +1,10 @@
{
"name": "@ruvector/graph-node-linux-arm64-gnu",
"version": "0.1.26",
"os": ["linux"],
"cpu": ["arm64"],
"main": "ruvector-graph.node",
"files": ["ruvector-graph.node"],
"license": "MIT",
"repository": {"type": "git", "url": "https://github.com/ruvnet/ruvector.git"}
}

View File

@@ -0,0 +1,10 @@
{
"name": "@ruvector/graph-node-win32-x64-msvc",
"version": "0.1.26",
"os": ["win32"],
"cpu": ["x64"],
"main": "ruvector-graph.node",
"files": ["ruvector-graph.node"],
"license": "MIT",
"repository": {"type": "git", "url": "https://github.com/ruvnet/ruvector.git"}
}

View File

@@ -0,0 +1,67 @@
{
"name": "@ruvector/graph-node",
"version": "2.0.2",
"description": "Native Node.js bindings for RuVector Graph Database with hypergraph support, Cypher queries, and persistence - 10x faster than WASM",
"main": "index.js",
"types": "index.d.ts",
"author": "ruv.io Team <info@ruv.io> (https://ruv.io)",
"homepage": "https://ruv.io",
"repository": {
"type": "git",
"url": "https://github.com/ruvnet/ruvector.git",
"directory": "npm/packages/graph-node"
},
"bugs": {
"url": "https://github.com/ruvnet/ruvector/issues"
},
"license": "MIT",
"engines": {
"node": ">=18.0.0"
},
"files": [
"index.js",
"index.d.ts",
"README.md"
],
"scripts": {
"build:napi": "napi build --platform --release --cargo-cwd ../../../crates/ruvector-graph-node",
"test": "node test.js",
"benchmark": "node benchmark.js",
"publish:platforms": "node scripts/publish-platforms.js"
},
"devDependencies": {
"@napi-rs/cli": "^2.18.0"
},
"optionalDependencies": {
"@ruvector/graph-node-linux-x64-gnu": "2.0.2",
"@ruvector/graph-node-linux-arm64-gnu": "2.0.2",
"@ruvector/graph-node-darwin-x64": "2.0.2",
"@ruvector/graph-node-darwin-arm64": "2.0.2",
"@ruvector/graph-node-win32-x64-msvc": "2.0.2"
},
"publishConfig": {
"access": "public"
},
"keywords": [
"graph-database",
"graph",
"hypergraph",
"cypher",
"neo4j",
"vector-database",
"graph-query",
"knowledge-graph",
"property-graph",
"native",
"napi",
"rust",
"fast",
"performance",
"zero-copy",
"ai",
"machine-learning",
"rag",
"ruv",
"ruvector"
]
}

View File

@@ -0,0 +1,78 @@
#!/usr/bin/env node
/**
* Publish platform-specific @ruvector/graph-node packages to npm
*/
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
const platforms = [
{ name: 'linux-x64-gnu', nodeFile: 'index.linux-x64-gnu.node' },
{ name: 'linux-arm64-gnu', nodeFile: 'index.linux-arm64-gnu.node' },
{ name: 'darwin-x64', nodeFile: 'index.darwin-x64.node' },
{ name: 'darwin-arm64', nodeFile: 'index.darwin-arm64.node' },
{ name: 'win32-x64-msvc', nodeFile: 'index.win32-x64-msvc.node' },
];
const rootDir = path.join(__dirname, '..');
const version = require(path.join(rootDir, 'package.json')).version;
console.log('Publishing @ruvector/graph-node platform packages v' + version + '\n');
for (const platform of platforms) {
const pkgName = '@ruvector/graph-node-' + platform.name;
const nodeFile = path.join(rootDir, platform.nodeFile);
if (!fs.existsSync(nodeFile)) {
console.log('Skipping ' + pkgName + ' - ' + platform.nodeFile + ' not found');
continue;
}
const tmpDir = path.join(rootDir, 'npm', platform.name);
fs.mkdirSync(tmpDir, { recursive: true });
// Create package.json for platform package
const pkgJson = {
name: pkgName,
version: version,
description: 'RuVector Graph Node.js bindings for ' + platform.name,
main: 'ruvector-graph.node',
files: ['ruvector-graph.node'],
os: platform.name.includes('linux') ? ['linux'] :
platform.name.includes('darwin') ? ['darwin'] :
platform.name.includes('win32') ? ['win32'] : [],
cpu: platform.name.includes('x64') ? ['x64'] :
platform.name.includes('arm64') ? ['arm64'] : [],
engines: { node: '>=18.0.0' },
license: 'MIT',
repository: {
type: 'git',
url: 'https://github.com/ruvnet/ruvector.git',
directory: 'npm/packages/graph-node'
},
publishConfig: { access: 'public' }
};
fs.writeFileSync(
path.join(tmpDir, 'package.json'),
JSON.stringify(pkgJson, null, 2)
);
// Copy the .node file
fs.copyFileSync(nodeFile, path.join(tmpDir, 'ruvector-graph.node'));
// Publish
console.log('Publishing ' + pkgName + '@' + version + '...');
try {
execSync('npm publish --access public', { cwd: tmpDir, stdio: 'inherit' });
console.log('Published ' + pkgName + '@' + version + '\n');
} catch (e) {
console.error('Failed to publish ' + pkgName + ': ' + e.message + '\n');
}
// Cleanup
fs.rmSync(tmpDir, { recursive: true, force: true });
}
console.log('Done!');

View File

@@ -0,0 +1,172 @@
const { GraphDatabase, version, hello } = require('./index.js');
const fs = require('fs');
const path = require('path');
const os = require('os');
console.log('RuVector Graph Node Test');
console.log('========================\n');
// Test 1: Version and hello
console.log('1. Testing version and hello functions:');
console.log(' Version:', version());
console.log(' Hello:', hello());
console.log(' ✓ Basic functions work\n');
// Test 2: Create database
console.log('2. Creating graph database:');
const db = new GraphDatabase({
distanceMetric: 'Cosine',
dimensions: 3
});
console.log(' ✓ Database created\n');
// Test 3: Create nodes
console.log('3. Creating nodes:');
(async () => {
try {
const nodeId1 = await db.createNode({
id: 'alice',
embedding: new Float32Array([1.0, 0.0, 0.0]),
labels: ['Person', 'Employee'],
properties: { name: 'Alice', age: '30' }
});
console.log(' Created node:', nodeId1);
const nodeId2 = await db.createNode({
id: 'bob',
embedding: new Float32Array([0.0, 1.0, 0.0]),
labels: ['Person'],
properties: { name: 'Bob', age: '25' }
});
console.log(' Created node:', nodeId2);
console.log(' ✓ Nodes created\n');
// Test 4: Create edge
console.log('4. Creating edge:');
const edgeId = await db.createEdge({
from: 'alice',
to: 'bob',
description: 'knows',
embedding: new Float32Array([0.5, 0.5, 0.0]),
confidence: 0.95
});
console.log(' Created edge:', edgeId);
console.log(' ✓ Edge created\n');
// Test 5: Create hyperedge
console.log('5. Creating hyperedge:');
const nodeId3 = await db.createNode({
id: 'charlie',
embedding: new Float32Array([0.0, 0.0, 1.0])
});
const hyperedgeId = await db.createHyperedge({
nodes: ['alice', 'bob', 'charlie'],
description: 'collaborated_on_project',
embedding: new Float32Array([0.33, 0.33, 0.33]),
confidence: 0.85
});
console.log(' Created hyperedge:', hyperedgeId);
console.log(' ✓ Hyperedge created\n');
// Test 6: Query
console.log('6. Querying graph:');
const results = await db.query('MATCH (n) RETURN n');
console.log(' Query results:', JSON.stringify(results, null, 2));
console.log(' ✓ Query executed\n');
// Test 7: Search hyperedges
console.log('7. Searching hyperedges:');
const searchResults = await db.searchHyperedges({
embedding: new Float32Array([0.3, 0.3, 0.3]),
k: 5
});
console.log(' Search results:', searchResults);
console.log(' ✓ Search completed\n');
// Test 8: k-hop neighbors
console.log('8. Finding k-hop neighbors:');
const neighbors = await db.kHopNeighbors('alice', 2);
console.log(' Neighbors:', neighbors);
console.log(' ✓ Neighbors found\n');
// Test 9: Statistics
console.log('9. Getting statistics:');
const stats = await db.stats();
console.log(' Stats:', stats);
console.log(' ✓ Statistics retrieved\n');
// Test 10: Transactions
console.log('10. Testing transactions:');
const txId = await db.begin();
console.log(' Transaction started:', txId);
await db.commit(txId);
console.log(' Transaction committed');
console.log(' ✓ Transaction test passed\n');
// Test 11: Batch insert
console.log('11. Testing batch insert:');
const batchResult = await db.batchInsert({
nodes: [
{ id: 'n1', embedding: new Float32Array([1, 0, 0]) },
{ id: 'n2', embedding: new Float32Array([0, 1, 0]) }
],
edges: [
{
from: 'n1',
to: 'n2',
description: 'connects',
embedding: new Float32Array([0.5, 0.5, 0])
}
]
});
console.log(' Batch result:', batchResult);
console.log(' ✓ Batch insert completed\n');
// Test 12: Persistence
console.log('12. Testing persistence:');
const tmpDir = os.tmpdir();
const dbPath = path.join(tmpDir, `ruvector-test-${Date.now()}.db`);
console.log(' Creating persistent database at:', dbPath);
const persistentDb = new GraphDatabase({
distanceMetric: 'Cosine',
dimensions: 3,
storagePath: dbPath
});
console.log(' isPersistent():', persistentDb.isPersistent());
console.log(' getStoragePath():', persistentDb.getStoragePath());
// Add data to persistent database
await persistentDb.createNode({
id: 'persistent_node_1',
embedding: new Float32Array([1.0, 0.5, 0.2]),
labels: ['PersistentTest'],
properties: { testKey: 'testValue' }
});
console.log(' Created node in persistent database');
const persistentStats = await persistentDb.stats();
console.log(' Persistent DB stats:', persistentStats);
// Test opening existing database
console.log(' Opening existing database with GraphDatabase.open()...');
const reopenedDb = GraphDatabase.open(dbPath);
console.log(' Reopened isPersistent():', reopenedDb.isPersistent());
// Cleanup
try {
fs.unlinkSync(dbPath);
console.log(' Cleaned up test database');
} catch (e) {
// Ignore cleanup errors
}
console.log(' ✓ Persistence test passed\n');
console.log('✅ All tests passed!');
} catch (error) {
console.error('❌ Test failed:', error);
process.exit(1);
}
})();