@mastra/pg 1.23.0-alpha.6 → 1.23.0

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/dist/index.js CHANGED
@@ -560,6 +560,8 @@ var PgVector = class extends MastraVector {
560
560
  indexMetadataCache = /* @__PURE__ */ new Map();
561
561
  createdIndexes = /* @__PURE__ */ new Map();
562
562
  namespaceReadyIndexes = /* @__PURE__ */ new Set();
563
+ /** In-flight lazy namespace migrations, keyed by index name (see {@link ensureNamespaceReady}). */
564
+ namespaceReadyCache = /* @__PURE__ */ new Map();
563
565
  indexVectorTypes = /* @__PURE__ */ new Map();
564
566
  mutexesByName = /* @__PURE__ */ new Map();
565
567
  schema;
@@ -804,8 +806,10 @@ var PgVector = class extends MastraVector {
804
806
  AND attnum > 0
805
807
  AND NOT attisdropped`, [tableName])).rowCount === 0) return;
806
808
  await client.query(`ALTER TABLE ${tableName} ADD COLUMN IF NOT EXISTS namespace VARCHAR(255) NOT NULL DEFAULT '${DEFAULT_NAMESPACE}'`);
807
- const namespaceIndexName = this.getNamespaceIndexName(parsedIndexName);
808
- await client.query(`CREATE UNIQUE INDEX IF NOT EXISTS "${namespaceIndexName}" ON ${tableName} (namespace, vector_id)`);
809
+ if (!(await this.getNamespaceSchemaState(tableName, client)).composite_index) {
810
+ const namespaceIndexName = this.getNamespaceIndexName(parsedIndexName);
811
+ await client.query(`CREATE UNIQUE INDEX IF NOT EXISTS "${namespaceIndexName}" ON ${tableName} (namespace, vector_id)`);
812
+ }
809
813
  const legacyConstraints = await client.query(`SELECT c.conname
810
814
  FROM pg_constraint c
811
815
  WHERE c.conrelid = to_regclass($1)
@@ -816,21 +820,84 @@ var PgVector = class extends MastraVector {
816
820
  await client.query(`ALTER TABLE ${tableName} DROP CONSTRAINT "${parsedConstraintName}"`);
817
821
  }
818
822
  }
819
- /**
820
- * Name of the unique (namespace, vector_id) index for a table.
821
- *
822
- * The full `<index>_namespace_vector_id_idx` name is kept whenever it fits so tables that
823
- * were already migrated keep matching `IF NOT EXISTS`. Longer index names would exceed
824
- * Postgres' 63-char identifier limit, so those fall back to a truncated prefix plus a hash
825
- * of the index name to distinguish tables that
826
- * share a long prefix.
827
- */
828
823
  getNamespaceIndexName(parsedIndexName) {
829
824
  const fullName = `${parsedIndexName}_namespace_vector_id_idx`;
830
825
  if (fullName.length <= 63) return fullName;
831
826
  const suffix = `_ns_${createHash("sha256").update(parsedIndexName).digest("hex").slice(0, 32)}_idx`;
832
827
  return `${parsedIndexName.slice(0, 63 - suffix.length)}${suffix}`;
833
828
  }
829
+ async getNamespaceSchemaState(tableName, client) {
830
+ return (await client.query(`SELECT
831
+ EXISTS (SELECT 1 FROM pg_attribute WHERE attrelid = to_regclass($1)
832
+ AND attname = 'vector_id' AND attnum > 0 AND NOT attisdropped) AS vector_id,
833
+ EXISTS (SELECT 1 FROM pg_attribute WHERE attrelid = to_regclass($1)
834
+ AND attname = 'namespace' AND attnum > 0 AND NOT attisdropped) AS namespace,
835
+ EXISTS (
836
+ SELECT 1 FROM pg_index i
837
+ WHERE i.indrelid = to_regclass($1)
838
+ AND i.indisunique AND i.indisvalid AND i.indisready AND i.indimmediate
839
+ AND i.indpred IS NULL AND i.indexprs IS NULL AND i.indnkeyatts = 2
840
+ AND (SELECT array_agg(a.attname::text ORDER BY a.attname)
841
+ FROM unnest(i.indkey) WITH ORDINALITY AS k(attnum, position)
842
+ JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = k.attnum
843
+ WHERE k.position <= i.indnkeyatts) = ARRAY['namespace', 'vector_id']
844
+ ) AS composite_index,
845
+ EXISTS (SELECT 1 FROM pg_constraint WHERE conrelid = to_regclass($1)
846
+ AND contype = 'u' AND pg_get_constraintdef(oid) = 'UNIQUE (vector_id)') AS legacy_constraint`, [tableName])).rows[0];
847
+ }
848
+ async reconcileNamespace(indexName, client) {
849
+ const { tableName } = this.getTableName(indexName);
850
+ await client.query("BEGIN");
851
+ try {
852
+ await client.query("SELECT pg_advisory_xact_lock((1936876916::bigint << 32) | to_regclass($1)::oid::bigint)", [tableName]);
853
+ const state = await this.getNamespaceSchemaState(tableName, client);
854
+ if (!state.vector_id) {
855
+ await client.query("ROLLBACK");
856
+ return false;
857
+ }
858
+ if (!state.namespace || !state.composite_index || state.legacy_constraint) {
859
+ if (this.disableInit || process.env.MASTRA_DISABLE_STORAGE_INIT === "true") throw new MastraError({
860
+ id: createVectorErrorId("PG", "ENSURE_NAMESPACE", "MIGRATION_REQUIRED"),
861
+ domain: ErrorDomain.MASTRA_VECTOR,
862
+ category: ErrorCategory.USER,
863
+ text: `Vector index "${indexName}" requires namespace migration and schema changes are disabled. Call createIndex({ indexName: "${indexName}", dimension }) with init enabled, or apply the namespace migration SQL from the @mastra/pg changelog.`,
864
+ details: { indexName }
865
+ });
866
+ await this.ensureNamespaceSchema(indexName, client);
867
+ const migrated = await this.getNamespaceSchemaState(tableName, client);
868
+ if (!migrated.namespace || !migrated.composite_index || migrated.legacy_constraint) throw new MastraError({
869
+ id: createVectorErrorId("PG", "ENSURE_NAMESPACE", "MIGRATION_REQUIRED"),
870
+ domain: ErrorDomain.MASTRA_VECTOR,
871
+ category: ErrorCategory.USER,
872
+ text: `Vector index "${indexName}" requires a valid unique (namespace, vector_id) index. Resolve conflicting index names and retry the namespace migration.`,
873
+ details: { indexName }
874
+ });
875
+ }
876
+ await client.query("COMMIT");
877
+ return true;
878
+ } catch (error) {
879
+ await client.query("ROLLBACK");
880
+ throw error;
881
+ }
882
+ }
883
+ /**
884
+ * Every data path filters on `namespace`, but tables created before that column existed are
885
+ * only migrated by `createIndex()`, which callers are not required to invoke before reading.
886
+ * Run the migration lazily instead, once per index per process.
887
+ */
888
+ ensureNamespaceReady(indexName) {
889
+ if (this.namespaceReadyIndexes.has(indexName)) return Promise.resolve();
890
+ return this.memoize(this.namespaceReadyCache, indexName, () => this.getMutexByName(`create-${indexName}`).runExclusive(async () => {
891
+ if (this.namespaceReadyIndexes.has(indexName)) return;
892
+ const client = await this.pool.connect();
893
+ try {
894
+ if (await this.reconcileNamespace(indexName, client)) this.namespaceReadyIndexes.add(indexName);
895
+ else this.namespaceReadyCache.delete(indexName);
896
+ } finally {
897
+ client.release();
898
+ }
899
+ }));
900
+ }
834
901
  transformFilter(filter) {
835
902
  return new PGFilterTranslator().translate(filter);
836
903
  }
@@ -888,8 +955,10 @@ var PgVector = class extends MastraVector {
888
955
  throw mastraError;
889
956
  }
890
957
  if (queryVector === void 0) {
891
- const client = await this.pool.connect();
958
+ let client;
892
959
  try {
960
+ await this.ensureNamespaceReady(indexName);
961
+ client = await this.pool.connect();
893
962
  const { sql: filterQuery, values: filterValues } = buildDeleteFilterQuery(this.transformFilter(filter));
894
963
  const { tableName } = this.getTableName(indexName);
895
964
  const filterClause = filterQuery.trim().replace(/^WHERE\s+/i, "");
@@ -915,6 +984,10 @@ var PgVector = class extends MastraVector {
915
984
  ...includeVector && embedding && { vector: JSON.parse(embedding) }
916
985
  }));
917
986
  } catch (error) {
987
+ if (error instanceof MastraError) {
988
+ this.logger?.trackException(error);
989
+ throw error;
990
+ }
918
991
  const mastraError = new MastraError({
919
992
  id: createVectorErrorId("PG", "QUERY", "FAILED"),
920
993
  domain: ErrorDomain.MASTRA_VECTOR,
@@ -924,12 +997,14 @@ var PgVector = class extends MastraVector {
924
997
  this.logger?.trackException(mastraError);
925
998
  throw mastraError;
926
999
  } finally {
927
- client.release();
1000
+ client?.release();
928
1001
  }
929
1002
  }
930
- const indexInfo = await this.getIndexMetadata({ indexName });
931
- const client = await this.pool.connect();
1003
+ let client;
932
1004
  try {
1005
+ await this.ensureNamespaceReady(indexName);
1006
+ const indexInfo = await this.getIndexMetadata({ indexName });
1007
+ client = await this.pool.connect();
933
1008
  await this.ensureSearchPath(client);
934
1009
  await client.query("BEGIN");
935
1010
  const { sql: filterQuery, values: filterValues } = buildFilterQuery(this.transformFilter(filter), minScore, topK);
@@ -991,7 +1066,11 @@ var PgVector = class extends MastraVector {
991
1066
  ...includeVector && embedding && { vector: ops.parseEmbedding(embedding) }
992
1067
  }));
993
1068
  } catch (error) {
994
- await client.query("ROLLBACK");
1069
+ await client?.query("ROLLBACK");
1070
+ if (error instanceof MastraError) {
1071
+ this.logger?.trackException(error);
1072
+ throw error;
1073
+ }
995
1074
  const mastraError = new MastraError({
996
1075
  id: createVectorErrorId("PG", "QUERY", "FAILED"),
997
1076
  domain: ErrorDomain.MASTRA_VECTOR,
@@ -1001,15 +1080,17 @@ var PgVector = class extends MastraVector {
1001
1080
  this.logger?.trackException(mastraError);
1002
1081
  throw mastraError;
1003
1082
  } finally {
1004
- client.release();
1083
+ client?.release();
1005
1084
  }
1006
1085
  }
1007
1086
  async upsert({ indexName, vectors, metadata, ids, deleteFilter, namespace = DEFAULT_NAMESPACE }) {
1008
1087
  validateUpsertInput("PG", vectors, metadata, ids);
1009
1088
  const { tableName } = this.getTableName(indexName);
1010
- const indexInfo = await this.getIndexMetadata({ indexName });
1011
- const client = await this.pool.connect();
1089
+ let client;
1012
1090
  try {
1091
+ const indexInfo = await this.getIndexMetadata({ indexName });
1092
+ await this.ensureNamespaceReady(indexName);
1093
+ client = await this.pool.connect();
1013
1094
  await this.ensureSearchPath(client);
1014
1095
  await client.query("BEGIN");
1015
1096
  if (deleteFilter) {
@@ -1075,7 +1156,11 @@ var PgVector = class extends MastraVector {
1075
1156
  });
1076
1157
  return vectorIds;
1077
1158
  } catch (error) {
1078
- await client.query("ROLLBACK");
1159
+ await client?.query("ROLLBACK");
1160
+ if (error instanceof MastraError) {
1161
+ this.logger?.trackException(error);
1162
+ throw error;
1163
+ }
1079
1164
  if (error instanceof Error && error.message?.includes("expected") && error.message?.includes("dimensions")) {
1080
1165
  const match = error.message.match(/expected (\d+) dimensions, not (\d+)/);
1081
1166
  if (match) {
@@ -1104,7 +1189,7 @@ var PgVector = class extends MastraVector {
1104
1189
  this.logger?.trackException(mastraError);
1105
1190
  throw mastraError;
1106
1191
  } finally {
1107
- client.release();
1192
+ client?.release();
1108
1193
  }
1109
1194
  }
1110
1195
  hasher = xxhash();
@@ -1232,9 +1317,8 @@ var PgVector = class extends MastraVector {
1232
1317
  namespace VARCHAR(255) NOT NULL DEFAULT '${DEFAULT_NAMESPACE}'
1233
1318
  );
1234
1319
  `);
1235
- await this.ensureNamespaceSchema(indexName, client);
1320
+ if (await this.reconcileNamespace(indexName, client)) this.namespaceReadyIndexes.add(indexName);
1236
1321
  this.createdIndexes.set(indexName, indexCacheKey);
1237
- this.namespaceReadyIndexes.add(indexName);
1238
1322
  this.indexVectorTypes.set(indexName, vectorType);
1239
1323
  if (buildIndex) await this.setupIndex({
1240
1324
  indexName,
@@ -1246,6 +1330,7 @@ var PgVector = class extends MastraVector {
1246
1330
  } catch (error) {
1247
1331
  this.createdIndexes.delete(indexName);
1248
1332
  this.namespaceReadyIndexes.delete(indexName);
1333
+ this.namespaceReadyCache.delete(indexName);
1249
1334
  this.indexVectorTypes.delete(indexName);
1250
1335
  throw error;
1251
1336
  } finally {
@@ -1620,27 +1705,30 @@ var PgVector = class extends MastraVector {
1620
1705
  }
1621
1706
  }
1622
1707
  async deleteIndex({ indexName }) {
1623
- const client = await this.pool.connect();
1624
- try {
1625
- const { tableName } = this.getTableName(indexName);
1626
- await client.query(`DROP TABLE IF EXISTS ${tableName} CASCADE`);
1627
- this.createdIndexes.delete(indexName);
1628
- this.namespaceReadyIndexes.delete(indexName);
1629
- this.indexVectorTypes.delete(indexName);
1630
- this.invalidateIndexCaches(indexName);
1631
- } catch (error) {
1632
- await client.query("ROLLBACK");
1633
- const mastraError = new MastraError({
1634
- id: createVectorErrorId("PG", "DELETE_INDEX", "FAILED"),
1635
- domain: ErrorDomain.MASTRA_VECTOR,
1636
- category: ErrorCategory.THIRD_PARTY,
1637
- details: { indexName }
1638
- }, error);
1639
- this.logger?.trackException(mastraError);
1640
- throw mastraError;
1641
- } finally {
1642
- client.release();
1643
- }
1708
+ await this.getMutexByName(`create-${indexName}`).runExclusive(async () => {
1709
+ const client = await this.pool.connect();
1710
+ try {
1711
+ const { tableName } = this.getTableName(indexName);
1712
+ await client.query(`DROP TABLE IF EXISTS ${tableName} CASCADE`);
1713
+ this.createdIndexes.delete(indexName);
1714
+ this.namespaceReadyIndexes.delete(indexName);
1715
+ this.namespaceReadyCache.delete(indexName);
1716
+ this.indexVectorTypes.delete(indexName);
1717
+ this.invalidateIndexCaches(indexName);
1718
+ } catch (error) {
1719
+ await client.query("ROLLBACK");
1720
+ const mastraError = new MastraError({
1721
+ id: createVectorErrorId("PG", "DELETE_INDEX", "FAILED"),
1722
+ domain: ErrorDomain.MASTRA_VECTOR,
1723
+ category: ErrorCategory.THIRD_PARTY,
1724
+ details: { indexName }
1725
+ }, error);
1726
+ this.logger?.trackException(mastraError);
1727
+ throw mastraError;
1728
+ } finally {
1729
+ client.release();
1730
+ }
1731
+ });
1644
1732
  }
1645
1733
  async truncateIndex({ indexName }) {
1646
1734
  const client = await this.pool.connect();
@@ -1696,6 +1784,7 @@ var PgVector = class extends MastraVector {
1696
1784
  details: { indexName }
1697
1785
  });
1698
1786
  const indexInfo = await this.getIndexMetadata({ indexName });
1787
+ await this.ensureNamespaceReady(indexName);
1699
1788
  client = await this.pool.connect();
1700
1789
  await this.ensureSearchPath(client);
1701
1790
  const { tableName } = this.getTableName(indexName);
@@ -1789,6 +1878,7 @@ var PgVector = class extends MastraVector {
1789
1878
  async deleteVector({ indexName, id, namespace = DEFAULT_NAMESPACE }) {
1790
1879
  let client;
1791
1880
  try {
1881
+ await this.ensureNamespaceReady(indexName);
1792
1882
  client = await this.pool.connect();
1793
1883
  const { tableName } = this.getTableName(indexName);
1794
1884
  const query = `
@@ -1797,6 +1887,7 @@ var PgVector = class extends MastraVector {
1797
1887
  `;
1798
1888
  await client.query(query, [id, namespace]);
1799
1889
  } catch (error) {
1890
+ if (error instanceof MastraError) throw error;
1800
1891
  const mastraError = new MastraError({
1801
1892
  id: createVectorErrorId("PG", "DELETE_VECTOR", "FAILED"),
1802
1893
  domain: ErrorDomain.MASTRA_VECTOR,
@@ -1823,6 +1914,7 @@ var PgVector = class extends MastraVector {
1823
1914
  let client;
1824
1915
  const effectiveNamespace = namespace ?? DEFAULT_NAMESPACE;
1825
1916
  try {
1917
+ await this.ensureNamespaceReady(indexName);
1826
1918
  client = await this.pool.connect();
1827
1919
  const { tableName } = this.getTableName(indexName);
1828
1920
  if (!filter && !ids && namespace === void 0) throw new MastraError({