@ruvector/wasm 0.1.16 β 0.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +22 -33
- package/README.md +0 -969
- package/pkg/README.md +0 -969
- package/src/indexeddb.js +0 -355
- package/src/worker-pool.js +0 -254
- package/src/worker.js +0 -184
package/README.md
DELETED
|
@@ -1,969 +0,0 @@
|
|
|
1
|
-
# Ruvector WASM
|
|
2
|
-
|
|
3
|
-
[](https://opensource.org/licenses/MIT)
|
|
4
|
-
[](https://www.npmjs.com/package/@ruvector/wasm)
|
|
5
|
-
[](#bundle-size)
|
|
6
|
-
[](#browser-compatibility)
|
|
7
|
-
[](https://webassembly.org/)
|
|
8
|
-
|
|
9
|
-
**High-performance vector database running entirely in your browser via WebAssembly.**
|
|
10
|
-
|
|
11
|
-
> Bring **sub-millisecond vector search** to the edge with **offline-first** capabilities. Perfect for AI applications, semantic search, and recommendation engines that run completely client-side. Built by [rUv](https://ruv.io) with Rust and WebAssembly.
|
|
12
|
-
|
|
13
|
-
## π Why Ruvector WASM?
|
|
14
|
-
|
|
15
|
-
In the age of privacy-first, offline-capable web applications, running AI workloads **entirely in the browser** is no longer optionalβit's essential.
|
|
16
|
-
|
|
17
|
-
**Ruvector WASM brings enterprise-grade vector search to the browser:**
|
|
18
|
-
|
|
19
|
-
- β‘ **Blazing Fast**: <1ms query latency with HNSW indexing and SIMD acceleration
|
|
20
|
-
- π **Privacy First**: All data stays in the browserβzero server round-trips
|
|
21
|
-
- π΄ **Offline Capable**: Full functionality without internet via IndexedDB persistence
|
|
22
|
-
- π **Edge Computing**: Deploy to CDNs for ultra-low latency globally
|
|
23
|
-
- πΎ **Persistent Storage**: IndexedDB integration with automatic synchronization
|
|
24
|
-
- π§΅ **Multi-threaded**: Web Workers support for parallel processing
|
|
25
|
-
- π¦ **Compact**: <400KB gzipped with optimizations
|
|
26
|
-
- π― **Zero Dependencies**: Pure Rust compiled to WebAssembly
|
|
27
|
-
|
|
28
|
-
## π Features
|
|
29
|
-
|
|
30
|
-
### Core Capabilities
|
|
31
|
-
|
|
32
|
-
- **Complete VectorDB API**: Insert, search, delete, batch operations with familiar patterns
|
|
33
|
-
- **HNSW Indexing**: Hierarchical Navigable Small World for fast approximate nearest neighbor search
|
|
34
|
-
- **Multiple Distance Metrics**: Euclidean, Cosine, Dot Product, Manhattan
|
|
35
|
-
- **SIMD Acceleration**: 2-4x speedup on supported hardware with automatic detection
|
|
36
|
-
- **Memory Efficient**: Optimized memory layouts and zero-copy operations
|
|
37
|
-
- **Type-Safe**: Full TypeScript definitions included
|
|
38
|
-
|
|
39
|
-
### Browser-Specific Features
|
|
40
|
-
|
|
41
|
-
- **IndexedDB Persistence**: Save/load database state with progressive loading
|
|
42
|
-
- **Web Workers Integration**: Parallel operations across multiple threads
|
|
43
|
-
- **Worker Pool Management**: Automatic load balancing across 4-8 workers
|
|
44
|
-
- **Zero-Copy Transfers**: Transferable objects for efficient data passing
|
|
45
|
-
- **Browser Console Debugging**: Enhanced error messages and stack traces
|
|
46
|
-
- **Progressive Web Apps**: Perfect for PWA offline scenarios
|
|
47
|
-
|
|
48
|
-
### Performance Optimizations
|
|
49
|
-
|
|
50
|
-
- **Batch Operations**: Efficient bulk insert/search for large datasets
|
|
51
|
-
- **LRU Caching**: 1000-entry hot vector cache for frequently accessed data
|
|
52
|
-
- **Lazy Loading**: Progressive data loading with callbacks
|
|
53
|
-
- **Compressed Storage**: Optimized serialization for IndexedDB
|
|
54
|
-
- **WASM Streaming**: Compile WASM modules while downloading
|
|
55
|
-
|
|
56
|
-
## π¦ Installation
|
|
57
|
-
|
|
58
|
-
### NPM
|
|
59
|
-
|
|
60
|
-
```bash
|
|
61
|
-
npm install @ruvector/wasm
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
### Yarn
|
|
65
|
-
|
|
66
|
-
```bash
|
|
67
|
-
yarn add @ruvector/wasm
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
### CDN (for quick prototyping)
|
|
71
|
-
|
|
72
|
-
```html
|
|
73
|
-
<script type="module">
|
|
74
|
-
import init, { VectorDB } from 'https://unpkg.com/@ruvector/wasm/pkg/ruvector_wasm.js';
|
|
75
|
-
|
|
76
|
-
await init();
|
|
77
|
-
const db = new VectorDB(384, 'cosine', true);
|
|
78
|
-
</script>
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
## β‘ Quick Start
|
|
82
|
-
|
|
83
|
-
### Basic Usage
|
|
84
|
-
|
|
85
|
-
```javascript
|
|
86
|
-
import init, { VectorDB } from '@ruvector/wasm';
|
|
87
|
-
|
|
88
|
-
// 1. Initialize WASM module (one-time setup)
|
|
89
|
-
await init();
|
|
90
|
-
|
|
91
|
-
// 2. Create database with 384-dimensional vectors
|
|
92
|
-
const db = new VectorDB(
|
|
93
|
-
384, // dimensions
|
|
94
|
-
'cosine', // distance metric
|
|
95
|
-
true // enable HNSW index
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
// 3. Insert vectors with metadata
|
|
99
|
-
const embedding = new Float32Array(384).map(() => Math.random());
|
|
100
|
-
const id = db.insert(
|
|
101
|
-
embedding,
|
|
102
|
-
'doc_1', // optional ID
|
|
103
|
-
{ title: 'My Document', type: 'article' } // optional metadata
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
// 4. Search for similar vectors
|
|
107
|
-
const query = new Float32Array(384).map(() => Math.random());
|
|
108
|
-
const results = db.search(query, 10); // top 10 results
|
|
109
|
-
|
|
110
|
-
// 5. Process results
|
|
111
|
-
results.forEach(result => {
|
|
112
|
-
console.log(`ID: ${result.id}`);
|
|
113
|
-
console.log(`Score: ${result.score}`);
|
|
114
|
-
console.log(`Metadata:`, result.metadata);
|
|
115
|
-
});
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### React Integration
|
|
119
|
-
|
|
120
|
-
```typescript
|
|
121
|
-
import { useEffect, useState } from 'react';
|
|
122
|
-
import init, { VectorDB } from '@ruvector/wasm';
|
|
123
|
-
|
|
124
|
-
function SemanticSearch() {
|
|
125
|
-
const [db, setDb] = useState<VectorDB | null>(null);
|
|
126
|
-
const [results, setResults] = useState([]);
|
|
127
|
-
const [loading, setLoading] = useState(true);
|
|
128
|
-
|
|
129
|
-
useEffect(() => {
|
|
130
|
-
// Initialize WASM and create database
|
|
131
|
-
init().then(() => {
|
|
132
|
-
const vectorDB = new VectorDB(384, 'cosine', true);
|
|
133
|
-
setDb(vectorDB);
|
|
134
|
-
setLoading(false);
|
|
135
|
-
});
|
|
136
|
-
}, []);
|
|
137
|
-
|
|
138
|
-
const handleSearch = async (queryEmbedding: Float32Array) => {
|
|
139
|
-
if (!db) return;
|
|
140
|
-
|
|
141
|
-
const searchResults = db.search(queryEmbedding, 10);
|
|
142
|
-
setResults(searchResults);
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
if (loading) return <div>Loading vector database...</div>;
|
|
146
|
-
|
|
147
|
-
return (
|
|
148
|
-
<div>
|
|
149
|
-
<h1>Semantic Search</h1>
|
|
150
|
-
{/* Your search UI */}
|
|
151
|
-
</div>
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
### Vue.js Integration
|
|
157
|
-
|
|
158
|
-
```vue
|
|
159
|
-
<template>
|
|
160
|
-
<div>
|
|
161
|
-
<h1>Vector Search</h1>
|
|
162
|
-
<div v-if="!dbReady">Initializing...</div>
|
|
163
|
-
<div v-else>
|
|
164
|
-
<button @click="search">Search</button>
|
|
165
|
-
<ul>
|
|
166
|
-
<li v-for="result in results" :key="result.id">
|
|
167
|
-
{{ result.id }}: {{ result.score }}
|
|
168
|
-
</li>
|
|
169
|
-
</ul>
|
|
170
|
-
</div>
|
|
171
|
-
</div>
|
|
172
|
-
</template>
|
|
173
|
-
|
|
174
|
-
<script setup>
|
|
175
|
-
import { ref, onMounted } from 'vue';
|
|
176
|
-
import init, { VectorDB } from '@ruvector/wasm';
|
|
177
|
-
|
|
178
|
-
const db = ref(null);
|
|
179
|
-
const dbReady = ref(false);
|
|
180
|
-
const results = ref([]);
|
|
181
|
-
|
|
182
|
-
onMounted(async () => {
|
|
183
|
-
await init();
|
|
184
|
-
db.value = new VectorDB(384, 'cosine', true);
|
|
185
|
-
dbReady.value = true;
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
const search = () => {
|
|
189
|
-
const query = new Float32Array(384).map(() => Math.random());
|
|
190
|
-
results.value = db.value.search(query, 10);
|
|
191
|
-
};
|
|
192
|
-
</script>
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
### Svelte Integration
|
|
196
|
-
|
|
197
|
-
```svelte
|
|
198
|
-
<script>
|
|
199
|
-
import { onMount } from 'svelte';
|
|
200
|
-
import init, { VectorDB } from '@ruvector/wasm';
|
|
201
|
-
|
|
202
|
-
let db = null;
|
|
203
|
-
let ready = false;
|
|
204
|
-
let results = [];
|
|
205
|
-
|
|
206
|
-
onMount(async () => {
|
|
207
|
-
await init();
|
|
208
|
-
db = new VectorDB(384, 'cosine', true);
|
|
209
|
-
ready = true;
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
function search() {
|
|
213
|
-
const query = new Float32Array(384).map(() => Math.random());
|
|
214
|
-
results = db.search(query, 10);
|
|
215
|
-
}
|
|
216
|
-
</script>
|
|
217
|
-
|
|
218
|
-
{#if !ready}
|
|
219
|
-
<p>Loading...</p>
|
|
220
|
-
{:else}
|
|
221
|
-
<button on:click={search}>Search</button>
|
|
222
|
-
{#each results as result}
|
|
223
|
-
<div>{result.id}: {result.score}</div>
|
|
224
|
-
{/each}
|
|
225
|
-
{/if}
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
## π₯ Advanced Usage
|
|
229
|
-
|
|
230
|
-
### Web Workers for Background Processing
|
|
231
|
-
|
|
232
|
-
Offload heavy vector operations to background threads for smooth UI performance:
|
|
233
|
-
|
|
234
|
-
```javascript
|
|
235
|
-
// main.js
|
|
236
|
-
import { WorkerPool } from '@ruvector/wasm/worker-pool';
|
|
237
|
-
|
|
238
|
-
const pool = new WorkerPool(
|
|
239
|
-
'/worker.js',
|
|
240
|
-
'/pkg/ruvector_wasm.js',
|
|
241
|
-
{
|
|
242
|
-
poolSize: navigator.hardwareConcurrency || 4, // Auto-detect CPU cores
|
|
243
|
-
dimensions: 384,
|
|
244
|
-
metric: 'cosine',
|
|
245
|
-
useHnsw: true
|
|
246
|
-
}
|
|
247
|
-
);
|
|
248
|
-
|
|
249
|
-
// Initialize worker pool
|
|
250
|
-
await pool.init();
|
|
251
|
-
|
|
252
|
-
// Batch insert in parallel (non-blocking)
|
|
253
|
-
const vectors = generateVectors(10000, 384);
|
|
254
|
-
const ids = await pool.insertBatch(vectors);
|
|
255
|
-
|
|
256
|
-
// Parallel search across workers
|
|
257
|
-
const query = new Float32Array(384).map(() => Math.random());
|
|
258
|
-
const results = await pool.search(query, 100);
|
|
259
|
-
|
|
260
|
-
// Get pool statistics
|
|
261
|
-
const stats = pool.getStats();
|
|
262
|
-
console.log(`Workers: ${stats.busyWorkers}/${stats.poolSize} busy`);
|
|
263
|
-
console.log(`Queue: ${stats.queuedTasks} tasks waiting`);
|
|
264
|
-
|
|
265
|
-
// Cleanup when done
|
|
266
|
-
pool.terminate();
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
```javascript
|
|
270
|
-
// worker.js - Web Worker implementation
|
|
271
|
-
importScripts('/pkg/ruvector_wasm.js');
|
|
272
|
-
|
|
273
|
-
const { VectorDB } = wasm_bindgen;
|
|
274
|
-
|
|
275
|
-
let db = null;
|
|
276
|
-
|
|
277
|
-
self.onmessage = async (e) => {
|
|
278
|
-
const { type, data } = e.data;
|
|
279
|
-
|
|
280
|
-
switch (type) {
|
|
281
|
-
case 'init':
|
|
282
|
-
await wasm_bindgen('/pkg/ruvector_wasm_bg.wasm');
|
|
283
|
-
db = new VectorDB(data.dimensions, data.metric, data.useHnsw);
|
|
284
|
-
self.postMessage({ type: 'ready' });
|
|
285
|
-
break;
|
|
286
|
-
|
|
287
|
-
case 'insert':
|
|
288
|
-
const id = db.insert(data.vector, data.id, data.metadata);
|
|
289
|
-
self.postMessage({ type: 'inserted', id });
|
|
290
|
-
break;
|
|
291
|
-
|
|
292
|
-
case 'search':
|
|
293
|
-
const results = db.search(data.query, data.k);
|
|
294
|
-
self.postMessage({ type: 'results', results });
|
|
295
|
-
break;
|
|
296
|
-
}
|
|
297
|
-
};
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
### IndexedDB Persistence - Offline First
|
|
301
|
-
|
|
302
|
-
Keep your vector database synchronized across sessions:
|
|
303
|
-
|
|
304
|
-
```javascript
|
|
305
|
-
import { IndexedDBPersistence } from '@ruvector/wasm/indexeddb';
|
|
306
|
-
import init, { VectorDB } from '@ruvector/wasm';
|
|
307
|
-
|
|
308
|
-
await init();
|
|
309
|
-
|
|
310
|
-
// Create persistence layer
|
|
311
|
-
const persistence = new IndexedDBPersistence('my_vector_db', {
|
|
312
|
-
version: 1,
|
|
313
|
-
cacheSize: 1000, // LRU cache for hot vectors
|
|
314
|
-
batchSize: 100 // Batch size for bulk operations
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
await persistence.open();
|
|
318
|
-
|
|
319
|
-
// Create or restore VectorDB
|
|
320
|
-
const db = new VectorDB(384, 'cosine', true);
|
|
321
|
-
|
|
322
|
-
// Load existing data from IndexedDB (with progress)
|
|
323
|
-
await persistence.loadAll(async (progress) => {
|
|
324
|
-
console.log(`Loading: ${progress.loaded}/${progress.total} vectors`);
|
|
325
|
-
console.log(`Progress: ${(progress.percent * 100).toFixed(1)}%`);
|
|
326
|
-
|
|
327
|
-
// Insert batch into VectorDB
|
|
328
|
-
if (progress.vectors.length > 0) {
|
|
329
|
-
const ids = db.insertBatch(progress.vectors);
|
|
330
|
-
console.log(`Inserted ${ids.length} vectors`);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
if (progress.complete) {
|
|
334
|
-
console.log('Database fully loaded!');
|
|
335
|
-
}
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
// Insert new vectors and save to IndexedDB
|
|
339
|
-
const vector = new Float32Array(384).map(() => Math.random());
|
|
340
|
-
const id = db.insert(vector, 'vec_123', { category: 'new' });
|
|
341
|
-
|
|
342
|
-
await persistence.save({
|
|
343
|
-
id,
|
|
344
|
-
vector,
|
|
345
|
-
metadata: { category: 'new' }
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
// Batch save for better performance
|
|
349
|
-
const entries = [...]; // Your vector entries
|
|
350
|
-
await persistence.saveBatch(entries);
|
|
351
|
-
|
|
352
|
-
// Get storage statistics
|
|
353
|
-
const stats = await persistence.getStats();
|
|
354
|
-
console.log(`Total vectors: ${stats.totalVectors}`);
|
|
355
|
-
console.log(`Storage used: ${(stats.storageBytes / 1024 / 1024).toFixed(2)} MB`);
|
|
356
|
-
console.log(`Cache size: ${stats.cacheSize}`);
|
|
357
|
-
console.log(`Cache hit rate: ${(stats.cacheHitRate * 100).toFixed(2)}%`);
|
|
358
|
-
|
|
359
|
-
// Clear old data
|
|
360
|
-
await persistence.clear();
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
### Batch Operations for Performance
|
|
364
|
-
|
|
365
|
-
Process large datasets efficiently:
|
|
366
|
-
|
|
367
|
-
```javascript
|
|
368
|
-
import init, { VectorDB } from '@ruvector/wasm';
|
|
369
|
-
|
|
370
|
-
await init();
|
|
371
|
-
const db = new VectorDB(384, 'cosine', true);
|
|
372
|
-
|
|
373
|
-
// Batch insert (10x faster than individual inserts)
|
|
374
|
-
const entries = [];
|
|
375
|
-
for (let i = 0; i < 10000; i++) {
|
|
376
|
-
entries.push({
|
|
377
|
-
vector: new Float32Array(384).map(() => Math.random()),
|
|
378
|
-
id: `vec_${i}`,
|
|
379
|
-
metadata: { index: i, batch: Math.floor(i / 100) }
|
|
380
|
-
});
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
const ids = db.insertBatch(entries);
|
|
384
|
-
console.log(`Inserted ${ids.length} vectors in batch`);
|
|
385
|
-
|
|
386
|
-
// Multiple parallel searches
|
|
387
|
-
const queries = Array.from({ length: 100 }, () =>
|
|
388
|
-
new Float32Array(384).map(() => Math.random())
|
|
389
|
-
);
|
|
390
|
-
|
|
391
|
-
const allResults = queries.map(query => db.search(query, 10));
|
|
392
|
-
console.log(`Completed ${allResults.length} searches`);
|
|
393
|
-
```
|
|
394
|
-
|
|
395
|
-
### Memory Management Best Practices
|
|
396
|
-
|
|
397
|
-
```javascript
|
|
398
|
-
import init, { VectorDB } from '@ruvector/wasm';
|
|
399
|
-
|
|
400
|
-
await init();
|
|
401
|
-
|
|
402
|
-
// Reuse Float32Array buffers to reduce GC pressure
|
|
403
|
-
const buffer = new Float32Array(384);
|
|
404
|
-
|
|
405
|
-
// Insert with reused buffer
|
|
406
|
-
for (let i = 0; i < 1000; i++) {
|
|
407
|
-
// Fill buffer with new data
|
|
408
|
-
for (let j = 0; j < 384; j++) {
|
|
409
|
-
buffer[j] = Math.random();
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
db.insert(buffer, `vec_${i}`, { index: i });
|
|
413
|
-
|
|
414
|
-
// Buffer is copied internally, safe to reuse
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// Check memory usage
|
|
418
|
-
const vectorCount = db.len();
|
|
419
|
-
const isEmpty = db.isEmpty();
|
|
420
|
-
const dimensions = db.dimensions;
|
|
421
|
-
|
|
422
|
-
console.log(`Vectors: ${vectorCount}, Dims: ${dimensions}`);
|
|
423
|
-
|
|
424
|
-
// Clean up when done
|
|
425
|
-
// JavaScript GC will handle WASM memory automatically
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
## π Performance Benchmarks
|
|
429
|
-
|
|
430
|
-
### Browser Performance (Chrome 120 on M1 MacBook Pro)
|
|
431
|
-
|
|
432
|
-
| Operation | Vectors | Dimensions | Standard | SIMD | Speedup |
|
|
433
|
-
|-----------|---------|------------|----------|------|---------|
|
|
434
|
-
| **Insert (individual)** | 10,000 | 384 | 3.2s | 1.1s | 2.9x |
|
|
435
|
-
| **Insert (batch)** | 10,000 | 384 | 1.2s | 0.4s | 3.0x |
|
|
436
|
-
| **Search (k=10)** | 100 queries | 384 | 0.5s | 0.2s | 2.5x |
|
|
437
|
-
| **Search (k=100)** | 100 queries | 384 | 1.8s | 0.7s | 2.6x |
|
|
438
|
-
| **Delete** | 1,000 | 384 | 0.2s | 0.1s | 2.0x |
|
|
439
|
-
|
|
440
|
-
### Throughput Comparison
|
|
441
|
-
|
|
442
|
-
```
|
|
443
|
-
Operation Ruvector WASM Tensorflow.js ml5.js
|
|
444
|
-
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
445
|
-
Insert (ops/sec) 25,000 5,000 1,200
|
|
446
|
-
Search (queries/sec) 500 80 20
|
|
447
|
-
Memory (10K vectors) ~50MB ~200MB ~150MB
|
|
448
|
-
Bundle Size (gzipped) 380KB 800KB 450KB
|
|
449
|
-
Offline Support β
Partial β
|
|
450
|
-
SIMD Acceleration β
β β
|
|
451
|
-
```
|
|
452
|
-
|
|
453
|
-
### Real-World Application Performance
|
|
454
|
-
|
|
455
|
-
**Semantic Search (10,000 documents, 384-dim embeddings)**
|
|
456
|
-
- Cold start: ~800ms (WASM compile + data load)
|
|
457
|
-
- Warm query: <5ms (with HNSW index)
|
|
458
|
-
- IndexedDB load: ~2s (10,000 vectors)
|
|
459
|
-
- Memory footprint: ~60MB
|
|
460
|
-
|
|
461
|
-
**Recommendation Engine (100,000 items, 128-dim embeddings)**
|
|
462
|
-
- Initial load: ~8s from IndexedDB
|
|
463
|
-
- Query latency: <10ms (p50)
|
|
464
|
-
- Memory usage: ~180MB
|
|
465
|
-
- Bundle impact: +400KB gzipped
|
|
466
|
-
|
|
467
|
-
## π Browser Compatibility
|
|
468
|
-
|
|
469
|
-
### Support Matrix
|
|
470
|
-
|
|
471
|
-
| Browser | Version | WASM | SIMD | Workers | IndexedDB | Status |
|
|
472
|
-
|---------|---------|------|------|---------|-----------|--------|
|
|
473
|
-
| **Chrome** | 91+ | β
| β
| β
| β
| Full Support |
|
|
474
|
-
| **Firefox** | 89+ | β
| β
| β
| β
| Full Support |
|
|
475
|
-
| **Safari** | 16.4+ | β
| Partial | β
| β
| Limited SIMD |
|
|
476
|
-
| **Edge** | 91+ | β
| β
| β
| β
| Full Support |
|
|
477
|
-
| **Opera** | 77+ | β
| β
| β
| β
| Full Support |
|
|
478
|
-
| **Samsung Internet** | 15+ | β
| β | β
| β
| No SIMD |
|
|
479
|
-
|
|
480
|
-
### SIMD Support Detection
|
|
481
|
-
|
|
482
|
-
```javascript
|
|
483
|
-
import { detectSIMD } from '@ruvector/wasm';
|
|
484
|
-
|
|
485
|
-
if (detectSIMD()) {
|
|
486
|
-
console.log('SIMD acceleration available!');
|
|
487
|
-
// Load SIMD-optimized build
|
|
488
|
-
await import('@ruvector/wasm/pkg-simd/ruvector_wasm.js');
|
|
489
|
-
} else {
|
|
490
|
-
console.log('Standard build');
|
|
491
|
-
// Load standard build
|
|
492
|
-
await import('@ruvector/wasm');
|
|
493
|
-
}
|
|
494
|
-
```
|
|
495
|
-
|
|
496
|
-
### Polyfills and Fallbacks
|
|
497
|
-
|
|
498
|
-
```javascript
|
|
499
|
-
// Check for required features
|
|
500
|
-
const hasWASM = typeof WebAssembly !== 'undefined';
|
|
501
|
-
const hasWorkers = typeof Worker !== 'undefined';
|
|
502
|
-
const hasIndexedDB = typeof indexedDB !== 'undefined';
|
|
503
|
-
|
|
504
|
-
if (!hasWASM) {
|
|
505
|
-
console.error('WebAssembly not supported');
|
|
506
|
-
// Fallback to server-side processing
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
if (!hasWorkers) {
|
|
510
|
-
console.warn('Web Workers not available, using main thread');
|
|
511
|
-
// Use synchronous API
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
if (!hasIndexedDB) {
|
|
515
|
-
console.warn('IndexedDB not available, data will not persist');
|
|
516
|
-
// Use in-memory only
|
|
517
|
-
}
|
|
518
|
-
```
|
|
519
|
-
|
|
520
|
-
## π¦ Bundle Size
|
|
521
|
-
|
|
522
|
-
### Production Build Sizes
|
|
523
|
-
|
|
524
|
-
```
|
|
525
|
-
Build Type Uncompressed Gzipped Brotli
|
|
526
|
-
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
527
|
-
Standard WASM 1.2 MB 450 KB 380 KB
|
|
528
|
-
SIMD WASM 1.3 MB 480 KB 410 KB
|
|
529
|
-
JavaScript Glue 45 KB 12 KB 9 KB
|
|
530
|
-
TypeScript Definitions 8 KB 2 KB 1.5 KB
|
|
531
|
-
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
532
|
-
Total (Standard) 1.25 MB 462 KB 390 KB
|
|
533
|
-
Total (SIMD) 1.35 MB 492 KB 420 KB
|
|
534
|
-
```
|
|
535
|
-
|
|
536
|
-
### With Optimizations (wasm-opt)
|
|
537
|
-
|
|
538
|
-
```bash
|
|
539
|
-
npm run optimize
|
|
540
|
-
```
|
|
541
|
-
|
|
542
|
-
```
|
|
543
|
-
Optimized Build Uncompressed Gzipped Brotli
|
|
544
|
-
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
545
|
-
Standard WASM 900 KB 380 KB 320 KB
|
|
546
|
-
SIMD WASM 980 KB 410 KB 350 KB
|
|
547
|
-
```
|
|
548
|
-
|
|
549
|
-
### Code Splitting Strategy
|
|
550
|
-
|
|
551
|
-
```javascript
|
|
552
|
-
// Lazy load WASM module when needed
|
|
553
|
-
const loadVectorDB = async () => {
|
|
554
|
-
const { default: init, VectorDB } = await import('@ruvector/wasm');
|
|
555
|
-
await init();
|
|
556
|
-
return VectorDB;
|
|
557
|
-
};
|
|
558
|
-
|
|
559
|
-
// Use in your application
|
|
560
|
-
button.addEventListener('click', async () => {
|
|
561
|
-
const VectorDB = await loadVectorDB();
|
|
562
|
-
const db = new VectorDB(384, 'cosine', true);
|
|
563
|
-
// Use db...
|
|
564
|
-
});
|
|
565
|
-
```
|
|
566
|
-
|
|
567
|
-
## π¨ Building from Source
|
|
568
|
-
|
|
569
|
-
### Prerequisites
|
|
570
|
-
|
|
571
|
-
- **Rust**: 1.77 or higher
|
|
572
|
-
- **wasm-pack**: Latest version
|
|
573
|
-
- **Node.js**: 18.0 or higher
|
|
574
|
-
|
|
575
|
-
```bash
|
|
576
|
-
# Install wasm-pack
|
|
577
|
-
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
|
578
|
-
|
|
579
|
-
# Or via npm
|
|
580
|
-
npm install -g wasm-pack
|
|
581
|
-
```
|
|
582
|
-
|
|
583
|
-
### Build Commands
|
|
584
|
-
|
|
585
|
-
```bash
|
|
586
|
-
# Clone repository
|
|
587
|
-
git clone https://github.com/ruvnet/ruvector.git
|
|
588
|
-
cd ruvector/crates/ruvector-wasm
|
|
589
|
-
|
|
590
|
-
# Install dependencies
|
|
591
|
-
npm install
|
|
592
|
-
|
|
593
|
-
# Build for web (ES modules)
|
|
594
|
-
npm run build:web
|
|
595
|
-
|
|
596
|
-
# Build with SIMD optimizations
|
|
597
|
-
npm run build:simd
|
|
598
|
-
|
|
599
|
-
# Build for Node.js
|
|
600
|
-
npm run build:node
|
|
601
|
-
|
|
602
|
-
# Build for bundlers (webpack, rollup, etc.)
|
|
603
|
-
npm run build:bundler
|
|
604
|
-
|
|
605
|
-
# Build all targets
|
|
606
|
-
npm run build:all
|
|
607
|
-
|
|
608
|
-
# Run tests in browser
|
|
609
|
-
npm test
|
|
610
|
-
|
|
611
|
-
# Run tests in Node.js
|
|
612
|
-
npm run test:node
|
|
613
|
-
|
|
614
|
-
# Check bundle size
|
|
615
|
-
npm run size
|
|
616
|
-
|
|
617
|
-
# Optimize with wasm-opt (requires binaryen)
|
|
618
|
-
npm run optimize
|
|
619
|
-
|
|
620
|
-
# Serve examples locally
|
|
621
|
-
npm run serve
|
|
622
|
-
```
|
|
623
|
-
|
|
624
|
-
### Development Workflow
|
|
625
|
-
|
|
626
|
-
```bash
|
|
627
|
-
# Watch mode (requires custom setup)
|
|
628
|
-
wasm-pack build --dev --target web -- --features simd
|
|
629
|
-
|
|
630
|
-
# Run specific browser tests
|
|
631
|
-
npm run test:firefox
|
|
632
|
-
|
|
633
|
-
# Profile WASM performance
|
|
634
|
-
wasm-pack build --profiling --target web
|
|
635
|
-
|
|
636
|
-
# Generate documentation
|
|
637
|
-
cargo doc --no-deps --open
|
|
638
|
-
```
|
|
639
|
-
|
|
640
|
-
### Custom Build Configuration
|
|
641
|
-
|
|
642
|
-
```toml
|
|
643
|
-
# .cargo/config.toml
|
|
644
|
-
[target.wasm32-unknown-unknown]
|
|
645
|
-
rustflags = [
|
|
646
|
-
"-C", "opt-level=z",
|
|
647
|
-
"-C", "lto=fat",
|
|
648
|
-
"-C", "codegen-units=1"
|
|
649
|
-
]
|
|
650
|
-
```
|
|
651
|
-
|
|
652
|
-
## π API Reference
|
|
653
|
-
|
|
654
|
-
### VectorDB Class
|
|
655
|
-
|
|
656
|
-
```typescript
|
|
657
|
-
class VectorDB {
|
|
658
|
-
constructor(
|
|
659
|
-
dimensions: number,
|
|
660
|
-
metric?: 'euclidean' | 'cosine' | 'dotproduct' | 'manhattan',
|
|
661
|
-
useHnsw?: boolean
|
|
662
|
-
);
|
|
663
|
-
|
|
664
|
-
// Insert operations
|
|
665
|
-
insert(vector: Float32Array, id?: string, metadata?: object): string;
|
|
666
|
-
insertBatch(entries: VectorEntry[]): string[];
|
|
667
|
-
|
|
668
|
-
// Search operations
|
|
669
|
-
search(query: Float32Array, k: number, filter?: object): SearchResult[];
|
|
670
|
-
|
|
671
|
-
// Retrieval operations
|
|
672
|
-
get(id: string): VectorEntry | null;
|
|
673
|
-
len(): number;
|
|
674
|
-
isEmpty(): boolean;
|
|
675
|
-
|
|
676
|
-
// Delete operations
|
|
677
|
-
delete(id: string): boolean;
|
|
678
|
-
|
|
679
|
-
// Persistence (IndexedDB)
|
|
680
|
-
saveToIndexedDB(): Promise<void>;
|
|
681
|
-
static loadFromIndexedDB(dbName: string): Promise<VectorDB>;
|
|
682
|
-
|
|
683
|
-
// Properties
|
|
684
|
-
readonly dimensions: number;
|
|
685
|
-
}
|
|
686
|
-
```
|
|
687
|
-
|
|
688
|
-
### Types
|
|
689
|
-
|
|
690
|
-
```typescript
|
|
691
|
-
interface VectorEntry {
|
|
692
|
-
id?: string;
|
|
693
|
-
vector: Float32Array;
|
|
694
|
-
metadata?: Record<string, any>;
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
interface SearchResult {
|
|
698
|
-
id: string;
|
|
699
|
-
score: number;
|
|
700
|
-
vector?: Float32Array;
|
|
701
|
-
metadata?: Record<string, any>;
|
|
702
|
-
}
|
|
703
|
-
```
|
|
704
|
-
|
|
705
|
-
### Utility Functions
|
|
706
|
-
|
|
707
|
-
```typescript
|
|
708
|
-
// Detect SIMD support
|
|
709
|
-
function detectSIMD(): boolean;
|
|
710
|
-
|
|
711
|
-
// Get version
|
|
712
|
-
function version(): string;
|
|
713
|
-
|
|
714
|
-
// Array conversion
|
|
715
|
-
function arrayToFloat32Array(arr: number[]): Float32Array;
|
|
716
|
-
|
|
717
|
-
// Benchmarking
|
|
718
|
-
function benchmark(name: string, iterations: number, dimensions: number): number;
|
|
719
|
-
```
|
|
720
|
-
|
|
721
|
-
See [WASM API Documentation](../../docs/getting-started/wasm-api.md) for complete reference.
|
|
722
|
-
|
|
723
|
-
## π― Example Applications
|
|
724
|
-
|
|
725
|
-
### Semantic Search Engine
|
|
726
|
-
|
|
727
|
-
```javascript
|
|
728
|
-
// Semantic search with OpenAI embeddings
|
|
729
|
-
import init, { VectorDB } from '@ruvector/wasm';
|
|
730
|
-
import { Configuration, OpenAIApi } from 'openai';
|
|
731
|
-
|
|
732
|
-
await init();
|
|
733
|
-
|
|
734
|
-
const openai = new OpenAIApi(new Configuration({
|
|
735
|
-
apiKey: process.env.OPENAI_API_KEY
|
|
736
|
-
}));
|
|
737
|
-
|
|
738
|
-
const db = new VectorDB(1536, 'cosine', true); // OpenAI ada-002 = 1536 dims
|
|
739
|
-
|
|
740
|
-
// Index documents
|
|
741
|
-
const documents = [
|
|
742
|
-
'The quick brown fox jumps over the lazy dog',
|
|
743
|
-
'Machine learning is a subset of artificial intelligence',
|
|
744
|
-
'WebAssembly enables high-performance web applications'
|
|
745
|
-
];
|
|
746
|
-
|
|
747
|
-
for (const [i, doc] of documents.entries()) {
|
|
748
|
-
const response = await openai.createEmbedding({
|
|
749
|
-
model: 'text-embedding-ada-002',
|
|
750
|
-
input: doc
|
|
751
|
-
});
|
|
752
|
-
|
|
753
|
-
const embedding = new Float32Array(response.data.data[0].embedding);
|
|
754
|
-
db.insert(embedding, `doc_${i}`, { text: doc });
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
// Search
|
|
758
|
-
const queryResponse = await openai.createEmbedding({
|
|
759
|
-
model: 'text-embedding-ada-002',
|
|
760
|
-
input: 'What is AI?'
|
|
761
|
-
});
|
|
762
|
-
|
|
763
|
-
const queryEmbedding = new Float32Array(queryResponse.data.data[0].embedding);
|
|
764
|
-
const results = db.search(queryEmbedding, 3);
|
|
765
|
-
|
|
766
|
-
results.forEach(result => {
|
|
767
|
-
console.log(`${result.score.toFixed(4)}: ${result.metadata.text}`);
|
|
768
|
-
});
|
|
769
|
-
```
|
|
770
|
-
|
|
771
|
-
### Offline Recommendation Engine
|
|
772
|
-
|
|
773
|
-
```javascript
|
|
774
|
-
// Product recommendations that work offline
|
|
775
|
-
import init, { VectorDB } from '@ruvector/wasm';
|
|
776
|
-
import { IndexedDBPersistence } from '@ruvector/wasm/indexeddb';
|
|
777
|
-
|
|
778
|
-
await init();
|
|
779
|
-
|
|
780
|
-
const db = new VectorDB(128, 'cosine', true);
|
|
781
|
-
const persistence = new IndexedDBPersistence('product_recommendations');
|
|
782
|
-
await persistence.open();
|
|
783
|
-
|
|
784
|
-
// Load cached recommendations
|
|
785
|
-
await persistence.loadAll(async (progress) => {
|
|
786
|
-
if (progress.vectors.length > 0) {
|
|
787
|
-
db.insertBatch(progress.vectors);
|
|
788
|
-
}
|
|
789
|
-
});
|
|
790
|
-
|
|
791
|
-
// Get recommendations based on user history
|
|
792
|
-
function getRecommendations(userHistory, k = 10) {
|
|
793
|
-
// Compute user preference vector (average of liked items)
|
|
794
|
-
const userVector = computeAverageEmbedding(userHistory);
|
|
795
|
-
const recommendations = db.search(userVector, k);
|
|
796
|
-
|
|
797
|
-
return recommendations.map(r => ({
|
|
798
|
-
productId: r.id,
|
|
799
|
-
score: r.score,
|
|
800
|
-
...r.metadata
|
|
801
|
-
}));
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
// Add new products (syncs to IndexedDB)
|
|
805
|
-
async function addProduct(productId, embedding, metadata) {
|
|
806
|
-
db.insert(embedding, productId, metadata);
|
|
807
|
-
await persistence.save({ id: productId, vector: embedding, metadata });
|
|
808
|
-
}
|
|
809
|
-
```
|
|
810
|
-
|
|
811
|
-
### RAG (Retrieval-Augmented Generation)
|
|
812
|
-
|
|
813
|
-
```javascript
|
|
814
|
-
// Browser-based RAG system
|
|
815
|
-
import init, { VectorDB } from '@ruvector/wasm';
|
|
816
|
-
|
|
817
|
-
await init();
|
|
818
|
-
|
|
819
|
-
const db = new VectorDB(768, 'cosine', true); // BERT embeddings
|
|
820
|
-
|
|
821
|
-
// Index knowledge base
|
|
822
|
-
const knowledgeBase = loadKnowledgeBase(); // Your documents
|
|
823
|
-
for (const doc of knowledgeBase) {
|
|
824
|
-
const embedding = await getBertEmbedding(doc.text);
|
|
825
|
-
db.insert(embedding, doc.id, { text: doc.text, source: doc.source });
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
// RAG query function
|
|
829
|
-
async function ragQuery(question, llm) {
|
|
830
|
-
// 1. Get question embedding
|
|
831
|
-
const questionEmbedding = await getBertEmbedding(question);
|
|
832
|
-
|
|
833
|
-
// 2. Retrieve relevant context
|
|
834
|
-
const context = db.search(questionEmbedding, 5);
|
|
835
|
-
|
|
836
|
-
// 3. Augment prompt with context
|
|
837
|
-
const prompt = `
|
|
838
|
-
Context:
|
|
839
|
-
${context.map(r => r.metadata.text).join('\n\n')}
|
|
840
|
-
|
|
841
|
-
Question: ${question}
|
|
842
|
-
|
|
843
|
-
Answer based on the context above:
|
|
844
|
-
`;
|
|
845
|
-
|
|
846
|
-
// 4. Generate response
|
|
847
|
-
const response = await llm.generate(prompt);
|
|
848
|
-
|
|
849
|
-
return {
|
|
850
|
-
answer: response,
|
|
851
|
-
sources: context.map(r => r.metadata.source)
|
|
852
|
-
};
|
|
853
|
-
}
|
|
854
|
-
```
|
|
855
|
-
|
|
856
|
-
## π Troubleshooting
|
|
857
|
-
|
|
858
|
-
### Common Issues
|
|
859
|
-
|
|
860
|
-
**1. WASM Module Not Loading**
|
|
861
|
-
|
|
862
|
-
```javascript
|
|
863
|
-
// Ensure correct MIME type
|
|
864
|
-
// Add to server config (nginx):
|
|
865
|
-
// types {
|
|
866
|
-
// application/wasm wasm;
|
|
867
|
-
// }
|
|
868
|
-
|
|
869
|
-
// Or use explicit fetch
|
|
870
|
-
const wasmUrl = new URL('./pkg/ruvector_wasm_bg.wasm', import.meta.url);
|
|
871
|
-
await init(await fetch(wasmUrl));
|
|
872
|
-
```
|
|
873
|
-
|
|
874
|
-
**2. CORS Errors**
|
|
875
|
-
|
|
876
|
-
```javascript
|
|
877
|
-
// For local development
|
|
878
|
-
// package.json
|
|
879
|
-
{
|
|
880
|
-
"scripts": {
|
|
881
|
-
"serve": "python3 -m http.server 8080 --bind 127.0.0.1"
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
```
|
|
885
|
-
|
|
886
|
-
**3. Memory Issues**
|
|
887
|
-
|
|
888
|
-
```javascript
|
|
889
|
-
// Monitor memory usage
|
|
890
|
-
const stats = db.len();
|
|
891
|
-
const estimatedMemory = stats * dimensions * 4; // bytes
|
|
892
|
-
|
|
893
|
-
if (estimatedMemory > 100_000_000) { // 100MB
|
|
894
|
-
console.warn('High memory usage, consider chunking');
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
// Use batch operations to reduce GC pressure
|
|
898
|
-
const BATCH_SIZE = 1000;
|
|
899
|
-
for (let i = 0; i < entries.length; i += BATCH_SIZE) {
|
|
900
|
-
const batch = entries.slice(i, i + BATCH_SIZE);
|
|
901
|
-
db.insertBatch(batch);
|
|
902
|
-
}
|
|
903
|
-
```
|
|
904
|
-
|
|
905
|
-
**4. Web Worker Issues**
|
|
906
|
-
|
|
907
|
-
```javascript
|
|
908
|
-
// Ensure worker script URL is correct
|
|
909
|
-
const workerUrl = new URL('./worker.js', import.meta.url);
|
|
910
|
-
const worker = new Worker(workerUrl, { type: 'module' });
|
|
911
|
-
|
|
912
|
-
// Handle worker errors
|
|
913
|
-
worker.onerror = (error) => {
|
|
914
|
-
console.error('Worker error:', error);
|
|
915
|
-
};
|
|
916
|
-
```
|
|
917
|
-
|
|
918
|
-
See [WASM Troubleshooting Guide](../../docs/getting-started/wasm-troubleshooting.md) for more solutions.
|
|
919
|
-
|
|
920
|
-
## π Links & Resources
|
|
921
|
-
|
|
922
|
-
### Documentation
|
|
923
|
-
|
|
924
|
-
- **[Getting Started Guide](../../docs/guide/GETTING_STARTED.md)** - Complete setup and usage
|
|
925
|
-
- **[WASM API Reference](../../docs/getting-started/wasm-api.md)** - Full API documentation
|
|
926
|
-
- **[Performance Tuning](../../docs/optimization/PERFORMANCE_TUNING_GUIDE.md)** - Optimization tips
|
|
927
|
-
- **[Main README](../../README.md)** - Project overview and features
|
|
928
|
-
|
|
929
|
-
### Examples & Demos
|
|
930
|
-
|
|
931
|
-
- **[Vanilla JS Example](../../examples/wasm-vanilla/)** - Basic implementation
|
|
932
|
-
- **[React Demo](../../examples/wasm-react/)** - React integration with hooks
|
|
933
|
-
- **[Live Demo](https://ruvector-demo.vercel.app)** - Try it in your browser
|
|
934
|
-
- **[CodeSandbox](https://codesandbox.io/s/ruvector-wasm)** - Interactive playground
|
|
935
|
-
|
|
936
|
-
### Community & Support
|
|
937
|
-
|
|
938
|
-
- **GitHub**: [github.com/ruvnet/ruvector](https://github.com/ruvnet/ruvector)
|
|
939
|
-
- **Discord**: [Join our community](https://discord.gg/ruvnet)
|
|
940
|
-
- **Twitter**: [@ruvnet](https://twitter.com/ruvnet)
|
|
941
|
-
- **Issues**: [Report bugs](https://github.com/ruvnet/ruvector/issues)
|
|
942
|
-
|
|
943
|
-
## π License
|
|
944
|
-
|
|
945
|
-
MIT License - see [LICENSE](../../LICENSE) for details.
|
|
946
|
-
|
|
947
|
-
Free to use for commercial and personal projects.
|
|
948
|
-
|
|
949
|
-
## π Acknowledgments
|
|
950
|
-
|
|
951
|
-
- Built with [wasm-pack](https://github.com/rustwasm/wasm-pack) and [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen)
|
|
952
|
-
- HNSW algorithm implementation from [hnsw_rs](https://github.com/jean-pierreBoth/hnswlib-rs)
|
|
953
|
-
- SIMD optimizations powered by Rust's excellent WebAssembly support
|
|
954
|
-
- The WebAssembly community for making this possible
|
|
955
|
-
|
|
956
|
-
---
|
|
957
|
-
|
|
958
|
-
<div align="center">
|
|
959
|
-
|
|
960
|
-
**Built by [rUv](https://ruv.io) β’ Open Source on [GitHub](https://github.com/ruvnet/ruvector)**
|
|
961
|
-
|
|
962
|
-
[](https://github.com/ruvnet/ruvector)
|
|
963
|
-
[](https://twitter.com/ruvnet)
|
|
964
|
-
|
|
965
|
-
**Perfect for**: PWAs β’ Offline-First Apps β’ Edge Computing β’ Privacy-First AI
|
|
966
|
-
|
|
967
|
-
[Get Started](../../docs/guide/GETTING_STARTED.md) β’ [API Docs](../../docs/getting-started/wasm-api.md) β’ [Examples](../../examples/)
|
|
968
|
-
|
|
969
|
-
</div>
|