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

endee

v1.1.0

Published

TypeScript client for encrypted vector database with maximum security and speed

Readme

Endee - TypeScript Vector Database Client

Endee is a TypeScript client for a local vector database designed for maximum speed and efficiency. This package provides full type safety, modern ES module support, and optimized code for rapid Approximate Nearest Neighbor (ANN) searches on vector data.

Key Features

  • TypeScript First: Full type safety and IntelliSense support
  • Fast ANN Searches: Efficient similarity searches on vector data
  • Multiple Distance Metrics: Support for cosine, L2, and inner product distance metrics
  • Hybrid Indexes: Support for dense vectors, sparse vectors, and hybrid (dense + sparse) searches
  • Metadata Support: Attach and search with metadata and filters
  • High Performance: Optimized for speed and efficiency
  • Modern ES Modules: Native ES module support with proper tree-shaking

Requirements

  • Node.js >= 18.0.0
  • Endee Local server running (see Quick Start)

Installation

npm install endee

Quick Start

Initialize the Client

The Endee client connects to your local server (defaults to http://127.0.0.1:8080/api/v1):

import { Endee, Precision } from 'endee';

// Connect to local Endee server (defaults to localhost:8080)
const client = new Endee();

Using Authentication? If your server has NDD_AUTH_TOKEN set, pass the same token when initializing:

const client = new Endee('your-auth-token');

Setting a Custom Base URL

If your server runs on a different port, use setBaseUrl():

const client = new Endee();

// Set custom base URL for non-default port
client.setBaseUrl('http://0.0.0.0:8081/api/v1');

Create a Dense Index

import { Precision } from 'endee';

await client.createIndex({
  name: 'my_vectors',
  dimension: 384,
  spaceType: 'cosine',
  precision: Precision.INT8D,
});

Dense Index Parameters:

| Parameter | Description | |-----------|-------------| | name | Unique name for your index | | dimension | Vector dimensionality (must match your embedding model's output) | | spaceType | Distance metric - "cosine", "l2", or "ip" (inner product) | | M | Graph connectivity - higher values increase recall but use more memory (default: 16) | | efCon | Construction-time parameter - higher values improve index quality (default: 128) | | precision | Quantization precision (default: Precision.INT8D) |

Create a Hybrid Index

Hybrid indexes combine dense vector search with sparse vector search. Add the sparseDimension parameter:

await client.createIndex({
  name: 'hybrid_index',
  dimension: 384,           // Dense vector dimension
  sparseDimension: 30000,   // Sparse vector dimension (vocabulary size)
  spaceType: 'cosine',
  precision: Precision.INT8D,
});

List and Access Indexes

// List all indexes
const indexes = await client.listIndexes();

// Get reference to an existing index
const index = await client.getIndex('my_vectors');

// Delete an index
await client.deleteIndex('my_vectors');

Upserting Vectors

The index.upsert() method adds or updates vectors in an existing index.

const index = await client.getIndex('my_index');

await index.upsert([
  {
    id: 'vec1',
    vector: [0.1, 0.2, 0.3 /* ... */],
    meta: { title: 'First document' },
    filter: { category: 'tech' },
  },
  {
    id: 'vec2',
    vector: [0.3, 0.4, 0.5 /* ... */],
    meta: { title: 'Second document' },
    filter: { category: 'science' },
  },
]);

Vector Object Fields:

| Field | Required | Description | |-------|----------|-------------| | id | Yes | Unique identifier for the vector | | vector | Yes | Array of floats representing the embedding | | meta | No | Arbitrary metadata object | | filter | No | Key-value pairs for filtering during queries |

Querying the Index

The index.query() method performs a similarity search.

const results = await index.query({
  vector: [0.15, 0.25 /* ... */],
  topK: 5,
  ef: 128,
  includeVectors: true,
});

for (const item of results) {
  console.log(`ID: ${item.id}, Similarity: ${item.similarity}`);
}

Query Parameters:

| Parameter | Description | |-----------|-------------| | vector | Query vector (must match index dimension) | | topK | Number of results to return (default: 10, max: 512) | | ef | Search quality parameter (default: 128, max: 1024) | | includeVectors | Include vector data in results (default: false) | | filter | Optional filter criteria (array of filter objects) | | sparseIndices | Sparse vector indices for hybrid search (default: null) | | sparseValues | Sparse vector values for hybrid search (default: null) | | prefilterCardinalityThreshold | Controls when search switches from HNSW filtered search to brute-force prefiltering on the matched subset (default: 10,000, range: 1,000–1,000,000). See Filter Tuning for details. | | filterBoostPercentage | Expands the internal HNSW candidate pool by this percentage when a filter is active, compensating for filtered-out results (default: 0, range: 0–100). See Filter Tuning for details. |

Filtered Querying

Use the filter parameter to restrict results. All filters are combined with logical AND.

const results = await index.query({
  vector: [0.15, 0.25 /* ... */],
  topK: 5,
  filter: [
    { category: { $eq: 'tech' } },
    { score: { $range: [80, 100] } },
  ],
});

Filtering Operators

| Operator | Description | Example | |----------|-------------|---------| | $eq | Exact match | { status: { $eq: 'published' } } | | $in | Match any in list | { tags: { $in: ['ai', 'ml'] } } | | $range | Numeric range (inclusive) | { score: { $range: [70, 95] } } |

Note: The $range operator supports values within [0 - 999]. Normalize larger values before upserting.

Filter Tuning

When using filtered queries, two optional parameters let you tune the trade-off between search speed and recall:

prefilterCardinalityThreshold

Controls when the search strategy switches from HNSW filtered search (fast, graph-based) to brute-force prefiltering (exhaustive scan on the matched subset).

| Value | Behavior | |-------|----------| | 1_000 | Prefilter only for very selective filters — minimum value | | 10_000 | Prefilter only when the filter matches ≤10,000 vectors (default) | | 1_000_000 | Prefilter for almost all filtered searches — maximum value |

The intuition: when very few vectors match your filter, HNSW may struggle to find enough valid candidates through graph traversal. In that case, scanning the filtered subset directly (prefiltering) is faster and more accurate. Raising the threshold means prefiltering kicks in more often; lowering it favors HNSW graph search.

// Only prefilter when filter matches ≤5,000 vectors
const results = await index.query({
  vector: [/* ... */],
  topK: 10,
  filter: [{ category: { $eq: 'rare' } }],
  prefilterCardinalityThreshold: 5_000,
});

filterBoostPercentage

When using HNSW filtered search, some candidates explored during graph traversal are discarded by the filter, which can leave you with fewer results than topK. filterBoostPercentage compensates by expanding the internal candidate pool before filtering is applied.

  • 0 → no boost, standard candidate pool size (default)
  • 20 → fetch 20% more candidates internally before applying the filter
  • Maximum: 100 (doubles the candidate pool)
// Fetch 30% more candidates to compensate for aggressive filtering
const results = await index.query({
  vector: [/* ... */],
  topK: 10,
  filter: [{ visibility: { $eq: 'public' } }],
  filterBoostPercentage: 30,
});

Using Both Together

const results = await index.query({
  vector: [/* ... */],
  topK: 10,
  filter: [{ category: { $eq: 'rare' } }],
  prefilterCardinalityThreshold: 5_000,  // switch to brute-force for small match sets
  filterBoostPercentage: 25,             // boost candidates for HNSW filtered search
});

Tip: Start with the defaults (prefilterCardinalityThreshold: 10_000, filterBoostPercentage: 0). If filtered queries return fewer results than expected, try increasing filterBoostPercentage. If filtered queries are slow on selective filters, try lowering prefilterCardinalityThreshold. Valid range for the threshold is 1,000–1,000,000.

Hybrid Search

Upserting Hybrid Vectors

Provide both dense vectors and sparse representations:

const index = await client.getIndex('hybrid_index');

await index.upsert([
  {
    id: 'doc1',
    vector: [0.1, 0.2 /* ... */],           // Dense vector
    sparseIndices: [10, 50, 200],            // Non-zero term positions
    sparseValues: [0.8, 0.5, 0.3],           // Weights for each position
    meta: { title: 'Document 1' },
  },
  {
    id: 'doc2',
    vector: [0.3, 0.4 /* ... */],
    sparseIndices: [15, 100, 500],
    sparseValues: [0.9, 0.4, 0.6],
    meta: { title: 'Document 2' },
  },
]);

Hybrid Vector Fields:

| Field | Required | Description | |-------|----------|-------------| | id | Yes | Unique identifier | | vector | Yes | Dense embedding vector | | sparseIndices | Yes (hybrid) | Non-zero term positions in sparse vector | | sparseValues | Yes (hybrid) | Weights for each sparse index | | meta | No | Metadata dictionary | | filter | No | Filter fields |

Important: sparseIndices and sparseValues must have the same length. Values in sparseIndices must be within [0, sparseDimension).

Querying Hybrid Index

Provide both dense and sparse query vectors:

const results = await index.query({
  vector: [0.15, 0.25 /* ... */],       // Dense query
  sparseIndices: [10, 100, 300],         // Sparse query positions
  sparseValues: [0.7, 0.5, 0.4],         // Sparse query weights
  topK: 5,
});

for (const item of results) {
  console.log(`ID: ${item.id}, Similarity: ${item.similarity}`);
}

Hybrid Query Parameters:

| Parameter | Description | |-----------|-------------| | vector | Dense query vector | | sparseIndices | Sparse query positions | | sparseValues | Sparse query weights | | topK | Number of results to return | | ef | Search quality parameter (max 1024, default: 128) | | includeVectors | Include vector data in results (default: false) | | filter | Optional filter criteria | | prefilterCardinalityThreshold | Controls when search switches from HNSW filtered search to brute-force prefiltering on the matched subset (default: 10,000, range: 1,000–1,000,000). See Filter Tuning for details. | | filterBoostPercentage | Expands the internal HNSW candidate pool by this percentage when a filter is active, compensating for filtered-out results (default: 0, range: 0–100). See Filter Tuning for details. |

You can also query with:

  • Dense only: Provide only vector
  • Sparse only: Provide only sparseIndices and sparseValues
  • Hybrid: Provide all three for combined results

Deletion Methods

Delete by ID

Delete vector with a specifc vector id.

await index.deleteVector('vec1');

Delete by Filter

Delete all vectors matching specific filters.

await index.deleteWithFilter([{'category': {'$eq' : 'tech'}}]);

Delete Index

Delete an entire Index.

await client.deleteIndex('my_index');

Warning: Deletion operations are irreversible.

Additional Operations

Get Vector by ID

const vector = await index.getVector('vec1');

Describe Index

const info = index.describe();
console.log(info);
// { name, spaceType, dimension, sparseDimension, isHybrid, count, precision, M }

Precision Options

Endee supports different quantization precision levels:

import { Precision } from 'endee';

Precision.BINARY;   // Binary quantization (1-bit) - smallest storage, fastest search
Precision.INT8D;    // 8-bit integer quantization (default) - balanced performance
Precision.INT16D;   // 16-bit integer quantization - higher precision
Precision.FLOAT16;  // 16-bit floating point - good balance
Precision.FLOAT32;  // 32-bit floating point - highest precision

Choosing Precision:

  • BINARY: Best for very large datasets where speed and storage are critical
  • INT8D (default): Recommended for most use cases - good balance of accuracy and performance
  • INT16D: When you need better accuracy than INT8D but less storage than FLOAT32
  • FLOAT16: Good compromise between precision and storage for embeddings
  • FLOAT32: When you need maximum precision and storage is not a concern

Complete Example

import { Endee, Precision } from 'endee';

// Initialize client
const client = new Endee();

// Create a dense index
await client.createIndex({
  name: 'documents',
  dimension: 384,
  spaceType: 'cosine',
  precision: Precision.INT8D,
});

// Get the index
const index = await client.getIndex('documents');

// Add vectors
await index.upsert([
  {
    id: 'doc1',
    vector: [0.1, 0.2 /* ... 384 dimensions */],
    meta: { title: 'First Document' },
    filter: { category: 'tech' },
  },
  {
    id: 'doc2',
    vector: [0.3, 0.4 /* ... 384 dimensions */],
    meta: { title: 'Second Document' },
    filter: { category: 'science' },
  },
]);

// Query the index
const results = await index.query({
  vector: [0.15, 0.25 /* ... */],
  topK: 5,
});

for (const item of results) {
  console.log(`ID: ${item.id}, Similarity: ${item.similarity}`);
}

API Reference

Endee Class

| Method | Description | |--------|-------------| | createIndex(options) | Create a new index (add sparseDimension for hybrid) | | listIndexes() | List all indexes | | deleteIndex(name) | Delete an index | | getIndex(name) | Get reference to an index | | setBaseUrl(url) | Set a custom base URL |

Index Class

| Method | Description | |--------|-------------| | upsert(vectors) | Insert or update vectors | | query(options) | Search for similar vectors (supports vector, topK, filter, ef, includeVectors, sparseIndices, sparseValues, prefilterCardinalityThreshold, filterBoostPercentage) | | deleteVector(id) | Delete a vector by ID | | deleteWithFilter(filter) | Delete vectors by Filter | | getVector(id) | Get a vector by ID | | describe() | Get index info |

TypeScript Types

The package includes comprehensive TypeScript types:

import type {
  VectorItem,
  QueryOptions,
  QueryResult,
  CreateIndexOptions,
  IndexDescription,
  SpaceType,
  Precision,
} from 'endee';

License

MIT

Author

Pankaj Singh