
Company News
Socket Joins New OpenJS Program to Fund Node.js Security Work
Socket is joining the OpenJS Security Stewardship Program to fund Node.js vulnerability research, maintainer remediation, and security releases.
@memberjunction/ai-vectors-memory
Advanced tools
An in-memory vector similarity search and clustering service for MemberJunction. Provides six distance metrics, two clustering algorithms (K-Means and DBSCAN), and comprehensive utility methods for vector analysis -- all without requiring an external vector database.
graph TD
subgraph MemoryPkg["@memberjunction/ai-vectors-memory"]
SVS["SimpleVectorService<TMetadata>"]
subgraph Search["Similarity Search"]
FN["FindNearest"]
FS["FindSimilar"]
FAT["FindAboveThreshold"]
end
subgraph Metrics["Distance Metrics"]
COS["Cosine"]
EUC["Euclidean"]
MAN["Manhattan"]
DOT["Dot Product"]
JAC["Jaccard"]
HAM["Hamming"]
end
subgraph Clustering["Clustering"]
KM["K-Means (K-Means++)"]
DBS["DBSCAN"]
EM["Elbow Method"]
end
subgraph Evaluation["Evaluation"]
SIL["Silhouette Score"]
WCD["Within-Cluster Distance"]
BCD["Between-Cluster Distance"]
CENT["Find Centroid"]
end
end
FN --> Metrics
FS --> FN
FAT --> FN
KM --> Metrics
DBS --> FN
style MemoryPkg fill:#2d6a9f,stroke:#1a4971,color:#fff
style Search fill:#2d8659,stroke:#1a5c3a,color:#fff
style Metrics fill:#b8762f,stroke:#8a5722,color:#fff
style Clustering fill:#7c5295,stroke:#563a6b,color:#fff
style Evaluation fill:#2d8659,stroke:#1a5c3a,color:#fff
npm install @memberjunction/ai-vectors-memory
Unlike the other vector packages that depend on external vector databases (Pinecone, etc.), this package operates entirely in-memory. It is ideal for:
The SimpleVectorService class is generic (SimpleVectorService<TMetadata>) for type-safe metadata access.
import { SimpleVectorService, VectorEntry } from '@memberjunction/ai-vectors-memory';
const service = new SimpleVectorService();
// Load vectors
service.LoadVectors([
{ key: 'doc1', vector: [0.1, 0.2, 0.3], metadata: { title: 'Document 1' } },
{ key: 'doc2', vector: [0.4, 0.5, 0.6], metadata: { title: 'Document 2' } },
{ key: 'doc3', vector: [0.7, 0.8, 0.9], metadata: { title: 'Document 3' } }
]);
// Find nearest neighbors
const results = service.FindNearest([0.15, 0.25, 0.35], 2);
results.forEach(r => console.log(`${r.key}: ${r.score.toFixed(3)}`));
classDiagram
class SimpleVectorService~TMetadata~ {
+LoadVectors(entries) void
+AddVector(key, vector, metadata?) void
+AddOrUpdateVector(key, vector, metadata?) boolean
+UpdateVector(key, updates) boolean
+FindNearest(query, topK, threshold?, metric?, filter?) VectorSearchResult[]
+FindSimilar(key, topK, threshold?, metric?, filter?) VectorSearchResult[]
+FindAboveThreshold(query, threshold, metric?, filter?) VectorSearchResult[]
+Similarity(key1, key2) number
+CalculateDistance(a, b, metric?) number
+KMeansCluster(k, maxIter?, metric?, tolerance?) ClusterResult
+DBSCANCluster(epsilon, minPoints, metric?, filter?) ClusterResult
+ElbowMethod(minK, maxK, metric?) Map
+SilhouetteScore(result, metric?) number
+WithinClusterDistance(result, metric?) number
+BetweenClusterDistance(result, metric?) number
+FindCentroid(vectors) number[]
+Size : number
+ExpectedDimensions : number
+GetVector(key) number[]
+GetMetadata(key) TMetadata
+RemoveVector(key) boolean
+ExportVectors() VectorEntry[]
+Clear() void
+Has(key) boolean
+GetAllKeys() string[]
}
class VectorEntry~TMetadata~ {
+key : string
+vector : number[]
+metadata? : TMetadata
}
class VectorSearchResult~TMetadata~ {
+key : string
+score : number
+metadata? : TMetadata
}
class ClusterResult~TMetadata~ {
+clusters : Map~number, string[]~
+centroids? : Map~number, number[]~
+outliers? : string[]
+metadata? : ClusterMetadata
}
SimpleVectorService --> VectorEntry : stores
SimpleVectorService --> VectorSearchResult : returns
SimpleVectorService --> ClusterResult : returns
style SimpleVectorService fill:#2d6a9f,stroke:#1a4971,color:#fff
style VectorEntry fill:#2d8659,stroke:#1a5c3a,color:#fff
style VectorSearchResult fill:#2d8659,stroke:#1a5c3a,color:#fff
style ClusterResult fill:#7c5295,stroke:#563a6b,color:#fff
type DistanceMetric = 'cosine' | 'euclidean' | 'manhattan' | 'dotproduct' | 'jaccard' | 'hamming';
All metrics are normalized to a 0-1 range where 1 = most similar.
| Metric | Best For | Formula |
|---|---|---|
cosine (default) | Text embeddings, semantic search | (dot(A,B) / (norm(A) * norm(B)) + 1) / 2 |
euclidean | Physical measurements, specs | 1 / (1 + sqrt(sum((a-b)^2))) |
manhattan | Grid navigation, time series | 1 / (1 + sum(abs(a-b))) |
dotproduct | Recommendations, weighted scoring | (tanh(dot(A,B) / sqrt(n)) + 1) / 2 |
jaccard | Categorical/binary data, set comparison | intersection / union |
hamming | Configuration drift, error detection | 1 - (differences / length) |
K-nearest neighbor search with optional threshold and metadata pre-filtering.
const results = service.FindNearest(
queryVector, // vector to search for
10, // topK results
0.7, // minimum similarity threshold
'cosine', // distance metric
(meta) => meta.status === 'active' // pre-filter by metadata
);
Pre-filtering happens before similarity calculation, making filtered searches significantly faster than post-filtering.
Find vectors similar to an existing stored vector (excludes the source vector from results).
const similar = service.FindSimilar('doc-123', 5, 0.8, 'cosine');
Return all vectors above a similarity threshold (no topK limit).
const matches = service.FindAboveThreshold(queryVector, 0.9, 'cosine');
// Add individual vectors
service.AddVector('key1', [0.1, 0.2, 0.3], { category: 'A' });
// Add or update (upsert)
const wasUpdate = service.AddOrUpdateVector('key1', [0.4, 0.5, 0.6]);
// Update in place (vector, metadata, or both)
service.UpdateVector('key1', { metadata: { category: 'B' } });
// Remove
service.RemoveVector('key1');
// Bulk load from array or Map
service.LoadVectors(new Map([['k1', [1, 2, 3]], ['k2', [4, 5, 6]]]));
// Export for persistence
const allVectors = service.ExportVectors();
Dimension validation is automatic -- all vectors must have the same dimensionality.
Partitions vectors into K clusters by minimizing within-cluster variance.
const result = service.KMeansCluster(3, 100, 'euclidean', 0.0001);
result.clusters.forEach((members, clusterId) => {
const centroid = result.centroids.get(clusterId);
console.log(`Cluster ${clusterId}: ${members.length} members`);
});
console.log(`Silhouette: ${result.metadata.silhouetteScore.toFixed(3)}`);
console.log(`Converged in ${result.metadata.iterations} iterations`);
Density-based clustering that automatically determines the number of clusters and identifies outliers.
const result = service.DBSCANCluster(
0.3, // epsilon (max distance for neighbors)
3, // minPoints (minimum cluster density)
'euclidean', // metric
(meta) => meta.active // optional pre-filter
);
console.log(`Found ${result.clusters.size} clusters`);
console.log(`Outliers: ${result.outliers?.length ?? 0}`);
Find the optimal number of clusters by testing a range of K values.
const elbowData = service.ElbowMethod(2, 10, 'euclidean');
elbowData.forEach((inertia, k) => {
console.log(`k=${k}: inertia=${inertia.toFixed(2)}`);
});
graph LR
CR["ClusterResult"] --> SIL["SilhouetteScore<br/>-1 to 1<br/>(higher = better)"]
CR --> WCD["WithinClusterDistance<br/>0 to 1<br/>(lower = tighter)"]
CR --> BCD["BetweenClusterDistance<br/>0 to 1<br/>(higher = more separated)"]
CR --> CENT["FindCentroid<br/>mean vector"]
style CR fill:#2d6a9f,stroke:#1a4971,color:#fff
style SIL fill:#2d8659,stroke:#1a5c3a,color:#fff
style WCD fill:#b8762f,stroke:#8a5722,color:#fff
style BCD fill:#b8762f,stroke:#8a5722,color:#fff
style CENT fill:#7c5295,stroke:#563a6b,color:#fff
| Method | Returns | Interpretation |
|---|---|---|
SilhouetteScore | -1 to 1 | > 0.7 strong, 0.5-0.7 reasonable, < 0.25 no structure |
WithinClusterDistance | 0 to 1 | Lower = tighter clusters (more cohesive) |
BetweenClusterDistance | 0 to 1 | Higher = better separated clusters |
FindCentroid | number[] | Mean position of a vector set |
Use TypeScript generics for type-safe metadata access:
interface ProductMetadata {
name: string;
category: string;
price: number;
}
const service = new SimpleVectorService<ProductMetadata>();
service.AddVector('prod1', embedding, { name: 'Widget', category: 'Tools', price: 29.99 });
const results = service.FindNearest(queryVector, 5);
results.forEach(r => {
// TypeScript knows r.metadata is ProductMetadata
console.log(`${r.metadata.name}: $${r.metadata.price}`);
});
| Operation | Complexity | Notes |
|---|---|---|
| AddVector / LoadVectors | O(1) per vector | Map-based storage |
| FindNearest (no filter) | O(n) | Linear scan with sort |
| FindNearest (with filter) | O(m) where m < n | Filter reduces candidate set |
| KMeansCluster | O(n * k * iterations) | K-Means++ initialization |
| DBSCANCluster | O(n^2) | Neighborhood pre-computation |
Memory usage: approximately 8 bytes * dimensions + ~100 bytes per vector. Example: 10,000 vectors at 384 dimensions is roughly 31 MB.
This package ships two VectorDBBase driver implementations so the in-memory primitive can be consumed by the broader vector-sync / EntityDocument infrastructure without standing up a remote store:
SimpleVectorDatabaseIn-process VectorDBBase driver that reads from an MJ: Vector Indexes row configured to point at any entity and field. Use when you have arbitrary entity rows with embeddings stored in a column and want to make them queryable through the SearchEngine cross-scope fusion path.
SimpleVectorServiceProvider (new in v5.38)EntityDocument-keyed in-process driver, purpose-built for Provider.SearchEntities() and any other EntityDocument-backed search. Each "index" corresponds to one MJ: Entity Documents row; vectors come from MJ: Entity Record Documents.VectorJSON filtered by EntityDocumentID, and matches surface the underlying entity record's RecordID in their metadata (not the EntityRecordDocument PK).
import { SimpleVectorServiceProvider } from '@memberjunction/ai-vectors-memory';
const provider = new SimpleVectorServiceProvider();
const result = await provider.QueryIndex(
{ id: entityDocumentId, vector: queryEmbedding, topK: 10 },
contextUser
);
// result.data.matches[i].metadata.RecordID is the parent record's ID
Lazy cache: Map<EntityDocumentID, LoadedIndex> with TTL eviction (default 15 minutes). After the vector-sync pipeline writes back fresh embeddings, call SimpleVectorServiceProvider.InvalidateIndex(entityDocumentId) for deterministic cache refresh; TTL is the safety net.
Read-only: ingestion methods (CreateRecord, UpdateRecord, etc.) throw via the unsupported() path. The vector-sync pipeline writes EntityRecordDocument.VectorJSON directly; this driver just rehydrates from those rows.
When NOT to use: > a few thousand EntityRecordDocument rows per EntityDocument, multi-process deployments, scenarios that need a real ANN index (HNSW / IVF). For those, configure a remote provider (Pinecone, Qdrant, pgvector) on the EntityDocument's VectorDatabaseID instead.
| Package | Purpose |
|---|---|
@memberjunction/core | LogError, RunView, UserInfo |
@memberjunction/global | RegisterClass for VectorDBBase registrations |
@memberjunction/ai-vectordb | VectorDBBase contract that the two providers implement |
This package has minimal dependencies, making it lightweight and suitable for both server-side and client-side use.
# Build
npm run build
# Development mode
npm run start
Business Source License 1.1 — see LICENSE for details.
FAQs
MemberJunction: AI Vectors In-Memory Utilities
The npm package @memberjunction/ai-vectors-memory receives a total of 0 weekly downloads. As such, @memberjunction/ai-vectors-memory popularity was classified as not popular.
We found that @memberjunction/ai-vectors-memory demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 11 open source maintainers collaborating on the project.

Company News
Socket is joining the OpenJS Security Stewardship Program to fund Node.js vulnerability research, maintainer remediation, and security releases.

Security News
Two compromised GitHub Actions were re-enabled with malicious tags intact, exposing thousands of downstream repositories to Mini Shai-Hulud.

Research
/Security News
A malicious Firefox extension fetches its payload after installation to evade detection, steal Google session cookies, and automate account takeover.