Skip to content

Vector Stores

Production-ready vector storage for scalable RAG applications.

Overview

Vector stores provide efficient similarity search for embeddings. Meloqui supports three implementations:

  • InMemoryVectorStore - Zero-dependency, for development and testing
  • PineconeVectorStore - Cloud-native, serverless scaling
  • QdrantVectorStore - Self-hosted, privacy-first

Quick Start

Development (InMemory)

typescript
import { InMemoryVectorStore, OpenAIEmbeddings } from 'meloqui';

const embeddings = new OpenAIEmbeddings();
const vectorStore = new InMemoryVectorStore({
  dimensions: embeddings.dimensions
});

// Store vectors
const text = 'Hello world';
const embedding = await embeddings.embed(text);
await vectorStore.upsert([{
  id: 'doc-1',
  values: embedding,
  metadata: { text }
}]);

// Search
const queryEmbedding = await embeddings.embed('greeting');
const results = await vectorStore.search(queryEmbedding, { topK: 5 });
console.log(results[0].metadata?.text); // 'Hello world'

Production (Pinecone)

typescript
import {
  PineconeVectorStore,
  InMemoryDocumentStore,
  OpenAIEmbeddings
} from 'meloqui';

const vectorStore = new PineconeVectorStore({
  indexName: 'my-docs',
  dimensions: 1536
});

const documentStore = new InMemoryDocumentStore({
  embeddingProvider: new OpenAIEmbeddings(),
  vectorStore
});

await documentStore.addDocuments([
  { content: '...', source: 'manual.pdf' }
]);

Self-Hosted (Qdrant)

typescript
import { QdrantVectorStore, InMemoryDocumentStore } from 'meloqui';

const vectorStore = new QdrantVectorStore({
  url: 'http://qdrant.internal:6333',
  collectionName: 'support-docs',
  dimensions: 1536
});

const documentStore = new InMemoryDocumentStore({
  embeddingProvider: new OpenAIEmbeddings(),
  vectorStore
});

IVectorStore Interface

All vector stores implement the same interface:

typescript
interface IVectorStore {
  upsert(vectors: Vector[], namespace?: string): Promise<void>;
  search(vector: number[], options?: VectorSearchOptions, namespace?: string): Promise<VectorSearchResult[]>;
  delete(ids: string[], namespace?: string): Promise<void>;
  clear(namespace?: string): Promise<void>;
  readonly dimensions: number;
}

InMemoryVectorStore

Zero-dependency implementation for development.

When to use:

  • Development and testing
  • Small datasets (<50k vectors)
  • Prototyping

Configuration:

typescript
const store = new InMemoryVectorStore({
  dimensions: 1536,
  metric: 'cosine' // or 'euclidean', 'dotProduct'
});

Metrics:

  • cosine - Cosine similarity (default, best for normalized embeddings)
  • euclidean - Euclidean distance (good for spatial data)
  • dotProduct - Dot product (fast, for already-normalized vectors)

PineconeVectorStore

Cloud vector database with automatic scaling.

When to use:

  • Production applications
  • Large datasets (millions of vectors)
  • Serverless deployment

Setup:

  1. Install peer dependency:

    bash
    npm install @pinecone-database/pinecone
  2. Set API key:

    bash
    export PINECONE_API_KEY=your-api-key
  3. Create index in Pinecone dashboard

Configuration:

typescript
const store = new PineconeVectorStore({
  apiKey: process.env.PINECONE_API_KEY, // or auto-detected
  indexName: 'my-index',
  dimensions: 1536,
  batchSize: 100 // optional, for rate limiting
});

Features:

  • Automatic batching for API limits
  • Namespace support for multi-tenancy
  • Metadata filtering

QdrantVectorStore

Self-hosted vector database for privacy-sensitive deployments.

When to use:

  • On-premise deployments
  • Privacy requirements
  • Cost optimization

Setup:

  1. Install peer dependency:

    bash
    npm install @qdrant/js-client-rest
  2. Run Qdrant (Docker):

    bash
    docker run -p 6333:6333 qdrant/qdrant

Configuration:

typescript
const store = new QdrantVectorStore({
  url: 'http://localhost:6333',
  collectionName: 'my-collection',
  dimensions: 1536,
  distance: 'Cosine', // or 'Euclid', 'Dot'
  createIfNotExists: true
});

Features:

  • Auto-creates collections with HNSW index
  • Namespace via payload filtering
  • Advanced metadata filtering

Search Options

All vector stores support the same search options:

typescript
const results = await store.search(queryVector, {
  topK: 10,              // Number of results
  minScore: 0.8,         // Minimum similarity threshold
  includeMetadata: true, // Include metadata in results
  includeValues: false,  // Include vector values
  filter: {              // Metadata filter (implementation-specific)
    category: 'tech'
  }
});

Multi-Tenancy

Use namespaces to isolate data:

typescript
// Store per-tenant data
await store.upsert(customerDocs, `tenant-${customerId}`);

// Search within tenant
const results = await store.search(
  queryVector,
  { topK: 5 },
  `tenant-${customerId}`
);

// Clear tenant data
await store.clear(`tenant-${customerId}`);

Integration with DocumentStore

Document stores can delegate vector operations:

typescript
const documentStore = new InMemoryDocumentStore({
  embeddingProvider: new OpenAIEmbeddings(),
  vectorStore: new PineconeVectorStore({
    indexName: 'docs',
    dimensions: 1536
  })
});

// Chunks stored locally, vectors in Pinecone
await documentStore.addDocuments([...]);

// Search uses Pinecone
const results = await documentStore.search('query');

Error Handling

Vector store errors include context:

typescript
try {
  await store.upsert(vectors);
} catch (error) {
  if (error instanceof VectorStoreError) {
    console.log(error.store);      // 'pinecone'
    console.log(error.operation);  // 'upsert'
    console.log(error.isRetryable); // true for network errors
  }
}

Comparison

FeatureInMemoryPineconeQdrant
SetupNoneCloud accountDocker/self-host
CostFreeUsage-basedInfrastructure
Scale<50k vectorsMillionsMillions
LatencyMicroseconds~50ms~10ms (local)
Namespaces✓ (via payload)
Metadata filterBasicAdvancedAdvanced
DependenciesNonePeerPeer

Best Practices

Development:

  • Use InMemoryVectorStore for local testing
  • Keep test datasets small (<1k vectors)

Production:

  • Pinecone for managed, serverless
  • Qdrant for self-hosted, cost-optimized
  • Always specify dimensions explicitly
  • Use namespaces for multi-tenancy
  • Monitor vector store latency

Performance:

  • Batch upserts when possible
  • Use includeValues: false unless needed
  • Set appropriate minScore thresholds
  • Consider caching frequent queries

Migration Guide

From Custom Vector Storage

If you're currently managing vectors manually, migrating to IVectorStore provides:

  • Consistent API across different backends
  • Built-in namespace support for multi-tenancy
  • Automatic batching and optimization
  • Error handling with retry support

Step 1: Identify your current approach

typescript
// Before: Manual vector management
const vectors = new Map<string, number[]>();
vectors.set('doc-1', embedding1);

// Search with custom logic
const results = Array.from(vectors.entries())
  .map(([id, vec]) => ({ id, score: cosineSim(queryVec, vec) }))
  .sort((a, b) => b.score - a.score)
  .slice(0, 10);

Step 2: Choose implementation

  • Development/Testing: InMemoryVectorStore (drop-in replacement)
  • Production (<1M vectors): PineconeVectorStore (managed, simple)
  • Production (self-hosted): QdrantVectorStore (cost-effective, private)

Step 3: Migrate data

typescript
import { InMemoryVectorStore } from 'meloqui';

// Initialize new store
const store = new InMemoryVectorStore({ dimensions: 1536 });

// Migrate existing vectors
const vectorsToMigrate = Array.from(vectors.entries()).map(([id, values]) => ({
  id,
  values,
  metadata: existingMetadata.get(id) // Add metadata if available
}));

await store.upsert(vectorsToMigrate);

// Replace search logic
const results = await store.search(queryVector, { topK: 10 });

Step 4: Update document store integration

typescript
// Before: Separate vector and document management
const documentStore = new InMemoryDocumentStore({
  embeddingProvider: new OpenAIEmbeddings()
});

// After: Delegate vectors to vector store
const documentStore = new InMemoryDocumentStore({
  embeddingProvider: new OpenAIEmbeddings(),
  vectorStore: store // Optional delegation
});

From Pinecone SDK Directly

If you're using @pinecone-database/pinecone directly:

typescript
// Before: Direct Pinecone usage
import { Pinecone } from '@pinecone-database/pinecone';
const pc = new Pinecone({ apiKey });
const index = pc.index('my-index');
await index.upsert([{ id, values, metadata }]);

// After: Via IVectorStore
import { PineconeVectorStore } from 'meloqui';
const store = new PineconeVectorStore({
  indexName: 'my-index',
  dimensions: 1536
});
await store.upsert([{ id, values, metadata }]);

Benefits:

  • Automatic batching (no need to split upserts manually)
  • Consistent error handling across all operations
  • Namespace parameter standardization
  • Swap to other backends without code changes

From Qdrant Client Directly

If you're using @qdrant/js-client-rest directly:

typescript
// Before: Direct Qdrant usage
import { QdrantClient } from '@qdrant/js-client-rest';
const client = new QdrantClient({ url, apiKey });
await client.upsert('collection', {
  points: [{ id, vector: values, payload: metadata }]
});

// After: Via IVectorStore
import { QdrantVectorStore } from 'meloqui';
const store = new QdrantVectorStore({
  url,
  collectionName: 'collection',
  dimensions: 1536
});
await store.upsert([{ id, values, metadata }]);

Benefits:

  • Auto-creates collections with optimal HNSW configuration
  • Namespace abstraction via payload filtering
  • Consistent with Pinecone/InMemory APIs

See Also

Released under the MIT License.