fix(ts-sdk): stop forcing pg on installs (optional ranged pg peer, optional natural) (#7450)

This commit is contained in:
Kartik
2026-09-25 20:46:24 +05:30
committed by GitHub
parent 8127e8bd47
commit 947ac79818
5 changed files with 93 additions and 57 deletions
@@ -5,6 +5,12 @@ description: "Use pgvector as a vector store in Mem0 for PostgreSQL-based vector
[pgvector](https://github.com/pgvector/pgvector) is an open-source vector similarity search extension for Postgres. After connecting to Postgres, run `CREATE EXTENSION IF NOT EXISTS vector;` to create the vector extension.
The TypeScript SDK loads the `pg` driver only when you use this store, so install it alongside `mem0ai`:
```bash
npm install pg
```
### Usage
<CodeGroup>
+13 -4
View File
@@ -100,7 +100,8 @@
"tsup": "^8.3.0",
"typescript": "5.5.4",
"iovalkey": "^0.3.3",
"@mochow/mochow-sdk-node": "^2.1.5"
"@mochow/mochow-sdk-node": "^2.1.5",
"@types/jest": "^29.5.14"
},
"dependencies": {
"axios": "^1.18.0",
@@ -127,8 +128,7 @@
"@qdrant/js-client-rest": "^1.18.0",
"@supabase/supabase-js": "^2.49.1",
"@turbopuffer/turbopuffer": "^2.0.0",
"@types/jest": "29.5.14",
"@types/pg": "8.11.0",
"@types/pg": "^8.11.0",
"@upstash/vector": "^1.2.3",
"better-sqlite3": "^12.6.2",
"cassandra-driver": "4.8.0",
@@ -141,7 +141,7 @@
"weaviate-client": "^3.0.0",
"ollama": "^0.5.14",
"oracledb": "^6.5.0 || ^7.0.0",
"pg": "8.11.3",
"pg": "^8.11.3",
"redis": "^4.6.13",
"@elastic/elasticsearch": "^9.0.0",
"iovalkey": "^0.3.3",
@@ -257,6 +257,15 @@
},
"oracledb": {
"optional": true
},
"pg": {
"optional": true
},
"@types/pg": {
"optional": true
},
"natural": {
"optional": true
}
},
"engines": {
+6 -42
View File
@@ -109,11 +109,8 @@ importers:
'@turbopuffer/turbopuffer':
specifier: ^2.0.0
version: 2.5.0
'@types/jest':
specifier: 29.5.14
version: 29.5.14
'@types/pg':
specifier: 8.11.0
specifier: ^8.11.0
version: 8.11.0
'@upstash/vector':
specifier: ^1.2.3
@@ -167,8 +164,8 @@ importers:
specifier: ^6.5.0 || ^7.0.0
version: 7.0.1
pg:
specifier: 8.11.3
version: 8.11.3
specifier: ^8.11.3
version: 8.21.0
redis:
specifier: ^4.6.13
version: 4.7.1
@@ -191,6 +188,9 @@ importers:
'@types/better-sqlite3':
specifier: ^7.6.13
version: 7.6.13
'@types/jest':
specifier: ^29.5.14
version: 29.5.14
'@types/node':
specifier: ^22.7.6
version: 22.19.21
@@ -2228,10 +2228,6 @@ packages:
buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
buffer-writer@2.0.0:
resolution: {integrity: sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==}
engines: {node: '>=4'}
buffer@5.7.1:
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
@@ -3846,9 +3842,6 @@ packages:
package-json-from-dist@1.0.1:
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
packet-reader@1.0.0:
resolution: {integrity: sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==}
pad-left@2.1.0:
resolution: {integrity: sha512-HJxs9K9AztdIQIAIa/OIazRAUW/L6B9hbQDxO4X07roW3eo9XqZc2ur9bn1StH9CnbbI9EgvejHQX7CBpCF1QA==}
engines: {node: '>=0.10.0'}
@@ -3920,15 +3913,6 @@ packages:
resolution: {integrity: sha512-o2XFanIMy/3+mThw69O8d4n1E5zsLhdO+OPqswezu7Z5ekP4hYDqlDjlmOpYMbzY2Br0ufCwJLdDIXeNVwcWFg==}
engines: {node: '>=10'}
pg@8.11.3:
resolution: {integrity: sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==}
engines: {node: '>= 8.0.0'}
peerDependencies:
pg-native: '>=3.0.1'
peerDependenciesMeta:
pg-native:
optional: true
pg@8.21.0:
resolution: {integrity: sha512-AUP1EYJuHraQGsVoCQVIcM7TEJVGtDzxWtGFZd8rds9d+CCXlU5Js1rYgfLNvxy9iJrpHjGrRjoi/3BT9fRyiA==}
engines: {node: '>= 16.0.0'}
@@ -7593,8 +7577,6 @@ snapshots:
buffer-from@1.1.2: {}
buffer-writer@2.0.0: {}
buffer@5.7.1:
dependencies:
base64-js: 1.5.1
@@ -9409,8 +9391,6 @@ snapshots:
package-json-from-dist@1.0.1: {}
packet-reader@1.0.0: {}
pad-left@2.1.0:
dependencies:
repeat-string: 1.6.1
@@ -9458,10 +9438,6 @@ snapshots:
pg-numeric@1.0.2: {}
pg-pool@3.14.0(pg@8.11.3):
dependencies:
pg: 8.11.3
pg-pool@3.14.0(pg@8.21.0):
dependencies:
pg: 8.21.0
@@ -9486,18 +9462,6 @@ snapshots:
postgres-interval: 3.0.0
postgres-range: 1.1.4
pg@8.11.3:
dependencies:
buffer-writer: 2.0.0
packet-reader: 1.0.0
pg-connection-string: 2.13.0
pg-pool: 3.14.0(pg@8.11.3)
pg-protocol: 1.14.0
pg-types: 2.2.0
pgpass: 1.0.5
optionalDependencies:
pg-cloudflare: 1.4.0
pg@8.21.0:
dependencies:
pg-connection-string: 2.13.0
+29 -11
View File
@@ -1,8 +1,11 @@
import type { Client as ClientType, ClientConfig } from "pg";
import pkg from "pg";
const { Client, escapeIdentifier } = pkg;
import { VectorStore } from "./base";
import { SearchFilters, VectorStoreConfig, VectorStoreResult } from "../types";
import { loadPeer } from "../utils/load_peer";
function escapeIdentifier(name: string): string {
return `"${name.replace(/"/g, '""')}"`;
}
const SAFE_IDENTIFIER_RE = /^[a-zA-Z_][a-zA-Z0-9_]{0,127}$/;
@@ -212,7 +215,7 @@ function buildClientConfig(
}
export class PGVector implements VectorStore {
private client: ClientType;
private client!: ClientType;
private collectionName: string;
private useDiskann: boolean;
private useHnsw: boolean;
@@ -234,13 +237,6 @@ export class PGVector implements VectorStore {
? ""
: validateIdentifier(config.dbname || "vector_store", "dbname");
this.config = config;
this.client = new Client(
buildClientConfig(
config,
this.useDirectConnection ? undefined : "postgres",
),
);
this.initialize().catch(console.error);
}
@@ -257,6 +253,18 @@ export class PGVector implements VectorStore {
private async _doInitialize(): Promise<void> {
try {
const pg = await loadPeer(
"pg",
"PGVector vector store",
() => import("pg"),
);
const { Client } = pg.default ?? pg;
this.client = new Client(
buildClientConfig(
this.config,
this.useDirectConnection ? undefined : "postgres",
),
);
await this.client.connect();
if (!this.useDirectConnection) {
@@ -345,6 +353,7 @@ export class PGVector implements VectorStore {
ids: string[],
payloads: Record<string, any>[],
): Promise<void> {
await this.initialize();
const values = vectors.map((vector, i) => ({
id: ids[i],
vector: `[${vector.join(",")}]`,
@@ -368,6 +377,7 @@ export class PGVector implements VectorStore {
topK: number = 5,
filters?: SearchFilters,
): Promise<VectorStoreResult[] | null> {
await this.initialize();
try {
const {
conditions,
@@ -406,6 +416,7 @@ export class PGVector implements VectorStore {
topK: number = 5,
filters?: SearchFilters,
): Promise<VectorStoreResult[]> {
await this.initialize();
const queryVector = `[${query.join(",")}]`;
const {
conditions,
@@ -435,6 +446,7 @@ export class PGVector implements VectorStore {
}
async get(vectorId: string): Promise<VectorStoreResult | null> {
await this.initialize();
const result = await this.client.query(
`SELECT id, payload FROM ${this.col()} WHERE id = $1`,
[vectorId],
@@ -453,6 +465,7 @@ export class PGVector implements VectorStore {
vector: number[],
payload: Record<string, any>,
): Promise<void> {
await this.initialize();
const vectorStr = `[${vector.join(",")}]`;
await this.client.query(
`
@@ -465,12 +478,14 @@ export class PGVector implements VectorStore {
}
async delete(vectorId: string): Promise<void> {
await this.initialize();
await this.client.query(`DELETE FROM ${this.col()} WHERE id = $1`, [
vectorId,
]);
}
async deleteCol(): Promise<void> {
await this.initialize();
await this.client.query(`DROP TABLE IF EXISTS ${this.col()}`);
}
@@ -487,6 +502,7 @@ export class PGVector implements VectorStore {
filters?: SearchFilters,
topK: number = 100,
): Promise<[VectorStoreResult[], number]> {
await this.initialize();
const {
conditions,
values: filterValues,
@@ -525,10 +541,11 @@ export class PGVector implements VectorStore {
}
async close(): Promise<void> {
await this.client.end();
await this.client?.end();
}
async getUserId(): Promise<string> {
await this.initialize();
const result = await this.client.query(
"SELECT user_id FROM memory_migrations LIMIT 1",
);
@@ -549,6 +566,7 @@ export class PGVector implements VectorStore {
}
async setUserId(userId: string): Promise<void> {
await this.initialize();
await this.client.query("DELETE FROM memory_migrations");
await this.client.query(
"INSERT INTO memory_migrations (user_id) VALUES ($1)",
@@ -0,0 +1,39 @@
jest.mock("pg", () => {
throw new Error("Cannot find module 'pg'");
});
jest.mock("natural", () => {
throw new Error("Cannot find module 'natural'");
});
describe("mem0ai/oss without pg or natural installed", () => {
beforeEach(() => {
jest.spyOn(console, "error").mockImplementation(() => {});
});
afterEach(() => {
jest.restoreAllMocks();
});
test("mem0ai/oss loads", async () => {
await expect(import("../src")).resolves.toHaveProperty("Memory");
});
test("PGVector explains how to install pg", async () => {
const { PGVector } = await import("../src/vector_stores/pgvector");
const store = new PGVector({
connectionString: "postgresql://localhost:5432/db",
embeddingModelDims: 3,
} as any);
await expect(store.initialize()).rejects.toThrow(
"The 'pg' package is required to use the PGVector vector store. Install it with: npm install pg",
);
});
test("BM25 lemmatization falls back to the built-in stemmer", async () => {
const { lemmatizeForBm25 } = await import("../src/utils/lemmatization");
expect(lemmatizeForBm25("The dogs were running")).toBe("dog runn running");
});
});