Skip to content

@memberjunction/ai-vectordb

A provider-agnostic abstraction layer for vector databases in MemberJunction. This package defines the VectorDBBase abstract class and all associated types that concrete vector database implementations must fulfill. It contains no provider-specific logic — only the contracts, data structures, and query interfaces shared across all implementations.

graph TD
    subgraph Abstraction["@memberjunction/ai-vectordb"]
        VDBB["VectorDBBase (abstract)"]
        VR["VectorRecord"]
        QO["QueryOptions / HybridQueryOptions"]
        QR["QueryResponse / ScoredRecord"]
        IDX["IndexDescription / IndexList"]
        BR["BaseResponse"]
    end

    subgraph Implementations["Provider Implementations"]
        PC["PineconeDatabase"]
        CUSTOM["Custom Provider"]
    end

    subgraph Consumers["Consuming Packages"]
        SYNC["ai-vector-sync"]
        DUPE["ai-vector-dupe"]
        CORE["ai-vectors (VectorBase)"]
    end

    PC -->|extends| VDBB
    CUSTOM -->|extends| VDBB
    SYNC --> VDBB
    DUPE --> VDBB
    CORE --> VDBB

    style Abstraction fill:#2d6a9f,stroke:#1a4971,color:#fff
    style Implementations fill:#2d8659,stroke:#1a5c3a,color:#fff
    style Consumers fill:#7c5295,stroke:#563a6b,color:#fff

All vector database providers extend VectorDBBase and implement its abstract methods. Consumer code programs against the base class, making it straightforward to swap providers without changing application logic.

Terminal window
npm install @memberjunction/ai-vectordb
import { VectorDBBase, VectorRecord } from '@memberjunction/ai-vectordb';
// Use any concrete provider that extends VectorDBBase
const db: VectorDBBase = new PineconeDatabase(apiKey);
// Insert a vector record
const record: VectorRecord = {
id: 'doc-001',
values: [0.1, 0.2, 0.3 /* ... */],
metadata: { entity: 'Products', recordId: '12345' }
};
await db.createRecord(record);
// Query for similar vectors
const result = await db.queryIndex({
vector: [0.1, 0.2, 0.3 /* ... */],
topK: 10,
includeMetadata: true
});
if (result.success) {
for (const match of result.data.matches) {
console.log(`Match: ${match.id} (score: ${match.score})`);
}
}

All vector database providers must extend this class. The constructor requires an API key, which is validated (must be non-empty) and stored for subclass access via a protected getter.

classDiagram
    class VectorDBBase {
        <<abstract>>
        #apiKey : string
        +constructor(apiKey: string)
        +SupportsHybridSearch : boolean
        +listIndexes()* IndexList
        +getIndex(params)* BaseResponse
        +createIndex(params)* BaseResponse
        +deleteIndex(params)* BaseResponse
        +editIndex(params)* BaseResponse
        +queryIndex(params)* BaseResponse
        +HybridQuery(params) BaseResponse
        +createRecord(record)* BaseResponse
        +createRecords(records)* BaseResponse
        +getRecord(params)* BaseResponse
        +getRecords(params)* BaseResponse
        +updateRecord(record)* BaseResponse
        +updateRecords(records)* BaseResponse
        +deleteRecord(record)* BaseResponse
        +deleteRecords(records)* BaseResponse
    }

    class PineconeDatabase {
        +listIndexes() IndexList
        +queryIndex(params) BaseResponse
    }

    class CustomProvider {
        +SupportsHybridSearch : boolean
        +HybridQuery(params) BaseResponse
    }

    VectorDBBase <|-- PineconeDatabase
    VectorDBBase <|-- CustomProvider

    style VectorDBBase fill:#2d6a9f,stroke:#1a4971,color:#fff
    style PineconeDatabase fill:#2d8659,stroke:#1a5c3a,color:#fff
    style CustomProvider fill:#2d8659,stroke:#1a5c3a,color:#fff

All abstract methods use union return types (BaseResponse | Promise<BaseResponse>) so subclasses can implement them as either synchronous or asynchronous.

