From ed2bdd014eabbc5887c1c11b6d52071a4c4d41e2 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 26 Nov 2025 12:54:04 +0000 Subject: [PATCH] docs: Add Cypher reference, include Tiny Dancer, fix WASM build - Create docs/api/CYPHER_REFERENCE.md with complete Cypher query guide - Update README to highlight all capabilities in core npx ruvector package - Add Tiny Dancer (AI agent routing) to features and comparison table - Fix ruvector-wasm insertBatch to use js_sys::Array instead of serde --- README.md | 21 ++- crates/ruvector-wasm/src/lib.rs | 8 +- docs/api/CYPHER_REFERENCE.md | 233 ++++++++++++++++++++++++++++++++ 3 files changed, 254 insertions(+), 8 deletions(-) create mode 100644 docs/api/CYPHER_REFERENCE.md diff --git a/README.md b/README.md index 24cd29cb..1caaf27a 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ npx ruvector ``` +> **All-in-One Package**: The core `ruvector` package includes everything — vector search, graph queries, GNN layers, tensor compression, and WASM support. No additional packages needed. + ## What Problem Does RuVector Solve? Traditional vector databases just store and search. When you ask "find similar items," they return results but never get smarter. @@ -22,6 +24,8 @@ Traditional vector databases just store and search. When you ask "find similar i 1. **Store vectors** like any vector DB (embeddings from OpenAI, Cohere, etc.) 2. **Query with Cypher** like Neo4j (`MATCH (a)-[:SIMILAR]->(b) RETURN b`) 3. **The index learns** — GNN layers make search results improve over time +4. **Compress automatically** — 2-32x memory reduction with adaptive tiered compression +5. **Run anywhere** — Node.js, browser (WASM), or native Rust Think of it as: **Pinecone + Neo4j + PyTorch** in one Rust package. @@ -55,6 +59,10 @@ const enhanced = layer.forward(query, neighbors, weights); // Compression (2-32x memory savings) const compressed = ruvector.compress(embedding, 0.3); + +// Tiny Dancer: AI agent routing +const router = new ruvector.Router(); +const decision = router.route(candidates, { optimize: 'cost' }); ``` ### Rust @@ -90,6 +98,7 @@ let enhanced = layer.forward(&query, &neighbors, &weights); | **Hyperedges** | Connect 3+ nodes at once | Model complex relationships | | **Tensor Compression** | f32→f16→PQ8→PQ4→Binary | 2-32x memory reduction | | **Differentiable Search** | Soft attention k-NN | End-to-end trainable | +| **Tiny Dancer** | FastGRNN neural routing | Optimize LLM inference costs | | **WASM/Browser** | Full client-side support | Run AI search offline | ## Comparison @@ -101,6 +110,7 @@ let enhanced = layer.forward(&query, &neighbors, &weights); | **Graph Queries** | ✅ Cypher | ❌ | ❌ | ❌ | ❌ | | **Hyperedges** | ✅ | ❌ | ❌ | ❌ | ❌ | | **Self-Learning (GNN)** | ✅ | ❌ | ❌ | ❌ | ❌ | +| **AI Agent Routing** | ✅ Tiny Dancer | ❌ | ❌ | ❌ | ❌ | | **Auto-Compression** | ✅ 2-32x | ❌ | ❌ | ✅ | ❌ | | **Browser/WASM** | ✅ | ❌ | ❌ | ❌ | ❌ | | **Differentiable** | ✅ | ❌ | ❌ | ❌ | ❌ | @@ -187,11 +197,12 @@ RETURN related ``` crates/ -├── ruvector-core/ # Vector DB engine (HNSW, storage) -├── ruvector-graph/ # Graph DB + Cypher parser -├── ruvector-gnn/ # GNN layers, compression, training -├── ruvector-gnn-wasm/ # WebAssembly bindings -└── ruvector-gnn-node/ # Node.js bindings (napi-rs) +├── ruvector-core/ # Vector DB engine (HNSW, storage) +├── ruvector-graph/ # Graph DB + Cypher parser + Hyperedges +├── ruvector-gnn/ # GNN layers, compression, training +├── ruvector-tiny-dancer-core/ # AI agent routing (FastGRNN) +├── ruvector-*-wasm/ # WebAssembly bindings +└── ruvector-*-node/ # Node.js bindings (napi-rs) ``` ## Contributing diff --git a/crates/ruvector-wasm/src/lib.rs b/crates/ruvector-wasm/src/lib.rs index 5d1d9859..221fbc9a 100644 --- a/crates/ruvector-wasm/src/lib.rs +++ b/crates/ruvector-wasm/src/lib.rs @@ -217,11 +217,13 @@ impl VectorDB { /// Array of vector IDs #[wasm_bindgen(js_name = insertBatch)] pub fn insert_batch(&self, entries: JsValue) -> Result, JsValue> { - let js_entries: Vec = from_value(entries) - .map_err(|e| JsValue::from_str(&format!("Invalid entries array: {}", e)))?; + // Convert JsValue to Array using reflection + let entries_array: js_sys::Array = entries.dyn_into() + .map_err(|_| JsValue::from_str("entries must be an array"))?; let mut vector_entries = Vec::new(); - for js_entry in js_entries { + for i in 0..entries_array.length() { + let js_entry = entries_array.get(i); let vector_arr: Float32Array = Reflect::get(&js_entry, &"vector".into())?.dyn_into()?; let id: Option = Reflect::get(&js_entry, &"id".into())?.as_string(); let metadata = Reflect::get(&js_entry, &"metadata".into()).ok(); diff --git a/docs/api/CYPHER_REFERENCE.md b/docs/api/CYPHER_REFERENCE.md new file mode 100644 index 00000000..664a2f99 --- /dev/null +++ b/docs/api/CYPHER_REFERENCE.md @@ -0,0 +1,233 @@ +# Cypher Query Language Reference + +RuVector implements a Neo4j-compatible Cypher query language with extensions for hyperedges. + +## Quick Examples + +```cypher +-- Create nodes +CREATE (p:Person {name: 'Alice', age: 30}) + +-- Create relationships +CREATE (a:Person {name: 'Alice'})-[:KNOWS]->(b:Person {name: 'Bob'}) + +-- Pattern matching +MATCH (p:Person)-[:KNOWS]->(friend) +WHERE p.name = 'Alice' +RETURN friend.name + +-- Hyperedges (N-ary relationships) +CREATE (a:Person)-[:ATTENDED]->(meeting:Meeting, room:Room, company:Company) +``` + +## Supported Clauses + +### MATCH + +Find patterns in the graph. + +```cypher +-- Simple node +MATCH (n:Person) +RETURN n + +-- With relationship +MATCH (a:Person)-[:KNOWS]->(b:Person) +RETURN a.name, b.name + +-- Variable-length paths +MATCH (a)-[*1..3]->(b) +RETURN a, b + +-- Optional matching +OPTIONAL MATCH (p:Person)-[:OWNS]->(c:Car) +RETURN p.name, c.model +``` + +### CREATE + +Create new nodes and relationships. + +```cypher +-- Single node +CREATE (n:Person {name: 'Alice'}) + +-- Multiple labels +CREATE (n:Person:Employee {name: 'Bob'}) + +-- With relationship +CREATE (a:Person {name: 'Alice'})-[:FRIEND]->(b:Person {name: 'Bob'}) +``` + +### MERGE + +Create if not exists, match if exists. + +```cypher +MERGE (p:Person {email: 'alice@example.com'}) +ON CREATE SET p.created = timestamp() +ON MATCH SET p.lastSeen = timestamp() +``` + +### SET + +Update properties. + +```cypher +MATCH (p:Person {name: 'Alice'}) +SET p.age = 31, p.updated = timestamp() +``` + +### DELETE + +Remove nodes and relationships. + +```cypher +-- Delete node (must have no relationships) +MATCH (p:Person {name: 'Temp'}) +DELETE p + +-- Delete node and all relationships +MATCH (p:Person {name: 'Temp'}) +DETACH DELETE p +``` + +### RETURN + +Project results. + +```cypher +MATCH (p:Person) +RETURN p.name AS name, p.age +ORDER BY p.age DESC +SKIP 10 +LIMIT 5 +``` + +### WITH + +Intermediate projection (query chaining). + +```cypher +MATCH (p:Person) +WITH p.name AS name, COUNT(*) AS count +WHERE count > 1 +RETURN name, count +``` + +### WHERE + +Filter results. + +```cypher +MATCH (p:Person) +WHERE p.age > 21 AND p.city = 'NYC' +RETURN p + +-- Pattern predicates +WHERE (p)-[:KNOWS]->(:Expert) +``` + +## Hyperedges (N-ary Relationships) + +RuVector extends Cypher with hyperedge support for N-ary relationships: + +```cypher +-- Create hyperedge (3+ nodes) +CREATE (author:Person)-[:WROTE]->(paper:Paper, journal:Journal, year:Year) + +-- Match hyperedge +MATCH (a:Person)-[r:ATTENDED]->(meeting, room, company) +RETURN a.name, meeting.topic, room.number +``` + +## Expressions + +### Operators + +| Type | Operators | +|------|-----------| +| Arithmetic | `+`, `-`, `*`, `/`, `%`, `^` | +| Comparison | `=`, `<>`, `<`, `>`, `<=`, `>=` | +| Logical | `AND`, `OR`, `NOT`, `XOR` | +| String | `STARTS WITH`, `ENDS WITH`, `CONTAINS` | +| Null | `IS NULL`, `IS NOT NULL` | +| List | `IN`, `[]` (indexing) | + +### Functions + +```cypher +-- String +RETURN toUpper(name), toLower(name), trim(name), substring(name, 0, 5) + +-- Numeric +RETURN abs(x), ceil(x), floor(x), round(x), sqrt(x) + +-- Collections +RETURN size(list), head(list), tail(list), range(1, 10) + +-- Type +RETURN type(r), labels(n), keys(n) +``` + +### Aggregations + +```cypher +MATCH (p:Person) +RETURN + COUNT(*) AS total, + AVG(p.age) AS avgAge, + MIN(p.age) AS minAge, + MAX(p.age) AS maxAge, + SUM(p.salary) AS totalSalary, + COLLECT(p.name) AS names +``` + +### CASE Expressions + +```cypher +MATCH (p:Person) +RETURN p.name, + CASE + WHEN p.age < 18 THEN 'minor' + WHEN p.age < 65 THEN 'adult' + ELSE 'senior' + END AS category +``` + +## Data Types + +| Type | Example | +|------|---------| +| Integer | `42`, `-17` | +| Float | `3.14`, `-2.5e10` | +| String | `'hello'`, `"world"` | +| Boolean | `true`, `false` | +| Null | `null` | +| List | `[1, 2, 3]`, `['a', 'b']` | +| Map | `{name: 'Alice', age: 30}` | + +## Path Variables + +```cypher +-- Assign path to variable +MATCH p = (a:Person)-[:KNOWS*]->(b:Person) +RETURN p, length(p) + +-- Path functions +RETURN nodes(p), relationships(p), length(p) +``` + +## Best Practices + +1. **Use labels** - Always specify node labels for faster lookups +2. **Index properties** - Create indexes on frequently queried properties +3. **Limit results** - Use `LIMIT` to avoid large result sets +4. **Parameterize** - Use parameters for values to enable query caching + +## See Also + +- [Getting Started Guide](../guide/GETTING_STARTED.md) +- [Node.js API](./NODEJS_API.md) +- [Rust API](./RUST_API.md) +- [GNN Architecture](../gnn-layer-implementation.md)