ruvector 0.2.29 → 0.2.30

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.
Files changed (3) hide show
  1. package/bin/cli.js +91 -63
  2. package/bin/mcp-server.js +3854 -3854
  3. package/package.json +1 -1
package/bin/cli.js CHANGED
@@ -183,7 +183,7 @@ program
183
183
  .command('insert <database> <file>')
184
184
  .description('Insert vectors from JSON file')
185
185
  .option('-b, --batch-size <number>', 'Batch size for insertion', '1000')
186
- .action((dbPath, file, options) => {
186
+ .action(async (dbPath, file, options) => {
187
187
  requireRuvector();
188
188
  const spinner = ora('Loading database...').start();
189
189
 
@@ -195,30 +195,34 @@ program
195
195
  try { dimension = JSON.parse(fs.readFileSync(metaPath, 'utf8')).dimension || 384; } catch (_) {}
196
196
  }
197
197
 
198
- const db = new VectorDB({ dimensions: dimension, storagePath: dbPath });
199
-
200
- if (fs.existsSync(dbPath)) {
201
- db.load(dbPath);
202
- }
203
-
204
198
  spinner.text = 'Reading vectors...';
205
199
  const data = JSON.parse(fs.readFileSync(file, 'utf8'));
206
200
  const vectors = Array.isArray(data) ? data : [data];
207
201
 
202
+ // New database: derive dimension from the data and write the sidecar
203
+ // so later stats/search invocations open it correctly (#508).
204
+ if (!fs.existsSync(dbPath) && vectors.length > 0 && Array.isArray(vectors[0].vector)) {
205
+ dimension = vectors[0].vector.length;
206
+ try { fs.writeFileSync(metaPath, JSON.stringify({ dimension }, null, 2)); } catch (_) {}
207
+ }
208
+
209
+ // The native binding loads/persists through storagePath itself —
210
+ // VectorDB has no load()/save() methods (#508).
211
+ const db = new VectorDB({ dimensions: dimension, storagePath: dbPath });
212
+
208
213
  spinner.text = `Inserting ${vectors.length} vectors...`;
209
214
  const batchSize = parseInt(options.batchSize);
210
215
 
211
216
  for (let i = 0; i < vectors.length; i += batchSize) {
212
217
  const batch = vectors.slice(i, i + batchSize);
213
- db.insertBatch(batch);
218
+ await db.insertBatch(batch);
214
219
  spinner.text = `Inserted ${Math.min(i + batchSize, vectors.length)}/${vectors.length} vectors...`;
215
220
  }
216
221
 
217
- db.save(dbPath);
218
222
  spinner.succeed(chalk.green(`Inserted ${vectors.length} vectors`));
219
223
 
220
- const stats = db.stats();
221
- console.log(chalk.gray(` Total vectors: ${stats.count}`));
224
+ const count = await db.len();
225
+ console.log(chalk.gray(` Total vectors: ${count}`));
222
226
  } catch (error) {
223
227
  spinner.fail(chalk.red('Failed to insert vectors'));
224
228
  console.error(chalk.red(error.message));
@@ -234,7 +238,7 @@ program
234
238
  .option('-k, --top-k <number>', 'Number of results', '10')
235
239
  .option('-t, --threshold <number>', 'Similarity threshold', '0.0')
236
240
  .option('-f, --filter <json>', 'Metadata filter as JSON')
237
- .action((dbPath, options) => {
241
+ .action(async (dbPath, options) => {
238
242
  requireRuvector();
239
243
  const spinner = ora('Loading database...').start();
240
244
 
@@ -246,8 +250,13 @@ program
246
250
  try { dimension = JSON.parse(fs.readFileSync(metaPath, 'utf8')).dimension || 384; } catch (_) {}
247
251
  }
248
252
 
253
+ if (!fs.existsSync(dbPath)) {
254
+ spinner.fail(chalk.red(`Database not found: ${dbPath}`));
255
+ process.exit(1);
256
+ }
257
+
258
+ // storagePath loads the existing store; VectorDB has no load() (#508).
249
259
  const db = new VectorDB({ dimensions: dimension, storagePath: dbPath });
250
- db.load(dbPath);
251
260
 
252
261
  spinner.text = 'Searching...';
253
262
 
@@ -262,7 +271,7 @@ program
262
271
  query.filter = JSON.parse(options.filter);
263
272
  }
264
273
 
265
- const results = db.search(query);
274
+ const results = await db.search(query);
266
275
  spinner.succeed(chalk.green(`Found ${results.length} results`));
267
276
 
268
277
  console.log(chalk.cyan('\nSearch Results:'));
@@ -284,35 +293,40 @@ program
284
293
  program
285
294
  .command('stats <database>')
286
295
  .description('Show database statistics')
287
- .action((dbPath) => {
296
+ .action(async (dbPath) => {
288
297
  requireRuvector();
289
298
  const spinner = ora('Loading database...').start();
290
299
 
291
300
  try {
292
- // Read dimension from sidecar (avoids JSON-parsing binary redb)
301
+ // Read dimension/metric from sidecar (avoids JSON-parsing binary redb)
293
302
  let dimension = 384;
303
+ let metric = 'cosine';
294
304
  const metaPath = `${dbPath}.meta.json`;
295
305
  if (fs.existsSync(metaPath)) {
296
- try { dimension = JSON.parse(fs.readFileSync(metaPath, 'utf8')).dimension || 384; } catch (_) {}
306
+ try {
307
+ const meta = JSON.parse(fs.readFileSync(metaPath, 'utf8'));
308
+ dimension = meta.dimension || dimension;
309
+ metric = meta.metric || metric;
310
+ } catch (_) {}
297
311
  }
298
312
 
299
- const db = new VectorDB({ dimensions: dimension, storagePath: dbPath });
300
- db.load(dbPath);
313
+ if (!fs.existsSync(dbPath)) {
314
+ spinner.fail(chalk.red(`Database not found: ${dbPath}`));
315
+ process.exit(1);
316
+ }
301
317
 
302
- const stats = db.stats();
318
+ // storagePath loads the existing store; VectorDB exposes len(),
319
+ // not a stats() aggregate (#508).
320
+ const db = new VectorDB({ dimensions: dimension, storagePath: dbPath });
321
+ const count = await db.len();
303
322
  spinner.succeed(chalk.green('Database statistics'));
304
323
 
305
324
  console.log(chalk.cyan('\nDatabase Stats:'));
306
- console.log(chalk.white(` Vector Count: ${chalk.yellow(stats.count)}`));
307
- console.log(chalk.white(` Dimension: ${chalk.yellow(stats.dimension)}`));
308
- console.log(chalk.white(` Metric: ${chalk.yellow(stats.metric)}`));
325
+ console.log(chalk.white(` Vector Count: ${chalk.yellow(count)}`));
326
+ console.log(chalk.white(` Dimension: ${chalk.yellow(dimension)}`));
327
+ console.log(chalk.white(` Metric: ${chalk.yellow(metric)}`));
309
328
  console.log(chalk.white(` Implementation: ${chalk.yellow(getImplementationType())}`));
310
329
 
311
- if (stats.memoryUsage) {
312
- const mb = (stats.memoryUsage / (1024 * 1024)).toFixed(2);
313
- console.log(chalk.white(` Memory Usage: ${chalk.yellow(mb + ' MB')}`));
314
- }
315
-
316
330
  const fileStats = fs.statSync(dbPath);
317
331
  const fileMb = (fileStats.size / (1024 * 1024)).toFixed(2);
318
332
  console.log(chalk.white(` File Size: ${chalk.yellow(fileMb + ' MB')}`));
@@ -1920,35 +1934,33 @@ program
1920
1934
  const spinner = ora('Exporting database...').start();
1921
1935
 
1922
1936
  try {
1923
- const outputFile = options.output || `${dbPath.replace(/\/$/, '')}_export.${options.format}`;
1924
-
1925
- // Load database
1926
- const db = new VectorDB({ dimension: 384 }); // Will be overwritten by load
1927
- if (fs.existsSync(dbPath)) {
1928
- db.load(dbPath);
1929
- } else {
1937
+ if (!fs.existsSync(dbPath)) {
1930
1938
  spinner.fail(chalk.red(`Database not found: ${dbPath}`));
1931
1939
  process.exit(1);
1932
1940
  }
1933
1941
 
1934
- const stats = db.getStats();
1935
- const data = {
1936
- version: packageJson.version,
1937
- exportedAt: new Date().toISOString(),
1938
- stats: stats,
1939
- vectors: [] // Would contain actual vector data
1940
- };
1942
+ const outputFile = options.output || `${dbPath.replace(/\/$/, '')}_export.${options.format}`;
1941
1943
 
1942
- if (options.format === 'json') {
1943
- fs.writeFileSync(outputFile, JSON.stringify(data, null, 2));
1944
- } else {
1945
- spinner.fail(chalk.yellow(`Format '${options.format}' not yet supported. Using JSON.`));
1946
- fs.writeFileSync(outputFile.replace(/\.[^.]+$/, '.json'), JSON.stringify(data, null, 2));
1944
+ // Read dimension/metric from sidecar; storagePath loads the store (#508)
1945
+ let dimension = 384;
1946
+ const metaPath = `${dbPath}.meta.json`;
1947
+ if (fs.existsSync(metaPath)) {
1948
+ try { dimension = JSON.parse(fs.readFileSync(metaPath, 'utf8')).dimension || 384; } catch (_) {}
1947
1949
  }
1948
-
1949
- spinner.succeed(chalk.green(`Exported to: ${outputFile}`));
1950
- console.log(chalk.gray(` Vectors: ${stats.count || 0}`));
1951
- console.log(chalk.gray(` Format: ${options.format}`));
1950
+ const db = new VectorDB({ dimensions: dimension, storagePath: dbPath });
1951
+ const count = await db.len();
1952
+
1953
+ // HONESTY: VectorDB has no enumeration API, so vector payloads cannot
1954
+ // be exported yet — only metadata. Refuse to write a file that import
1955
+ // would silently pretend to restore.
1956
+ spinner.fail(chalk.yellow(
1957
+ `Export is not supported yet: the database has ${count} vectors but ` +
1958
+ `the VectorDB API has no enumeration method to read them back out. ` +
1959
+ `The .db file itself is the portable artifact — copy it (with its ` +
1960
+ `.meta.json sidecar) to back up or move the database.`
1961
+ ));
1962
+ console.log(chalk.gray(` Requested output: ${outputFile} (not written)`));
1963
+ process.exit(1);
1952
1964
  } catch (error) {
1953
1965
  spinner.fail(chalk.red('Export failed'));
1954
1966
  console.error(chalk.red(error.message));
@@ -1975,20 +1987,29 @@ program
1975
1987
  const data = JSON.parse(fs.readFileSync(file, 'utf8'));
1976
1988
  const dbPath = options.database || file.replace(/_export\.json$/, '');
1977
1989
 
1978
- spinner.text = 'Creating database...';
1979
-
1980
- const db = new VectorDB({
1981
- dimension: data.stats?.dimension || 384,
1982
- path: dbPath,
1983
- autoPersist: true
1984
- });
1990
+ // A plain JSON array of {vector, metadata} entries is importable via
1991
+ // the real API. The old _export.json format never contained vectors,
1992
+ // so importing it would fabricate an empty database (#508).
1993
+ const vectors = Array.isArray(data) ? data : null;
1994
+ if (!vectors || vectors.length === 0 || !vectors[0].vector) {
1995
+ spinner.fail(chalk.yellow(
1996
+ 'Import expects a JSON array of {vector, metadata} entries ' +
1997
+ '(the same format `ruvector insert` accepts). Legacy _export.json ' +
1998
+ 'files contain no vector data and cannot be restored. To move a ' +
1999
+ 'database, copy the .db file and its .meta.json sidecar.'
2000
+ ));
2001
+ process.exit(1);
2002
+ }
1985
2003
 
1986
- // Would import actual vectors here
1987
- db.save(dbPath);
2004
+ spinner.text = `Importing ${vectors.length} vectors...`;
2005
+ const dimension = vectors[0].vector.length;
2006
+ const db = new VectorDB({ dimensions: dimension, storagePath: dbPath });
2007
+ await db.insertBatch(vectors);
2008
+ const count = await db.len();
1988
2009
 
1989
2010
  spinner.succeed(chalk.green(`Imported to: ${dbPath}`));
1990
- console.log(chalk.gray(` Source version: ${data.version}`));
1991
- console.log(chalk.gray(` Exported at: ${data.exportedAt}`));
2011
+ console.log(chalk.gray(` Vectors imported: ${vectors.length} (db total: ${count})`));
2012
+ console.log(chalk.gray(` Dimension: ${dimension}`));
1992
2013
  } catch (error) {
1993
2014
  spinner.fail(chalk.red('Import failed'));
1994
2015
  console.error(chalk.red(error.message));
@@ -2572,7 +2593,11 @@ program
2572
2593
  const spinner = ora('Creating demo database...').start();
2573
2594
 
2574
2595
  try {
2575
- const db = new VectorDB({ dimensions: 4, distanceMetric: 'cosine' });
2596
+ // Explicit path + sidecar so the stats/search/insert/export commands
2597
+ // can open this database afterwards with the right dimension (#508).
2598
+ const demoPath = './demo.db';
2599
+ const db = new VectorDB({ dimensions: 4, distanceMetric: 'cosine', storagePath: demoPath });
2600
+ fs.writeFileSync(`${demoPath}.meta.json`, JSON.stringify({ dimension: 4, metric: 'cosine' }, null, 2));
2576
2601
 
2577
2602
  spinner.text = 'Inserting vectors...';
2578
2603
  // VectorDBWrapper.insert takes a single object: { id?, vector, metadata? }.
@@ -2597,6 +2622,9 @@ program
2597
2622
  });
2598
2623
 
2599
2624
  console.log(chalk.green('\n Demo complete!'));
2625
+ console.log(chalk.cyan('\n The database persists at ./demo.db — try:'));
2626
+ console.log(chalk.white(' npx ruvector stats ./demo.db'));
2627
+ console.log(chalk.white(' npx ruvector search ./demo.db --vector "[0.8, 0.6, 0, 0]"'));
2600
2628
  } catch (error) {
2601
2629
  spinner.fail(chalk.red('Demo failed'));
2602
2630
  console.error(chalk.red(error.message));