MethodParametersReturnsDescription
listIndexes()noneIndexListList all indexes in the project.
getIndex(params)BaseRequestParamsBaseResponseRetrieve configuration details for a single index by ID.
createIndex(params)CreateIndexParamsBaseResponseCreate a new vector index with specified dimension, metric, and optional provider-specific params.
deleteIndex(params)BaseRequestParamsBaseResponsePermanently delete an index and all its records.
editIndex(params)EditIndexParamsBaseResponseModify an existing index’s configuration.
queryIndex(params)QueryOptionsBaseResponseExecute a similarity search against an index by vector values or by record ID.
MethodParametersReturnsDescription
createRecord(record)VectorRecordBaseResponseInsert a single vector record into an index.
createRecords(records)VectorRecord[]BaseResponseInsert multiple vector records in batch.
getRecord(params)BaseRequestParamsBaseResponseFetch a single record by ID.
getRecords(params)BaseRequestParamsBaseResponseFetch multiple records (IDs passed via data).
updateRecord(record)UpdateOptionsBaseResponseUpdate a single record’s vector values and/or metadata.
updateRecords(records)UpdateOptionsBaseResponseUpdate multiple records in batch.
deleteRecord(record)VectorRecordBaseResponseDelete a single record from an index.
deleteRecords(records)VectorRecord[]BaseResponseDelete multiple records in batch.
MethodParametersReturnsDescription
SupportsHybridSearch (getter)nonebooleanReturns false by default. Override to return true in providers that implement HybridQuery().
HybridQuery(params)HybridQueryOptionsBaseResponsePerform a hybrid search combining vector similarity with keyword matching. Default implementation falls back to queryIndex().

Hybrid search combines dense vector similarity (semantic search) with sparse keyword matching (BM25) to produce more relevant results than either technique alone. This is particularly useful when queries contain specific terms (product names, codes, identifiers) that benefit from exact text matching alongside semantic understanding.

  1. Check support: Read the SupportsHybridSearch getter before calling HybridQuery().
  2. Call HybridQuery: Pass both a vector embedding and an optional keyword query string.
  3. Fallback behavior: If the provider does not override HybridQuery(), the base class automatically falls back to a pure vector queryIndex() call, ignoring keyword-specific parameters (KeywordQuery, Alpha, FusionMethod). This means consumer code does not need to branch on provider capability — it is always safe to call HybridQuery().

HybridQueryOptions extends QueryParamsBase with the following additional fields:

PropertyTypeRequiredDescription
vectorRecordValuesYesEmbedding vector for the semantic similarity component.
KeywordQuerystringNoKeyword query string for BM25/text search.
AlphanumberNoBalance between vector and keyword results. 0.0 = pure keyword, 1.0 = pure vector. Default: 0.7.
FusionMethod'rrf' | 'weighted'NoStrategy for fusing vector and keyword result lists. Default: 'rrf' (Reciprocal Rank Fusion).

Plus the inherited QueryParamsBase fields: topK, includeValues, includeMetadata, filter.

import { VectorDBBase, HybridQueryOptions, BaseResponse } from '@memberjunction/ai-vectordb';
export class WeaviateDB extends VectorDBBase {
public override get SupportsHybridSearch(): boolean {
return true;
}
public override async HybridQuery(params: HybridQueryOptions): Promise<BaseResponse> {
const results = await this.client.hybridSearch({
vector: params.vector,
query: params.KeywordQuery,
alpha: params.Alpha ?? 0.7,
fusionType: params.FusionMethod ?? 'rrf',
limit: params.topK,
includeMetadata: params.includeMetadata
});
return { success: true, message: 'Hybrid query complete', data: results };
}
// ... remaining abstract method implementations
}
const db: VectorDBBase = getVectorProvider();
// Safe to call regardless of provider -- falls back to pure vector search
const results = await db.HybridQuery({
vector: embedding,
KeywordQuery: 'quarterly revenue report',
Alpha: 0.6,
topK: 20,
includeMetadata: true
});
// Or check support for conditional UI behavior
if (db.SupportsHybridSearch) {
showKeywordSearchInput();
}

Follow these steps to add support for a new vector database (e.g., Weaviate, Qdrant, Milvus).

