JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 232
  • Score
    100M100P100Q76966F
  • License MIT

TypeScript client for encrypted vector database with maximum security and speed

Package Exports

  • endee
  • endee/dist/index.js

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (endee) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

Endee - TypeScript Vector Database Client

Endee is a TypeScript client for a 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
  • Client-Side Encryption: Optional AES-256 encryption for your metadata
  • High Performance: Optimized for speed and efficiency
  • Modern ES Modules: Native ES module support with proper tree-shaking

Installation

npm install endee

Getting Your Auth Token

  1. Go to the Endee Dashboard
  2. Sign up or log in to your account
  3. Navigate to API Keys section
  4. Click Generate New Token
  5. Copy the generated auth token and store it securely

Quick Start

import { Endee, Precision } from "endee";

// Initialize client with your Auth token
const endee = new Endee("<auth-token>");

// Create a new index
await endee.createIndex({
  name: "my_vectors",
  dimension: 1536, // Your vector dimension
  spaceType: "cosine" // Distance metric (cosine, l2, ip)
});

// Get index reference
const index = await endee.getIndex("my_vectors");

// Insert vectors
await index.upsert([
  {
    id: "doc1",
    vector: [0.1, 0.2, 0.3 /* ... */], // Your vector data
    meta: { text: "Example document", category: "reference" },
  },
]);

// Query similar vectors
const results = await index.query({
  vector: [0.2, 0.3, 0.4 /* ... */], // Query vector
  topK: 10
});

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

Basic Usage

Initializing the Client

import { Endee } from "endee";

// Production with specific region
const endee = new Endee("<auth-token>");

// Local development (defaults to http://127.0.0.1:8080/api/v1)
const endee = new Endee();

Managing Indexes

import { Precision } from "endee";

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

// Create an index with custom parameters
await endee.createIndex({
  name: "my_custom_index",
  dimension: 384,
  spaceType: "l2", // space type (cosine, l2, ip)
  M: 32, // M: Graph connectivity parameter
  efCon: 256, // efCon: Construction-time parameter
  precision: Precision.INT8D // Quantization precision (default: Precision.INT8D)
  sparseDimension: 1000 // Optional: Enable hybrid index with sparse dimension
});

// Delete an index
await endee.deleteIndex("my_index");

Working with Vectors

// Get index reference
const index = await endee.getIndex("my_index");

// Insert multiple vectors in a batch
await index.upsert([
  {
    id: "vec1",
    vector: [/* ... */], // Your vector
    meta: { title: "First document" },
    filter: { tags: "important" },
  },
  {
    id: "vec2",
    vector: [/* ... */], // Another vector
    filter: { visibility: "public" }, // Optional filter values
  },
]);

// Query with custom parameters
const results = await index.query({
  vector: [/* ... */], // Query vector
  topK: 5, // Number of results to return
  filter: [{ tags: { "$eq": "important" } }], // Filter array with operators
  ef: 128, // Runtime parameter for search quality
  includeVectors: true, // Include vector data in results
});

// Delete vectors
await index.deleteVector("vec1");
await index.deleteWithFilter({ visibility: { "$eq": "public" } });

// Get a specific vector
const vector = await index.getVector("vec1");

// Get index description
const description = await index.describe();
console.log(description);

Hybrid Indexes

Endee supports Hybrid Indexes that combine dense vectors with sparse vectors, enabling powerful search capabilities. Hybrid indexes allow you to:

  • Store both dense embeddings (e.g., from neural networks) and sparse features (e.g., keyword frequencies, categorical encodings)
  • Perform dense-only searches (traditional vector similarity)
  • Perform sparse-only searches (keyword/feature matching)
  • Perform hybrid searches (combining both dense and sparse signals)

Creating a Hybrid Index

To create a hybrid index, specify the sparseDimension parameter when creating an index:

// Create a hybrid index with dense dimension 384 and sparse dimension 10000
await endee.createIndex({
  name: "hybrid_index",
  dimension: 384, // Dense vector dimension
  sparseDimension: 10000, // Sparse vector dimension (max index value)
  spaceType: "cosine",
  precision: "medium"
});

// Get the hybrid index reference
const index = await endee.getIndex("hybrid_index");

// Check if index is hybrid
const description = index.describe();
console.log(description.isHybrid); // true
console.log(description.sparseDimension); // 10000

Upserting Vectors with Sparse Data

When working with a hybrid index, you can insert vectors with both dense and sparse components:

// Insert vectors with both dense and sparse components (hybrid index only)
await index.upsert([
  {
    id: "doc2",
    vector: [0.2, 0.3, 0.4, /* ... 384 dimensions */], // Dense vector
    sparseIndices: [5, 42, 100, 500], // Sparse feature indices
    sparseValues: [0.8, 0.6, 0.9, 0.7], // Sparse feature values
    meta: { title: "Document 2", category: "tech" }
  },
  {
    id: "doc3",
    vector: [0.3, 0.4, 0.5, /* ... 384 dimensions */],
    sparseIndices: [10, 25, 200],
    sparseValues: [0.5, 0.4, 0.6],
    meta: { title: "Document 3", category: "science" }
  }
]);

