Skip to content

CLI commands that take a <database> path are broken on freshly-created DBs (and embed text fails on missing ONNX bundle) #417

@djdamienford-arch

Description

@djdamienford-arch

Summary

Six CLI commands fail end-to-end on a database produced by npx ruvector create, and npx ruvector embed text fails immediately on
a fresh npm install. Both are reproducible on ruvector@0.2.25
straight from npm.

Reproduction (~30 seconds)

npx ruvector create /tmp/x.db -d 384 -m cosine                     
echo '[{"id":"a","vector":[…384 floats…]}]' > /tmp/v.json            
npx ruvector insert /tmp/x.db /tmp/v.json                            
# → SyntaxError: Unexpected token 'r', "redb…" is not valid JSON     
                                                                     
npx ruvector stats  /tmp/x.db   # same crash                         
npx ruvector search /tmp/x.db -v '[…]' -k 5   # same crash           
                                                                     
npx ruvector embed text "hello"                                      
# → Error: ONNX WASM files not bundled. The onnx/ directory is       
missing.                                                             

benchmark runs to completion but its rates are unreliable (Promise
leak — see below). export and import fail with "method does not
exist" errors.

Root causes

1. bin/cli.js insert / search / stats / export / import

handlers

const dbData = fs.readFileSync(dbPath, 'utf8');                    
const parsed = JSON.parse(dbData);            // ← bin/cli.js:191,   
241, 288, 1968, 2010                                                 
const dimension = parsed.dimension || 384;                           

create writes a redb (Rust binary) file via the native binding's
storagePath. JSON.parse of that file crashes on the magic bytes
"redb…". The same handlers also call methods that do not exist on
VectorDBWrapper: db.load, db.save, db.stats, db.getStats.
Verified API surface (dist/index.js):

constructor, insert, insertBatch, search, get, delete, len, isEmpty
 (all async)                                                         

Same handlers also pass dimension (singular) where the wrapper
expects dimensions, pass topK where the wrapper passes k to
native, and don't await the async wrapper methods.

2. benchmark (bin/cli.js:380-440)

db.insertBatch(vectors) and db.search(query) are never awaited;
the action handler isn't async. Benchmark numbers reflect spinner
timing, not actual ingest/search completion.

3. ONNX WASM not bundled (package.json build script)

"build": "tsc && cp src/core/onnx/pkg/package.json
dist/core/onnx/pkg/"                                                 

This copies one package.json. The actual WASM payload —
loader.js, ruvector_onnx_embeddings_wasm.{js,_bg.js,_bg.wasm}
never reaches dist/. At runtime onnx-embedder.js:184 looks for
dist/core/onnx/pkg/ruvector_onnx_embeddings_wasm.js and throws
"ONNX WASM files not bundled. The onnx/ directory is missing." The
package.json files array also doesn't include src/core/onnx/,
so even raw-source fallback is impossible.

Proposed fix (already implemented locally; happy to PR)

I have a working fix on fix/cli-sidecar-and-onnx-bundling (228
lines in bin/cli.js, 1 line in package.json, 36 lines in
scripts/verify-dist.js). Architecture summary:

  • CLI handlers — write a <dbPath>.meta.json sidecar in create
    carrying {dimensions, metric, version}.
    insert/search/stats/export/import read the sidecar instead
    of JSON.parse-ing the redb. All handlers become async, await every
    wrapper call, use canonical constructor keys, coerce numeric ids to
    strings in place, and pass k not topK.
  • Defensive guards — error clearly if sidecar exists but DB
    doesn't (and vice versa), so the native binding can't silently
    fabricate a fresh empty DB.
  • Build script — copy the full src/core/onnx/ tree into
    dist/core/onnx/ so the loader can find its WASM at runtime.
  • verify-dist.js — assert all 5 ONNX runtime assets exist
    before publish, so this regression cannot ship silently again.
  • Honest disclosuresVectorDBWrapper has no
    list/iter/keys, so export is metadata-only and the success
    message + import warning say so.

Question before I send a PR

The sidecar pattern is one of two reasonable architectures. The other
is to add an enumeration / metadata-read method to VectorDBWrapper
(e.g. db.metadata() returning {dimensions, metric, count}) so
the CLI can recover dimensions from the redb itself.

The wrapper-API approach is cleaner long-term but a bigger change.
The sidecar is a small CLI-only patch that ships today. Which would
you prefer for the PR?
Happy to align before opening it.

I can post the diff for review here as a comment if helpful, or open
the PR directly once you confirm direction.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions