@mastra/pg 0.16.0 → 0.16.1-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/README.md +113 -7
- package/dist/index.cjs +163 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +163 -36
- package/dist/index.js.map +1 -1
- package/dist/vector/index.d.ts +10 -0
- package/dist/vector/index.d.ts.map +1 -1
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -363,7 +363,9 @@ var PgVector = class extends MastraVector {
|
|
|
363
363
|
setupSchemaPromise = null;
|
|
364
364
|
installVectorExtensionPromise = null;
|
|
365
365
|
vectorExtensionInstalled = void 0;
|
|
366
|
+
vectorExtensionSchema = null;
|
|
366
367
|
schemaSetupComplete = void 0;
|
|
368
|
+
cacheWarmupPromise = null;
|
|
367
369
|
constructor({
|
|
368
370
|
connectionString,
|
|
369
371
|
schemaName,
|
|
@@ -394,18 +396,24 @@ var PgVector = class extends MastraVector {
|
|
|
394
396
|
"vector.type": "postgres"
|
|
395
397
|
}
|
|
396
398
|
}) ?? basePool;
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
399
|
+
this.cacheWarmupPromise = (async () => {
|
|
400
|
+
try {
|
|
401
|
+
const existingIndexes = await this.listIndexes();
|
|
402
|
+
await Promise.all(
|
|
403
|
+
existingIndexes.map(async (indexName) => {
|
|
404
|
+
const info = await this.getIndexInfo({ indexName });
|
|
405
|
+
const key = await this.getIndexCacheKey({
|
|
406
|
+
indexName,
|
|
407
|
+
metric: info.metric,
|
|
408
|
+
dimension: info.dimension,
|
|
409
|
+
type: info.type
|
|
410
|
+
});
|
|
411
|
+
this.createdIndexes.set(indexName, key);
|
|
412
|
+
})
|
|
413
|
+
);
|
|
414
|
+
} catch (error) {
|
|
415
|
+
this.logger?.debug("Cache warming skipped or failed", { error });
|
|
416
|
+
}
|
|
409
417
|
})();
|
|
410
418
|
} catch (error) {
|
|
411
419
|
throw new MastraError(
|
|
@@ -425,6 +433,45 @@ var PgVector = class extends MastraVector {
|
|
|
425
433
|
if (!this.mutexesByName.has(indexName)) this.mutexesByName.set(indexName, new Mutex());
|
|
426
434
|
return this.mutexesByName.get(indexName);
|
|
427
435
|
}
|
|
436
|
+
/**
|
|
437
|
+
* Detects which schema contains the vector extension
|
|
438
|
+
*/
|
|
439
|
+
async detectVectorExtensionSchema(client) {
|
|
440
|
+
try {
|
|
441
|
+
const result = await client.query(`
|
|
442
|
+
SELECT n.nspname as schema_name
|
|
443
|
+
FROM pg_extension e
|
|
444
|
+
JOIN pg_namespace n ON e.extnamespace = n.oid
|
|
445
|
+
WHERE e.extname = 'vector'
|
|
446
|
+
LIMIT 1;
|
|
447
|
+
`);
|
|
448
|
+
if (result.rows.length > 0) {
|
|
449
|
+
this.vectorExtensionSchema = result.rows[0].schema_name;
|
|
450
|
+
this.logger.debug("Vector extension found in schema", { schema: this.vectorExtensionSchema });
|
|
451
|
+
return this.vectorExtensionSchema;
|
|
452
|
+
}
|
|
453
|
+
return null;
|
|
454
|
+
} catch (error) {
|
|
455
|
+
this.logger.debug("Could not detect vector extension schema", { error });
|
|
456
|
+
return null;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Gets the properly qualified vector type name
|
|
461
|
+
*/
|
|
462
|
+
getVectorTypeName() {
|
|
463
|
+
if (this.vectorExtensionSchema) {
|
|
464
|
+
if (this.vectorExtensionSchema === "pg_catalog") {
|
|
465
|
+
return "vector";
|
|
466
|
+
}
|
|
467
|
+
if (this.vectorExtensionSchema === (this.schema || "public")) {
|
|
468
|
+
return "vector";
|
|
469
|
+
}
|
|
470
|
+
const validatedSchema = parseSqlIdentifier(this.vectorExtensionSchema, "vector extension schema");
|
|
471
|
+
return `${validatedSchema}.vector`;
|
|
472
|
+
}
|
|
473
|
+
return "vector";
|
|
474
|
+
}
|
|
428
475
|
getTableName(indexName) {
|
|
429
476
|
const parsedIndexName = parseSqlIdentifier(indexName, "index name");
|
|
430
477
|
const quotedIndexName = `"${parsedIndexName}"`;
|
|
@@ -496,11 +543,12 @@ var PgVector = class extends MastraVector {
|
|
|
496
543
|
await client.query(`SET LOCAL ivfflat.probes = ${probes}`);
|
|
497
544
|
}
|
|
498
545
|
const { tableName } = this.getTableName(indexName);
|
|
546
|
+
const vectorType = this.getVectorTypeName();
|
|
499
547
|
const query = `
|
|
500
548
|
WITH vector_scores AS (
|
|
501
549
|
SELECT
|
|
502
550
|
vector_id as id,
|
|
503
|
-
1 - (embedding <=> '${vectorStr}'
|
|
551
|
+
1 - (embedding <=> '${vectorStr}'::${vectorType}) as score,
|
|
504
552
|
metadata
|
|
505
553
|
${includeVector ? ", embedding" : ""}
|
|
506
554
|
FROM ${tableName}
|
|
@@ -544,13 +592,14 @@ var PgVector = class extends MastraVector {
|
|
|
544
592
|
try {
|
|
545
593
|
await client.query("BEGIN");
|
|
546
594
|
const vectorIds = ids || vectors.map(() => crypto.randomUUID());
|
|
595
|
+
const vectorType = this.getVectorTypeName();
|
|
547
596
|
for (let i = 0; i < vectors.length; i++) {
|
|
548
597
|
const query = `
|
|
549
598
|
INSERT INTO ${tableName} (vector_id, embedding, metadata)
|
|
550
|
-
VALUES ($1, $2
|
|
599
|
+
VALUES ($1, $2::${vectorType}, $3::jsonb)
|
|
551
600
|
ON CONFLICT (vector_id)
|
|
552
601
|
DO UPDATE SET
|
|
553
|
-
embedding = $2
|
|
602
|
+
embedding = $2::${vectorType},
|
|
554
603
|
metadata = $3::jsonb
|
|
555
604
|
RETURNING embedding::text
|
|
556
605
|
`;
|
|
@@ -697,11 +746,15 @@ var PgVector = class extends MastraVector {
|
|
|
697
746
|
try {
|
|
698
747
|
await this.setupSchema(client);
|
|
699
748
|
await this.installVectorExtension(client);
|
|
749
|
+
if (this.schema && this.vectorExtensionSchema && this.schema !== this.vectorExtensionSchema && this.vectorExtensionSchema !== "pg_catalog") {
|
|
750
|
+
await client.query(`SET search_path TO ${this.getSchemaName()}, "${this.vectorExtensionSchema}"`);
|
|
751
|
+
}
|
|
752
|
+
const vectorType = this.getVectorTypeName();
|
|
700
753
|
await client.query(`
|
|
701
754
|
CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
702
755
|
id SERIAL PRIMARY KEY,
|
|
703
756
|
vector_id TEXT UNIQUE NOT NULL,
|
|
704
|
-
embedding
|
|
757
|
+
embedding ${vectorType}(${dimension}),
|
|
705
758
|
metadata JSONB DEFAULT '{}'::jsonb
|
|
706
759
|
);
|
|
707
760
|
`);
|
|
@@ -756,17 +809,63 @@ var PgVector = class extends MastraVector {
|
|
|
756
809
|
async setupIndex({ indexName, metric, indexConfig }, client) {
|
|
757
810
|
const mutex = this.getMutexByName(`build-${indexName}`);
|
|
758
811
|
await mutex.runExclusive(async () => {
|
|
812
|
+
const isConfigEmpty = !indexConfig || Object.keys(indexConfig).length === 0 || !indexConfig.type && !indexConfig.ivf && !indexConfig.hnsw;
|
|
813
|
+
const indexType = isConfigEmpty ? "ivfflat" : indexConfig.type || "ivfflat";
|
|
759
814
|
const { tableName, vectorIndexName } = this.getTableName(indexName);
|
|
760
|
-
|
|
815
|
+
let existingIndexInfo = null;
|
|
816
|
+
let dimension = 0;
|
|
817
|
+
try {
|
|
818
|
+
existingIndexInfo = await this.getIndexInfo({ indexName });
|
|
819
|
+
dimension = existingIndexInfo.dimension;
|
|
820
|
+
if (isConfigEmpty && existingIndexInfo.metric === metric) {
|
|
821
|
+
if (existingIndexInfo.type === "flat") {
|
|
822
|
+
this.logger?.debug(`No index exists for ${vectorIndexName}, will create default ivfflat index`);
|
|
823
|
+
} else {
|
|
824
|
+
this.logger?.debug(
|
|
825
|
+
`Index ${vectorIndexName} already exists (type: ${existingIndexInfo.type}, metric: ${existingIndexInfo.metric}), preserving existing configuration`
|
|
826
|
+
);
|
|
827
|
+
const cacheKey = await this.getIndexCacheKey({
|
|
828
|
+
indexName,
|
|
829
|
+
dimension,
|
|
830
|
+
type: existingIndexInfo.type,
|
|
831
|
+
metric: existingIndexInfo.metric
|
|
832
|
+
});
|
|
833
|
+
this.createdIndexes.set(indexName, cacheKey);
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
let configMatches = existingIndexInfo.metric === metric && existingIndexInfo.type === indexType;
|
|
838
|
+
if (indexType === "hnsw") {
|
|
839
|
+
configMatches = configMatches && existingIndexInfo.config.m === (indexConfig.hnsw?.m ?? 8) && existingIndexInfo.config.efConstruction === (indexConfig.hnsw?.efConstruction ?? 32);
|
|
840
|
+
} else if (indexType === "flat") {
|
|
841
|
+
configMatches = configMatches && existingIndexInfo.type === "flat";
|
|
842
|
+
} else if (indexType === "ivfflat" && indexConfig.ivf?.lists) {
|
|
843
|
+
configMatches = configMatches && existingIndexInfo.config.lists === indexConfig.ivf?.lists;
|
|
844
|
+
}
|
|
845
|
+
if (configMatches) {
|
|
846
|
+
this.logger?.debug(`Index ${vectorIndexName} already exists with same configuration, skipping recreation`);
|
|
847
|
+
const cacheKey = await this.getIndexCacheKey({
|
|
848
|
+
indexName,
|
|
849
|
+
dimension,
|
|
850
|
+
type: existingIndexInfo.type,
|
|
851
|
+
metric: existingIndexInfo.metric
|
|
852
|
+
});
|
|
853
|
+
this.createdIndexes.set(indexName, cacheKey);
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
this.logger?.info(`Index ${vectorIndexName} configuration changed, rebuilding index`);
|
|
761
857
|
await client.query(`DROP INDEX IF EXISTS ${vectorIndexName}`);
|
|
858
|
+
this.describeIndexCache.delete(indexName);
|
|
859
|
+
} catch {
|
|
860
|
+
this.logger?.debug(`Index ${indexName} doesn't exist yet, will create it`);
|
|
762
861
|
}
|
|
763
|
-
if (
|
|
862
|
+
if (indexType === "flat") {
|
|
764
863
|
this.describeIndexCache.delete(indexName);
|
|
765
864
|
return;
|
|
766
865
|
}
|
|
767
866
|
const metricOp = metric === "cosine" ? "vector_cosine_ops" : metric === "euclidean" ? "vector_l2_ops" : "vector_ip_ops";
|
|
768
867
|
let indexSQL;
|
|
769
|
-
if (
|
|
868
|
+
if (indexType === "hnsw") {
|
|
770
869
|
const m = indexConfig.hnsw?.m ?? 8;
|
|
771
870
|
const efConstruction = indexConfig.hnsw?.efConstruction ?? 32;
|
|
772
871
|
indexSQL = `
|
|
@@ -803,27 +902,48 @@ var PgVector = class extends MastraVector {
|
|
|
803
902
|
if (!this.installVectorExtensionPromise) {
|
|
804
903
|
this.installVectorExtensionPromise = (async () => {
|
|
805
904
|
try {
|
|
806
|
-
const
|
|
807
|
-
|
|
808
|
-
|
|
905
|
+
const existingSchema = await this.detectVectorExtensionSchema(client);
|
|
906
|
+
if (existingSchema) {
|
|
907
|
+
this.vectorExtensionInstalled = true;
|
|
908
|
+
this.vectorExtensionSchema = existingSchema;
|
|
909
|
+
this.logger.info(`Vector extension already installed in schema: ${existingSchema}`);
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
try {
|
|
913
|
+
if (this.schema && this.schema !== "public") {
|
|
914
|
+
try {
|
|
915
|
+
await client.query(`CREATE EXTENSION IF NOT EXISTS vector SCHEMA ${this.getSchemaName()}`);
|
|
916
|
+
this.vectorExtensionInstalled = true;
|
|
917
|
+
this.vectorExtensionSchema = this.schema;
|
|
918
|
+
this.logger.info(`Vector extension installed in schema: ${this.schema}`);
|
|
919
|
+
return;
|
|
920
|
+
} catch (schemaError) {
|
|
921
|
+
this.logger.debug(`Could not install vector extension in schema ${this.schema}, trying public schema`, {
|
|
922
|
+
error: schemaError
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
await client.query("CREATE EXTENSION IF NOT EXISTS vector");
|
|
927
|
+
const installedSchema = await this.detectVectorExtensionSchema(client);
|
|
928
|
+
if (installedSchema) {
|
|
929
|
+
this.vectorExtensionInstalled = true;
|
|
930
|
+
this.vectorExtensionSchema = installedSchema;
|
|
931
|
+
this.logger.info(`Vector extension installed in schema: ${installedSchema}`);
|
|
932
|
+
}
|
|
933
|
+
} catch (error) {
|
|
934
|
+
this.logger.warn(
|
|
935
|
+
"Could not install vector extension. This requires superuser privileges. If the extension is already installed, you can ignore this warning.",
|
|
936
|
+
{ error }
|
|
809
937
|
);
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
if (!this.vectorExtensionInstalled) {
|
|
813
|
-
try {
|
|
814
|
-
await client.query("CREATE EXTENSION IF NOT EXISTS vector");
|
|
938
|
+
const existingSchema2 = await this.detectVectorExtensionSchema(client);
|
|
939
|
+
if (existingSchema2) {
|
|
815
940
|
this.vectorExtensionInstalled = true;
|
|
816
|
-
this.
|
|
817
|
-
|
|
818
|
-
this.logger.warn(
|
|
819
|
-
"Could not install vector extension. This requires superuser privileges. If the extension is already installed globally, you can ignore this warning."
|
|
820
|
-
);
|
|
941
|
+
this.vectorExtensionSchema = existingSchema2;
|
|
942
|
+
this.logger.info(`Vector extension found in schema: ${existingSchema2}`);
|
|
821
943
|
}
|
|
822
|
-
} else {
|
|
823
|
-
this.logger.debug("Vector extension already installed, skipping installation");
|
|
824
944
|
}
|
|
825
945
|
} catch (error) {
|
|
826
|
-
this.logger.error("Error
|
|
946
|
+
this.logger.error("Error setting up vector extension", { error });
|
|
827
947
|
this.vectorExtensionInstalled = void 0;
|
|
828
948
|
this.installVectorExtensionPromise = null;
|
|
829
949
|
throw error;
|
|
@@ -1025,6 +1145,12 @@ var PgVector = class extends MastraVector {
|
|
|
1025
1145
|
}
|
|
1026
1146
|
}
|
|
1027
1147
|
async disconnect() {
|
|
1148
|
+
if (this.cacheWarmupPromise) {
|
|
1149
|
+
try {
|
|
1150
|
+
await this.cacheWarmupPromise;
|
|
1151
|
+
} catch {
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1028
1154
|
await this.pool.end();
|
|
1029
1155
|
}
|
|
1030
1156
|
/**
|
|
@@ -1047,8 +1173,9 @@ var PgVector = class extends MastraVector {
|
|
|
1047
1173
|
let updateParts = [];
|
|
1048
1174
|
let values = [id];
|
|
1049
1175
|
let valueIndex = 2;
|
|
1176
|
+
const vectorType = this.getVectorTypeName();
|
|
1050
1177
|
if (update.vector) {
|
|
1051
|
-
updateParts.push(`embedding = $${valueIndex}
|
|
1178
|
+
updateParts.push(`embedding = $${valueIndex}::${vectorType}`);
|
|
1052
1179
|
values.push(`[${update.vector.join(",")}]`);
|
|
1053
1180
|
valueIndex++;
|
|
1054
1181
|
}
|