feat(ts-sdk): add AWS Neptune Analytics vector store (#5797)

Co-authored-by: kartik-mem0 <kartik.labhshetwar@mem0.ai>
This commit is contained in:
Rod Boev
2026-07-10 19:51:05 +05:30
committed by GitHub
co-authored by kartik-mem0
parent ba9054e0c8
commit 33a0ed7559
12 changed files with 2886 additions and 33 deletions
+1
View File
@@ -36,6 +36,7 @@ export * from "./vector_stores/langchain";
export * from "./vector_stores/vectorize";
export * from "./vector_stores/azure_ai_search";
export * from "./vector_stores/pgvector";
export * from "./vector_stores/neptune_analytics";
export * from "./vector_stores/elasticsearch";
export * from "./vector_stores/upstash_vector";
export * from "./vector_stores/azure_mysql";
+4
View File
@@ -55,6 +55,7 @@ import { HuggingFaceEmbedder } from "../embeddings/huggingface";
import { LangchainVectorStore } from "../vector_stores/langchain";
import { AzureAISearch } from "../vector_stores/azure_ai_search";
import { PGVector } from "../vector_stores/pgvector";
import { NeptuneAnalyticsVectorStore } from "../vector_stores/neptune_analytics";
import { VertexAIEmbedder } from "../embeddings/vertexai";
import { ElasticsearchDB } from "../vector_stores/elasticsearch";
import { OpenSearchDB } from "../vector_stores/opensearch";
@@ -172,6 +173,9 @@ export class VectorStoreFactory {
return new VertexAIVectorSearch(config as any);
case "pgvector":
return new PGVector(config as any);
case "neptune":
case "neptune-analytics":
return new NeptuneAnalyticsVectorStore(config as any);
case "elasticsearch":
return new ElasticsearchDB(config as any);
case "opensearch":
@@ -1,4 +1,3 @@
import { createPool } from "mysql2/promise";
import type { Pool, RowDataPacket } from "mysql2/promise";
import { VectorStore } from "./base";
import { SearchFilters, VectorStoreConfig, VectorStoreResult } from "../types";
@@ -93,6 +92,17 @@ export class AzureMySQLDB implements VectorStore {
...(this.config.sslCa ? { ca: this.config.sslCa } : {}),
};
// Loaded dynamically: mysql2 is an optional peer dependency, so a static value import
// would break `import { Memory } from "mem0ai/oss"` for everyone else.
let createPool: typeof import("mysql2/promise").createPool;
try {
({ createPool } = await import("mysql2/promise"));
} catch {
throw new Error(
"The Azure MySQL vector store requires the 'mysql2' package. Install it with: npm install mysql2",
);
}
this.pool = createPool({
host: this.config.host,
port: this.config.port ?? 3306,
File diff suppressed because it is too large Load Diff
@@ -183,6 +183,12 @@ jest.mock("../src/vector_stores/pgvector", () => ({
.fn()
.mockImplementation((config) => ({ type: "pgvector", config })),
}));
jest.mock("../src/vector_stores/neptune_analytics", () => ({
NeptuneAnalyticsVectorStore: jest.fn().mockImplementation((config) => ({
type: "neptune-analytics",
config,
})),
}));
jest.mock("../src/vector_stores/upstash_vector", () => ({
UpstashVector: jest
.fn()
@@ -340,6 +346,8 @@ describe("VectorStoreFactory", () => {
["vectorize"],
["azure-ai-search"],
["pgvector"],
["neptune"],
["neptune-analytics"],
["upstash_vector"],
["azure_mysql"],
["cassandra"],
@@ -351,6 +359,30 @@ describe("VectorStoreFactory", () => {
expect(result.config).toBe(dummyVSConfig);
});
test("passes Neptune endpoint URI config through the factory", () => {
const config = {
collectionName: "test",
dimension: 4,
endpoint: "neptune-graph://g-1234567890",
region: "us-east-1",
};
const store = VectorStoreFactory.create("neptune", config) as any;
expect(store.config).toEqual(config);
});
test("keeps neptune-analytics as a compatibility alias", () => {
const config = {
collectionName: "test",
dimension: 4,
endpoint: "neptune-graph://g-1234567890",
region: "us-east-1",
};
const store = VectorStoreFactory.create("neptune-analytics", config) as any;
expect(store.config).toEqual(config);
});
test("throws for unsupported provider", () => {
expect(() =>
VectorStoreFactory.create("nonexistent", dummyVSConfig),
@@ -0,0 +1,70 @@
import { readdirSync, readFileSync, statSync } from "fs";
import { join, relative, resolve } from "path";
// Optional peers are not installed by npm/pnpm. A static value import of one therefore throws
// MODULE_NOT_FOUND the moment anything pulls in `mem0ai/oss`, because src/index.ts re-exports every
// vector store. Load them with `await import(...)` inside the code path that needs them instead.
const packageRoot = resolve(__dirname, "../../..");
function optionalPeers(): string[] {
const pkg = JSON.parse(
readFileSync(join(packageRoot, "package.json"), "utf8"),
);
return Object.entries(
(pkg.peerDependenciesMeta ?? {}) as Record<string, { optional?: boolean }>,
)
.filter(([, meta]) => meta.optional)
.map(([name]) => name);
}
function sourceFiles(dir: string, acc: string[] = []): string[] {
for (const entry of readdirSync(dir)) {
const full = join(dir, entry);
if (statSync(full).isDirectory()) {
if (entry !== "tests" && entry !== "__tests__") sourceFiles(full, acc);
} else if (entry.endsWith(".ts") && !entry.endsWith(".test.ts")) {
acc.push(full);
}
}
return acc;
}
// Matches `import ... from "pkg"` and `import "pkg"`, but not `import type ... from "pkg"`,
// `typeof import("pkg")`, or `await import("pkg")` — those are erased or already lazy.
function hasStaticValueImport(pkg: string, source: string): boolean {
const escaped = pkg.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const specifier = `["']${escaped}(?:\\/[^"']*)?["']`;
return (
new RegExp(
`(?:^|\\n)\\s*import\\s+(?!type\\b)[^;]*?from\\s+${specifier}`,
).test(source) ||
new RegExp(`(?:^|\\n)\\s*import\\s+${specifier}`).test(source)
);
}
describe("optional peer dependencies", () => {
const peers = optionalPeers();
it("are discoverable from package.json", () => {
expect(peers.length).toBeGreaterThan(0);
});
it("are never statically imported by src", () => {
const files = sourceFiles(join(packageRoot, "src"));
const sources = new Map(
files.map((file) => [file, readFileSync(file, "utf8")]),
);
const offenders: string[] = [];
for (const peer of peers) {
for (const [file, source] of sources) {
if (hasStaticValueImport(peer, source)) {
offenders.push(`${relative(packageRoot, file)} imports ${peer}`);
}
}
}
expect(offenders).toEqual([]);
});
});
File diff suppressed because it is too large Load Diff