import {
VectorDBBase,
BaseRequestParams,
BaseResponse,
CreateIndexParams,
EditIndexParams,
IndexList,
QueryOptions,
UpdateOptions,
VectorRecord
} from '@memberjunction/ai-vectordb';
export class QdrantDB extends VectorDBBase {
private client: QdrantClient;
constructor(apiKey: string, host: string) {
super(apiKey); // Validates the API key
this.client = new QdrantClient({ apiKey: this.apiKey, host });
}
async listIndexes(): Promise<IndexList> {
const collections = await this.client.getCollections();
return {
indexes: collections.map(c => ({
name: c.name,
dimension: c.vectors.size,
metric: c.vectors.distance,
host: this.host
}))
};
}
async getIndex(params: BaseRequestParams): Promise<BaseResponse> {
const info = await this.client.getCollection(params.id);
return { success: true, message: 'Index retrieved', data: info };
}
async createIndex(params: CreateIndexParams): Promise<BaseResponse> {
await this.client.createCollection(params.id, {
vectors: { size: params.dimension, distance: params.metric }
});
return { success: true, message: 'Index created', data: null };
}
async deleteIndex(params: BaseRequestParams): Promise<BaseResponse> {
await this.client.deleteCollection(params.id);
return { success: true, message: 'Index deleted', data: null };
}
async editIndex(params: EditIndexParams): Promise<BaseResponse> {
await this.client.updateCollection(params.id, params.data);
return { success: true, message: 'Index updated', data: null };
}
async queryIndex(params: QueryOptions): Promise<BaseResponse> {
const results = await this.client.search(params);
return { success: true, message: 'Query complete', data: results };
}
async createRecord(record: VectorRecord): Promise<BaseResponse> {
await this.client.upsert([record]);
return { success: true, message: 'Record created', data: null };
}
async createRecords(records: VectorRecord[]): Promise<BaseResponse> {
await this.client.upsert(records);
return { success: true, message: `${records.length} records created`, data: null };
}
async getRecord(params: BaseRequestParams): Promise<BaseResponse> {
const record = await this.client.retrieve(params.id);
return { success: true, message: 'Record retrieved', data: record };
}
async getRecords(params: BaseRequestParams): Promise<BaseResponse> {
const records = await this.client.retrieveMany(params.data);
return { success: true, message: 'Records retrieved', data: records };
}
async updateRecord(record: UpdateOptions): Promise<BaseResponse> {
await this.client.update(record);
return { success: true, message: 'Record updated', data: null };
}
async updateRecords(records: UpdateOptions): Promise<BaseResponse> {
await this.client.updateMany(records);
return { success: true, message: 'Records updated', data: null };
}
async deleteRecord(record: VectorRecord): Promise<BaseResponse> {
await this.client.delete(record.id);
return { success: true, message: 'Record deleted', data: null };
}
async deleteRecords(records: VectorRecord[]): Promise<BaseResponse> {
await this.client.deleteMany(records.map(r => r.id));
return { success: true, message: 'Records deleted', data: null };
}
}

Step 2: Register with MemberJunction Class Factory (Optional)

Section titled “Step 2: Register with MemberJunction Class Factory (Optional)”

If you want MemberJunction’s class factory to discover your provider automatically:

import { RegisterClass } from '@memberjunction/global';
@RegisterClass(VectorDBBase, 'QdrantDB')
export class QdrantDB extends VectorDBBase {
// ... implementation
}

Step 3: Add Hybrid Search Support (Optional)

Section titled “Step 3: Add Hybrid Search Support (Optional)”

If your vector database supports hybrid search natively, override the getter and method:

export class QdrantDB extends VectorDBBase {
public override get SupportsHybridSearch(): boolean {
return true;
}
public override async HybridQuery(params: HybridQueryOptions): Promise<BaseResponse> {
// Use your provider's native hybrid search API
const results = await this.client.hybridSearch({
vector: params.vector,
keyword: params.KeywordQuery,
alpha: params.Alpha ?? 0.7,
limit: params.topK
});
return { success: true, message: 'Hybrid query complete', data: results };
}
}

QueryOptions is a union type supporting two query strategies:

