npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

sqlite-hybrid

v0.1.3

Published

Vector search and FTS5 hybrid search on a user-provided better-sqlite3 connection

Downloads

591

Readme

sqlite-hybrid

Vector search + FTS5 hybrid search on a user-provided better-sqlite3 (or better-sqlite3-multiple-ciphers) connection. Wraps sqlite-vec for vector operations and SQLite's built-in FTS5 for keyword search, then fuses both via Reciprocal Rank Fusion (RRF).

import Database from 'better-sqlite3-multiple-ciphers';
import { SqliteHybrid } from 'sqlite-hybrid';
import { Embedder } from 'hf-embedder';

const embedder = Embedder.createSync({
  model: 'Xenova/multilingual-e5-small',
  dtype: 'q4',
  pooling: 'mean',
  normalize: true,
});

const db = new Database(':memory:');
const hybrid = new SqliteHybrid(db, {
  vectorSize: 384,
  onEmbed: (text) => embedder.embedSync(text),
});

db.exec(`CREATE TABLE reviews (
  id INTEGER PRIMARY KEY,
  product TEXT NOT NULL,
  data TEXT NOT NULL
)`);

// Index values inside JSON columns via json_extract
hybrid.createVectorIndex('reviews', "json_extract(data, '$.review')");
hybrid.createFTS5('reviews', "json_extract(data, '$.review')");
hybrid.createVectorIndex('reviews', "json_extract(data, '$.category')");
hybrid.createFTS5('reviews', "json_extract(data, '$.category')");

// Triggers compute embeddings and sync FTS5 immediately
const ins = db.prepare(`INSERT INTO reviews (id, product, data) VALUES (?, ?, ?)`);
ins.run(1, 'Noise-cancelling headphones',
  JSON.stringify({ review: 'Amazing sound quality and battery life', category: 'audio, wireless' }));
ins.run(2, 'Mechanical keyboard',
  JSON.stringify({ review: 'Great tactile feedback for programming', category: 'keyboard, wired' }));
ins.run(3, 'USB-C hub',
  JSON.stringify({ review: 'Reliable dock for laptop connectivity', category: 'accessories, wired' }));
ins.run(4, 'Wireless mouse',
  JSON.stringify({ review: 'Comfortable grip for long work sessions', category: 'mouse, wireless' }));

// Scoped hybrid search (single table)
const results = hybrid.hybridSearch('reviews', 'keyboard');
console.log(results[0].product); // "Mechanical keyboard"

// Global search across all indexed tables and fields
const global = hybrid.hybridSearch('wireless');
console.log(global[0]._table);  // "reviews"
console.log(global[0]._field);  // "json_extract(data, '$.category')"

Features

  • Vector indexes — creates vec0 companion tables via sqlite-vec. Sync triggers call your onEmbed callback immediately during INSERT/UPDATE and write embeddings directly.
  • FTS5 indexes — external content (plain columns) or contentless (JSON path) FTS5 virtual tables, kept in sync via triggers.
  • JSON path indexes — index values inside JSON columns via json_extract(col, 'path').
  • Hybrid search — runs vector and keyword retrievers independently, fuses via RRF: score = 1/(k + rank_vec) + 1/(k + rank_fts).
  • Global search — single-argument forms discover all indexed tables/fields from the internal registry and search across them.
  • Scoped search — two-argument forms restrict to a single source table.
  • Encrypted databases — works with better-sqlite3-multiple-ciphers and SQLCipher.

Installation

npm install sqlite-hybrid better-sqlite3

better-sqlite3 (or better-sqlite3-multiple-ciphers) is a peer dependency.

API

new SqliteHybrid(db, options)

| Option | Type | Description | |--------|------|-------------| | vectorSize | number | Embedding dimension (required) | | onEmbed | (text: string) => Float32Array \| number[] | Sync embedder for writes and search queries (required) |

Methods

| Method | Description | |--------|-------------| | createVectorIndex(table, field) | Create a vector index on table.field | | createFTS5(table, field) | Create an FTS5 index on table.field | | dropVectorIndex(table, field) | Drop a vector index | | dropFTS5(table, field) | Drop an FTS5 index | | vectorSearch(table, text, limit?) | Scoped vector search | | vectorSearch(text, limit?) | Global vector search | | keySearch(table, text, limit?) | Scoped keyword search | | keySearch(text, limit?) | Global keyword search | | hybridSearch(table, text, limit?) | Scoped hybrid search (vector + FTS5, RRF-fused) | | hybridSearch(text, limit?) | Global hybrid search |

Pass json_extract(col, 'path') as field to index a value inside a JSON column. The same expression is used for drops and searches.

See docs/api.md for full reference.

How it works

  1. createVectorIndex creates a vec0 virtual table and installs AFTER INSERT/UPDATE/DELETE triggers. The INSERT and UPDATE triggers call _sqlite_hybrid_embed (a SQL function registered on construction that wraps onEmbed) and write the result directly to the vec0 table.
  2. createFTS5 creates an FTS5 virtual table (external content for plain columns, contentless for JSON path) with sync triggers.
  3. For JSON path indexes, the field is parsed to extract the base column and JSON path. Companion table names include a sanitized path suffix, and triggers use json_extract(NEW.col, 'path') automatically.
  4. Search queries embed the query text via onEmbed, join companion tables back to the source table by rowid, and return full records with _score.
  5. Hybrid search runs vector and keyword queries against every indexed field on the target table, then fuses results via RRF: score = 1/(k + rank_vec) + 1/(k + rank_fts).

License

MIT