Important Notes:

  • sparseIndices and sparseValues must have the same length
  • Sparse indices must be in the range [0, sparseDimension)
  • Both sparseIndices and sparseValues must be provided together (or both omitted)
  • You cannot insert sparse data into a dense-only index (one created without sparseDimension)

Querying Hybrid Indexes

Hybrid indexes support three types of searches:

1. Dense Search (Traditional Vector Similarity)

Search using only the dense vector component:

const results = await index.query({
  vector: [0.2, 0.3, 0.4, /* ... 384 dimensions */], // Query dense vector
  topK: 10,
  ef: 128
});

// Results are ranked by dense vector similarity
for (const item of results) {
  console.log(`ID: ${item.id}, Similarity: ${item.similarity}`);
}

2. Sparse Search (Keyword/Feature Matching)

Search using only the sparse component:

const results = await index.query({
  sparseIndices: [5, 42, 100], // Query sparse feature indices
  sparseValues: [0.8, 0.6, 0.9], // Query sparse feature values
  topK: 10,
  ef: 128
});

// Results are ranked by sparse vector similarity
for (const item of results) {
  console.log(`ID: ${item.id}, Similarity: ${item.similarity}`);
}

3. Hybrid Search (Combined Dense + Sparse)

Search using both dense and sparse components for the most powerful results:

const results = await index.query({
  vector: [0.2, 0.3, 0.4, /* ... 384 dimensions */], // Dense query
  sparseIndices: [5, 42, 100], // Sparse query indices
  sparseValues: [0.8, 0.6, 0.9], // Sparse query values
  topK: 10,
  ef: 128
});

// Results combine both dense and sparse signals
for (const item of results) {
  console.log(`ID: ${item.id}, Similarity: ${item.similarity}`);
}

Use Cases for Hybrid Indexes

Hybrid indexes are ideal for scenarios where you want to combine:

  • Semantic similarity (dense embeddings from transformers) with keyword matching (sparse TF-IDF or BM25 features)
  • Neural embeddings with categorical features or one-hot encodings
  • Content-based recommendations (dense) with collaborative filtering signals (sparse)

Key Points

  • Hybrid indexes are optional: Create them only when you need sparse vector support
  • Dense-only indexes: Work with traditional dense vectors only (no sparse support)
  • Sparse data validation: The system validates sparse indices are within bounds and arrays match in length
  • Flexible querying: You can query with dense only, sparse only, or both on hybrid indexes
  • Backward compatible: Dense-only indexes continue to work as before

Filtering

Endee supports structured filtering using operators like $eq, $in, and $range. For detailed documentation on filtering, see the official documentation.

Encryption

Endee supports optional client-side encryption for your metadata using AES-256-CBC. When encryption is enabled, your metadata is encrypted before being sent to the server and decrypted when retrieved. The encryption key never leaves your environment.

Generating an Encryption Key

import { Endee } from "endee";

const endee = new Endee("<your-auth-token>");

// Generate a secure 256-bit encryption key
const key = endee.generateKey();

Important: Store your encryption key securely. If you lose the key, you will not be able to decrypt your data. The key is a 64-character hexadecimal string (256 bits).

Creating an Encrypted Index

// Create an index with encryption enabled
await endee.createIndex({
  name: "encrypted_index",
  dimension: 128,
  spaceType: "cosine",
  key: key // Pass your encryption key
});

Working with Encrypted Data

// Get index reference with encryption key
const index = await endee.getIndex("encrypted_index", key);

// Insert vectors - metadata will be automatically encrypted
await index.upsert([
  {
    id: "secret_doc",
    vector: [0.1, 0.2, 0.3 /* ... */], // Your vector data,
    meta: { 
      content: "This is sensitive information",
      userId: "user123"
    },
  },
]);

// Query vectors - metadata will be automatically decrypted
const results = await index.query({
  vector: [0.2, 0.4, 0.3 /* ... */], // Your query data,
  topK: 10,
});

// Results contain decrypted metadata
for (const item of results) {
  console.log(item.meta); // { content: "This is sensitive information", userId: "user123" }
}

Key Points

  • Encryption is optional: Only enable it if you need to protect sensitive metadata
  • Key management is your responsibility: Store keys securely (e.g., environment variables, secret managers)
  • Vectors are not encrypted: Only metadata is encrypted; vector data remains searchable
  • Key verification: The system verifies the key checksum when accessing an encrypted index
  • No key recovery: Lost keys cannot be recovered; encrypted data becomes inaccessible

Precision Options

Endee supports different quantization precision levels to optimize storage and performance:

import { Precision } from "endee";

// Available precision options:
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

// Example usage:
await endee.createIndex({
  name: "high_precision_index",
  dimension: 1536,
  spaceType: "cosine",
  precision: Precision.FLOAT32 // Use full 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

TypeScript Types

The package includes comprehensive TypeScript types:

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

Requirements

  • Node.js >= 18.0.0
  • TypeScript >= 5.0.0 (for development)

License

MIT

Author

Pankaj Singh