graph LR
    QPB["QueryParamsBase<br/>topK, includeValues,<br/>includeMetadata, filter"]
    QBV["QueryByVectorValues<br/>+ vector"]
    QBID["QueryByRecordId<br/>+ id"]
    QO["QueryOptions"]

    QPB --> QBV
    QPB --> QBID
    QBV --> QO
    QBID --> QO

    style QPB fill:#2d6a9f,stroke:#1a4971,color:#fff
    style QBV fill:#2d8659,stroke:#1a5c3a,color:#fff
    style QBID fill:#2d8659,stroke:#1a5c3a,color:#fff
    style QO fill:#b8762f,stroke:#8a5722,color:#fff

Shared query parameters inherited by both query strategies.

PropertyTypeRequiredDescription
topKnumberYesNumber of results to return.
includeValuesbooleanNoWhether to include embedding vectors in results. Default: false.
includeMetadatabooleanNoWhether to include metadata in results. Default: false.
filterobjectNoMetadata filter to narrow the search scope.

Query by providing a vector directly. Extends QueryParamsBase.

PropertyTypeRequiredDescription
vectorRecordValuesYesVector values output from an embedding model.

Query using an existing record’s stored vector. Extends QueryParamsBase.

PropertyTypeRequiredDescription
idstringYesID of an existing record whose vector to use for the query.
// Query by vector values
const resultByVector = await db.queryIndex({
vector: [0.1, 0.2, 0.3],
topK: 5,
includeMetadata: true,
filter: { category: 'finance' }
});
// Query by record ID (find similar to an existing record)
const resultById = await db.queryIndex({
id: 'doc-001',
topK: 10,
includeMetadata: true
});
TypeDescription
VectorRecord<T>A vector record with id, values (dense embedding), optional sparseValues, and optional metadata. Generic over metadata type.
RecordValuesArray<number> — a dense embedding vector.
RecordSparseValuesSparse vector representation with parallel indices and values arrays for non-zero positions.
RecordMetadataRecord<string, RecordMetadataValue> — arbitrary key-value metadata attached to records.
RecordMetadataValuestring | boolean | number | Array<string> — valid metadata value types.
BaseResponseStandard response envelope: success (boolean), message (string), data (result payload).
TypeDescription
IndexDescriptionDescribes an index: name (max 45 chars), dimension, metric, and host URL.
IndexListContainer with an optional indexes array of IndexDescription.
IndexModelMetricEnum'cosine' | 'euclidean' | 'dotproduct' distance metrics for similarity comparison.
TypeDescription
BaseRequestParamsBase request with id (string) and optional data.
CreateIndexParamsExtends BaseRequestParams with dimension (number), metric (IndexModelMetricEnum), and optional additionalParams.
EditIndexParamsExtends BaseRequestParams for index configuration changes.
UpdateOptions<T>Record update with id, optional values, optional sparseValues, and optional metadata. Values are optional to support metadata-only updates.
TypeDescription
QueryParamsBaseShared query parameters: topK, includeValues, includeMetadata, filter.
QueryByRecordIdExtends QueryParamsBase with id — query using an existing record’s vector.
QueryByVectorValuesExtends QueryParamsBase with vector — query using a raw embedding array.
QueryOptionsUnion of QueryByRecordId | QueryByVectorValues.
HybridQueryOptionsExtends QueryParamsBase with vector, KeywordQuery, Alpha, and FusionMethod for combined vector + keyword search.
QueryResponse<T>Query result containing matches (sorted ScoredRecord array), namespace, and optional usage.
ScoredRecord<T>Extends VectorRecord with a score field. Score interpretation depends on the index metric.
OperationUsageUsage metrics with optional readUnits count.

The IndexModelMetricEnum supports three metrics for similarity comparison:

MetricValueDescriptionScore Interpretation
Cosine'cosine'Measures angle between vectors (direction similarity)Higher = more similar
Euclidean'euclidean'Straight-line distance between vector endpointsLower = more similar
Dot Product'dotproduct'Measures both direction and magnitude alignmentHigher = more similar
PackageProviderHybrid Search
@memberjunction/ai-vectors-pineconePineconeNo

Create additional implementations by extending VectorDBBase and optionally registering with MemberJunction’s class factory.

PackagePurpose
@memberjunction/coreCore MemberJunction functionality
@memberjunction/globalGlobal utilities and class registration
Terminal window
# Build
cd packages/AI/Vectors/Database
npm run build
# Run tests
npm run test
# Watch mode
npm run test:watch

ISC