@mastra/pg 1.0.0-beta.1 → 1.0.0-beta.3

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 CHANGED
@@ -1,5 +1,41 @@
1
1
  # @mastra/pg
2
2
 
3
+ ## 1.0.0-beta.3
4
+
5
+ ### Patch Changes
6
+
7
+ - fix(pg): qualify vector type to fix schema lookup ([#10786](https://github.com/mastra-ai/mastra/pull/10786))
8
+
9
+ - feat(storage): support querying messages from multiple threads ([#10663](https://github.com/mastra-ai/mastra/pull/10663))
10
+ - Fixed TypeScript errors where `threadId: string | string[]` was being passed to places expecting `Scalar` type
11
+ - Added proper multi-thread support for `listMessages` across all adapters when `threadId` is an array
12
+ - Updated `_getIncludedMessages` to look up message threadId by ID (since message IDs are globally unique)
13
+ - **upstash**: Added `msg-idx:{messageId}` index for O(1) message lookups (backwards compatible with fallback to scan for old messages, with automatic backfill)
14
+
15
+ - fix: ensure score responses match saved payloads for Mastra Stores. ([#10557](https://github.com/mastra-ai/mastra/pull/10557))
16
+
17
+ - Unify transformScoreRow functions across storage adapters ([#10648](https://github.com/mastra-ai/mastra/pull/10648))
18
+
19
+ Added a unified `transformScoreRow` function in `@mastra/core/storage` that provides schema-driven row transformation for score data. This eliminates code duplication across 10 storage adapters while maintaining store-specific behavior through configurable options:
20
+ - `preferredTimestampFields`: Preferred source fields for timestamps (PostgreSQL, Cloudflare D1)
21
+ - `convertTimestamps`: Convert timestamp strings to Date objects (MSSQL, MongoDB, ClickHouse)
22
+ - `nullValuePattern`: Skip values matching pattern (ClickHouse's `'_null_'`)
23
+ - `fieldMappings`: Map source column names to schema fields (LibSQL's `additionalLLMContext`)
24
+
25
+ Each store adapter now uses the unified function with appropriate options, reducing ~200 lines of duplicate transformation logic while ensuring consistent behavior across all storage backends.
26
+
27
+ - Updated dependencies [[`ac0d2f4`](https://github.com/mastra-ai/mastra/commit/ac0d2f4ff8831f72c1c66c2be809706d17f65789), [`1a0d3fc`](https://github.com/mastra-ai/mastra/commit/1a0d3fc811482c9c376cdf79ee615c23bae9b2d6), [`85a628b`](https://github.com/mastra-ai/mastra/commit/85a628b1224a8f64cd82ea7f033774bf22df7a7e), [`c237233`](https://github.com/mastra-ai/mastra/commit/c23723399ccedf7f5744b3f40997b79246bfbe64), [`15f9e21`](https://github.com/mastra-ai/mastra/commit/15f9e216177201ea6e3f6d0bfb063fcc0953444f), [`ff94dea`](https://github.com/mastra-ai/mastra/commit/ff94dea935f4e34545c63bcb6c29804732698809), [`5b2ff46`](https://github.com/mastra-ai/mastra/commit/5b2ff4651df70c146523a7fca773f8eb0a2272f8), [`db41688`](https://github.com/mastra-ai/mastra/commit/db4168806d007417e2e60b4f68656dca4e5f40c9), [`5ca599d`](https://github.com/mastra-ai/mastra/commit/5ca599d0bb59a1595f19f58473fcd67cc71cef58), [`bff1145`](https://github.com/mastra-ai/mastra/commit/bff114556b3cbadad9b2768488708f8ad0e91475), [`5c8ca24`](https://github.com/mastra-ai/mastra/commit/5c8ca247094e0cc2cdbd7137822fb47241f86e77), [`e191844`](https://github.com/mastra-ai/mastra/commit/e1918444ca3f80e82feef1dad506cd4ec6e2875f), [`22553f1`](https://github.com/mastra-ai/mastra/commit/22553f11c63ee5e966a9c034a349822249584691), [`7237163`](https://github.com/mastra-ai/mastra/commit/72371635dbf96a87df4b073cc48fc655afbdce3d), [`2500740`](https://github.com/mastra-ai/mastra/commit/2500740ea23da067d6e50ec71c625ab3ce275e64), [`873ecbb`](https://github.com/mastra-ai/mastra/commit/873ecbb517586aa17d2f1e99283755b3ebb2863f), [`4f9bbe5`](https://github.com/mastra-ai/mastra/commit/4f9bbe5968f42c86f4930b8193de3c3c17e5bd36), [`02e51fe`](https://github.com/mastra-ai/mastra/commit/02e51feddb3d4155cfbcc42624fd0d0970d032c0), [`8f3fa3a`](https://github.com/mastra-ai/mastra/commit/8f3fa3a652bb77da092f913ec51ae46e3a7e27dc), [`cd29ad2`](https://github.com/mastra-ai/mastra/commit/cd29ad23a255534e8191f249593849ed29160886), [`bdf4d8c`](https://github.com/mastra-ai/mastra/commit/bdf4d8cdc656d8a2c21d81834bfa3bfa70f56c16), [`854e3da`](https://github.com/mastra-ai/mastra/commit/854e3dad5daac17a91a20986399d3a51f54bf68b), [`ce18d38`](https://github.com/mastra-ai/mastra/commit/ce18d38678c65870350d123955014a8432075fd9), [`cccf9c8`](https://github.com/mastra-ai/mastra/commit/cccf9c8b2d2dfc1a5e63919395b83d78c89682a0), [`61a5705`](https://github.com/mastra-ai/mastra/commit/61a570551278b6743e64243b3ce7d73de915ca8a), [`db70a48`](https://github.com/mastra-ai/mastra/commit/db70a48aeeeeb8e5f92007e8ede52c364ce15287), [`f0fdc14`](https://github.com/mastra-ai/mastra/commit/f0fdc14ee233d619266b3d2bbdeea7d25cfc6d13), [`db18bc9`](https://github.com/mastra-ai/mastra/commit/db18bc9c3825e2c1a0ad9a183cc9935f6691bfa1), [`9b37b56`](https://github.com/mastra-ai/mastra/commit/9b37b565e1f2a76c24f728945cc740c2b09be9da), [`41a23c3`](https://github.com/mastra-ai/mastra/commit/41a23c32f9877d71810f37e24930515df2ff7a0f), [`5d171ad`](https://github.com/mastra-ai/mastra/commit/5d171ad9ef340387276b77c2bb3e83e83332d729), [`f03ae60`](https://github.com/mastra-ai/mastra/commit/f03ae60500fe350c9d828621006cdafe1975fdd8), [`d1e74a0`](https://github.com/mastra-ai/mastra/commit/d1e74a0a293866dece31022047f5dbab65a304d0), [`39e7869`](https://github.com/mastra-ai/mastra/commit/39e7869bc7d0ee391077ce291474d8a84eedccff), [`5761926`](https://github.com/mastra-ai/mastra/commit/57619260c4a2cdd598763abbacd90de594c6bc76), [`c900fdd`](https://github.com/mastra-ai/mastra/commit/c900fdd504c41348efdffb205cfe80d48c38fa33), [`604a79f`](https://github.com/mastra-ai/mastra/commit/604a79fecf276e26a54a3fe01bb94e65315d2e0e), [`887f0b4`](https://github.com/mastra-ai/mastra/commit/887f0b4746cdbd7cb7d6b17ac9f82aeb58037ea5), [`2562143`](https://github.com/mastra-ai/mastra/commit/256214336b4faa78646c9c1776612393790d8784), [`ef11a61`](https://github.com/mastra-ai/mastra/commit/ef11a61920fa0ed08a5b7ceedd192875af119749)]:
28
+ - @mastra/core@1.0.0-beta.6
29
+
30
+ ## 1.0.0-beta.2
31
+
32
+ ### Patch Changes
33
+
34
+ - Add new deleteVectors, updateVector by filter ([#10408](https://github.com/mastra-ai/mastra/pull/10408))
35
+
36
+ - Updated dependencies [[`21a15de`](https://github.com/mastra-ai/mastra/commit/21a15de369fe82aac26bb642ed7be73505475e8b), [`feb7ee4`](https://github.com/mastra-ai/mastra/commit/feb7ee4d09a75edb46c6669a3beaceec78811747), [`b0e2ea5`](https://github.com/mastra-ai/mastra/commit/b0e2ea5b52c40fae438b9e2f7baee6f0f89c5442), [`c456e01`](https://github.com/mastra-ai/mastra/commit/c456e0149e3c176afcefdbd9bb1d2c5917723725), [`ab035c2`](https://github.com/mastra-ai/mastra/commit/ab035c2ef6d8cc7bb25f06f1a38508bd9e6f126b), [`1a46a56`](https://github.com/mastra-ai/mastra/commit/1a46a566f45a3fcbadc1cf36bf86d351f264bfa3), [`3cf540b`](https://github.com/mastra-ai/mastra/commit/3cf540b9fbfea8f4fc8d3a2319a4e6c0b0cbfd52), [`1c6ce51`](https://github.com/mastra-ai/mastra/commit/1c6ce51f875915ab57fd36873623013699a2a65d), [`898a972`](https://github.com/mastra-ai/mastra/commit/898a9727d286c2510d6b702dfd367e6aaf5c6b0f), [`a97003a`](https://github.com/mastra-ai/mastra/commit/a97003aa1cf2f4022a41912324a1e77263b326b8), [`ccc141e`](https://github.com/mastra-ai/mastra/commit/ccc141ed27da0abc3a3fc28e9e5128152e8e37f4), [`fe3b897`](https://github.com/mastra-ai/mastra/commit/fe3b897c2ccbcd2b10e81b099438c7337feddf89), [`00123ba`](https://github.com/mastra-ai/mastra/commit/00123ba96dc9e5cd0b110420ebdba56d8f237b25), [`29c4309`](https://github.com/mastra-ai/mastra/commit/29c4309f818b24304c041bcb4a8f19b5f13f6b62), [`16785ce`](https://github.com/mastra-ai/mastra/commit/16785ced928f6f22638f4488cf8a125d99211799), [`de8239b`](https://github.com/mastra-ai/mastra/commit/de8239bdcb1d8c0cfa06da21f1569912a66bbc8a), [`b5e6cd7`](https://github.com/mastra-ai/mastra/commit/b5e6cd77fc8c8e64e0494c1d06cee3d84e795d1e), [`3759cb0`](https://github.com/mastra-ai/mastra/commit/3759cb064935b5f74c65ac2f52a1145f7352899d), [`651e772`](https://github.com/mastra-ai/mastra/commit/651e772eb1475fb13e126d3fcc01751297a88214), [`b61b93f`](https://github.com/mastra-ai/mastra/commit/b61b93f9e058b11dd2eec169853175d31dbdd567), [`bae33d9`](https://github.com/mastra-ai/mastra/commit/bae33d91a63fbb64d1e80519e1fc1acaed1e9013), [`c0b731f`](https://github.com/mastra-ai/mastra/commit/c0b731fb27d712dc8582e846df5c0332a6a0c5ba), [`43ca8f2`](https://github.com/mastra-ai/mastra/commit/43ca8f2c7334851cc7b4d3d2f037d8784bfbdd5f), [`2ca67cc`](https://github.com/mastra-ai/mastra/commit/2ca67cc3bb1f6a617353fdcab197d9efebe60d6f), [`9e67002`](https://github.com/mastra-ai/mastra/commit/9e67002b52c9be19936c420a489dbee9c5fd6a78), [`35edc49`](https://github.com/mastra-ai/mastra/commit/35edc49ac0556db609189641d6341e76771b81fc)]:
37
+ - @mastra/core@1.0.0-beta.5
38
+
3
39
  ## 1.0.0-beta.1
4
40
 
5
41
  ### Patch Changes
package/README.md CHANGED
@@ -327,6 +327,9 @@ The system automatically detects configuration changes and only rebuilds indexes
327
327
  - `buildIndex({indexName, metric?, indexConfig?})`: Build or rebuild vector index
328
328
  - `upsert({indexName, vectors, metadata?, ids?})`: Add or update vectors
329
329
  - `query({indexName, queryVector, topK?, filter?, includeVector?, minScore?})`: Search for similar vectors
330
+ - `updateVector({ indexName, id?, filter?, update })`: Update a single vector by ID or metadata filter
331
+ - `deleteVector({ indexName, id })`: Delete a single vector by ID
332
+ - `deleteVectors({ indexName, ids?, filter? })`: Delete multiple vectors by IDs or metadata filter
330
333
  - `listIndexes()`: List all vector-enabled tables
331
334
  - `describeIndex(indexName)`: Get table statistics and index configuration
332
335
  - `deleteIndex(indexName)`: Delete a table
package/dist/index.cjs CHANGED
@@ -152,12 +152,20 @@ var createBasicOperator = (symbol) => {
152
152
  };
153
153
  };
154
154
  var createNumericOperator = (symbol) => {
155
- return (key, paramIndex) => {
155
+ return (key, paramIndex, value) => {
156
156
  const jsonPathKey = parseJsonPathKey(key);
157
- return {
158
- sql: `(metadata#>>'{${jsonPathKey}}')::numeric ${symbol} $${paramIndex}`,
159
- needsValue: true
160
- };
157
+ const isNumeric = typeof value === "number" || typeof value === "string" && !isNaN(Number(value)) && value.trim() !== "";
158
+ if (isNumeric) {
159
+ return {
160
+ sql: `(metadata#>>'{${jsonPathKey}}')::numeric ${symbol} $${paramIndex}::numeric`,
161
+ needsValue: true
162
+ };
163
+ } else {
164
+ return {
165
+ sql: `metadata#>>'{${jsonPathKey}}' ${symbol} $${paramIndex}::text`,
166
+ needsValue: true
167
+ };
168
+ }
161
169
  };
162
170
  };
163
171
  function buildElemMatchConditions(value, paramIndex) {
@@ -338,6 +346,83 @@ var parseJsonPathKey = (key) => {
338
346
  function escapeLikePattern(str) {
339
347
  return str.replace(/([%_\\])/g, "\\$1");
340
348
  }
349
+ function buildDeleteFilterQuery(filter) {
350
+ const values = [];
351
+ function buildCondition(key, value, parentPath) {
352
+ if (["$and", "$or", "$not", "$nor"].includes(key)) {
353
+ return handleLogicalOperator(key, value);
354
+ }
355
+ if (!value || typeof value !== "object") {
356
+ values.push(value);
357
+ return `metadata#>>'{${parseJsonPathKey(key)}}' = $${values.length}`;
358
+ }
359
+ const [[operator, operatorValue] = []] = Object.entries(value);
360
+ if (operator === "$not") {
361
+ const entries = Object.entries(operatorValue);
362
+ const conditions2 = entries.map(([nestedOp, nestedValue]) => {
363
+ if (!FILTER_OPERATORS[nestedOp]) {
364
+ throw new Error(`Invalid operator in $not condition: ${nestedOp}`);
365
+ }
366
+ const operatorFn2 = FILTER_OPERATORS[nestedOp];
367
+ const operatorResult2 = operatorFn2(key, values.length + 1, nestedValue);
368
+ if (operatorResult2.needsValue) {
369
+ values.push(nestedValue);
370
+ }
371
+ return operatorResult2.sql;
372
+ }).join(" AND ");
373
+ return `NOT (${conditions2})`;
374
+ }
375
+ const operatorFn = FILTER_OPERATORS[operator];
376
+ const operatorResult = operatorFn(key, values.length + 1, operatorValue);
377
+ if (operatorResult.needsValue) {
378
+ const transformedValue = operatorResult.transformValue ? operatorResult.transformValue() : operatorValue;
379
+ if (Array.isArray(transformedValue) && operator === "$elemMatch") {
380
+ values.push(...transformedValue);
381
+ } else {
382
+ values.push(transformedValue);
383
+ }
384
+ }
385
+ return operatorResult.sql;
386
+ }
387
+ function handleLogicalOperator(key, value, parentPath) {
388
+ if (key === "$not") {
389
+ const entries = Object.entries(value);
390
+ const conditions3 = entries.map(([fieldKey, fieldValue]) => buildCondition(fieldKey, fieldValue)).join(" AND ");
391
+ return `NOT (${conditions3})`;
392
+ }
393
+ if (!value || value.length === 0) {
394
+ switch (key) {
395
+ case "$and":
396
+ case "$nor":
397
+ return "true";
398
+ // Empty $and/$nor match everything
399
+ case "$or":
400
+ return "false";
401
+ // Empty $or matches nothing
402
+ default:
403
+ return "true";
404
+ }
405
+ }
406
+ const joinOperator = key === "$or" || key === "$nor" ? "OR" : "AND";
407
+ const conditions2 = value.map((f) => {
408
+ const entries = Object.entries(f || {});
409
+ if (entries.length === 0) return "";
410
+ const [firstKey, firstValue] = entries[0] || [];
411
+ if (["$and", "$or", "$not", "$nor"].includes(firstKey)) {
412
+ return buildCondition(firstKey, firstValue);
413
+ }
414
+ return entries.map(([k, v]) => buildCondition(k, v)).join(` ${joinOperator} `);
415
+ });
416
+ const joined = conditions2.join(` ${joinOperator} `);
417
+ const operatorFn = FILTER_OPERATORS[key];
418
+ return operatorFn(joined, 0, value).sql;
419
+ }
420
+ if (!filter) {
421
+ return { sql: "", values };
422
+ }
423
+ const conditions = Object.entries(filter).map(([key, value]) => buildCondition(key, value)).filter(Boolean).join(" AND ");
424
+ return { sql: conditions ? `WHERE ${conditions}` : "", values };
425
+ }
341
426
  function buildFilterQuery(filter, minScore, topK) {
342
427
  const values = [minScore, topK];
343
428
  function buildCondition(key, value, parentPath) {
@@ -537,9 +622,6 @@ var PgVector = class extends vector.MastraVector {
537
622
  if (this.vectorExtensionSchema === "pg_catalog") {
538
623
  return "vector";
539
624
  }
540
- if (this.vectorExtensionSchema === (this.schema || "public")) {
541
- return "vector";
542
- }
543
625
  const validatedSchema = utils.parseSqlIdentifier(this.vectorExtensionSchema, "vector extension schema");
544
626
  return `${validatedSchema}.vector`;
545
627
  }
@@ -659,11 +741,31 @@ var PgVector = class extends vector.MastraVector {
659
741
  client.release();
660
742
  }
661
743
  }
662
- async upsert({ indexName, vectors, metadata, ids }) {
744
+ async upsert({
745
+ indexName,
746
+ vectors,
747
+ metadata,
748
+ ids,
749
+ deleteFilter
750
+ }) {
663
751
  const { tableName } = this.getTableName(indexName);
664
752
  const client = await this.pool.connect();
665
753
  try {
666
754
  await client.query("BEGIN");
755
+ if (deleteFilter) {
756
+ this.logger?.debug(`Deleting vectors matching filter before upsert`, { indexName, deleteFilter });
757
+ const translatedFilter = this.transformFilter(deleteFilter);
758
+ const { sql: filterQuery, values: filterValues } = buildDeleteFilterQuery(translatedFilter);
759
+ const whereClause = filterQuery.trim().replace(/^WHERE\s+/i, "");
760
+ if (whereClause) {
761
+ const deleteQuery = `DELETE FROM ${tableName} WHERE ${whereClause}`;
762
+ const result = await client.query(deleteQuery, filterValues);
763
+ this.logger?.debug(`Deleted ${result.rowCount || 0} vectors before upsert`, {
764
+ indexName,
765
+ deletedCount: result.rowCount || 0
766
+ });
767
+ }
768
+ }
667
769
  const vectorIds = ids || vectors.map(() => crypto.randomUUID());
668
770
  const vectorType = this.getVectorTypeName();
669
771
  for (let i = 0; i < vectors.length; i++) {
@@ -679,6 +781,11 @@ var PgVector = class extends vector.MastraVector {
679
781
  await client.query(query, [vectorIds[i], `[${vectors[i]?.join(",")}]`, JSON.stringify(metadata?.[i] || {})]);
680
782
  }
681
783
  await client.query("COMMIT");
784
+ this.logger?.debug(`Upserted ${vectors.length} vectors to ${indexName}`, {
785
+ indexName,
786
+ vectorCount: vectors.length,
787
+ hadDeleteFilter: !!deleteFilter
788
+ });
682
789
  return vectorIds;
683
790
  } catch (error$1) {
684
791
  await client.query("ROLLBACK");
@@ -1236,17 +1343,36 @@ var PgVector = class extends vector.MastraVector {
1236
1343
  * @returns A promise that resolves when the update is complete.
1237
1344
  * @throws Will throw an error if no updates are provided or if the update operation fails.
1238
1345
  */
1239
- async updateVector({ indexName, id, update }) {
1346
+ async updateVector({ indexName, id, filter, update }) {
1240
1347
  let client;
1241
1348
  try {
1242
1349
  if (!update.vector && !update.metadata) {
1243
1350
  throw new Error("No updates provided");
1244
1351
  }
1352
+ if (!id && !filter) {
1353
+ throw new error.MastraError({
1354
+ id: "MASTRA_STORAGE_PG_VECTOR_UPDATE_MISSING_PARAMS",
1355
+ text: "Either id or filter must be provided",
1356
+ domain: error.ErrorDomain.MASTRA_VECTOR,
1357
+ category: error.ErrorCategory.USER,
1358
+ details: { indexName }
1359
+ });
1360
+ }
1361
+ if (id && filter) {
1362
+ throw new error.MastraError({
1363
+ id: "MASTRA_STORAGE_PG_VECTOR_UPDATE_CONFLICTING_PARAMS",
1364
+ text: "Cannot provide both id and filter - they are mutually exclusive",
1365
+ domain: error.ErrorDomain.MASTRA_VECTOR,
1366
+ category: error.ErrorCategory.USER,
1367
+ details: { indexName }
1368
+ });
1369
+ }
1245
1370
  client = await this.pool.connect();
1246
- let updateParts = [];
1247
- let values = [id];
1248
- let valueIndex = 2;
1371
+ const { tableName } = this.getTableName(indexName);
1249
1372
  const vectorType = this.getVectorTypeName();
1373
+ let updateParts = [];
1374
+ let values = [];
1375
+ let valueIndex = 1;
1250
1376
  if (update.vector) {
1251
1377
  updateParts.push(`embedding = $${valueIndex}::${vectorType}`);
1252
1378
  values.push(`[${update.vector.join(",")}]`);
@@ -1255,18 +1381,60 @@ var PgVector = class extends vector.MastraVector {
1255
1381
  if (update.metadata) {
1256
1382
  updateParts.push(`metadata = $${valueIndex}::jsonb`);
1257
1383
  values.push(JSON.stringify(update.metadata));
1384
+ valueIndex++;
1258
1385
  }
1259
1386
  if (updateParts.length === 0) {
1260
1387
  return;
1261
1388
  }
1262
- const { tableName } = this.getTableName(indexName);
1389
+ let whereClause;
1390
+ let whereValues;
1391
+ if (id) {
1392
+ whereClause = `vector_id = $${valueIndex}`;
1393
+ whereValues = [id];
1394
+ } else {
1395
+ if (!filter || Object.keys(filter).length === 0) {
1396
+ throw new error.MastraError({
1397
+ id: "MASTRA_STORAGE_PG_VECTOR_UPDATE_EMPTY_FILTER",
1398
+ text: "Cannot update with empty filter",
1399
+ domain: error.ErrorDomain.MASTRA_VECTOR,
1400
+ category: error.ErrorCategory.USER,
1401
+ details: { indexName }
1402
+ });
1403
+ }
1404
+ const translatedFilter = this.transformFilter(filter);
1405
+ const { sql: filterQuery, values: filterValues } = buildDeleteFilterQuery(translatedFilter);
1406
+ whereClause = filterQuery.trim().replace(/^WHERE\s+/i, "");
1407
+ if (!whereClause) {
1408
+ throw new error.MastraError({
1409
+ id: "MASTRA_STORAGE_PG_VECTOR_UPDATE_INVALID_FILTER",
1410
+ text: "Filter produced empty WHERE clause",
1411
+ domain: error.ErrorDomain.MASTRA_VECTOR,
1412
+ category: error.ErrorCategory.USER,
1413
+ details: { indexName, filter: JSON.stringify(filter) }
1414
+ });
1415
+ }
1416
+ whereClause = whereClause.replace(/\$(\d+)/g, (match, num) => {
1417
+ const newIndex = parseInt(num) + valueIndex - 1;
1418
+ return `$${newIndex}`;
1419
+ });
1420
+ whereValues = filterValues;
1421
+ }
1263
1422
  const query = `
1264
1423
  UPDATE ${tableName}
1265
1424
  SET ${updateParts.join(", ")}
1266
- WHERE vector_id = $1
1425
+ WHERE ${whereClause}
1267
1426
  `;
1268
- await client.query(query, values);
1427
+ const result = await client.query(query, [...values, ...whereValues]);
1428
+ this.logger?.info(`Updated ${result.rowCount || 0} vectors in ${indexName}`, {
1429
+ indexName,
1430
+ id: id ? id : void 0,
1431
+ filter: filter ? filter : void 0,
1432
+ updatedCount: result.rowCount || 0
1433
+ });
1269
1434
  } catch (error$1) {
1435
+ if (error$1 instanceof error.MastraError) {
1436
+ throw error$1;
1437
+ }
1270
1438
  const mastraError = new error.MastraError(
1271
1439
  {
1272
1440
  id: "MASTRA_STORAGE_PG_VECTOR_UPDATE_VECTOR_FAILED",
@@ -1274,7 +1442,8 @@ var PgVector = class extends vector.MastraVector {
1274
1442
  category: error.ErrorCategory.THIRD_PARTY,
1275
1443
  details: {
1276
1444
  indexName,
1277
- id
1445
+ ...id && { id },
1446
+ ...filter && { filter: JSON.stringify(filter) }
1278
1447
  }
1279
1448
  },
1280
1449
  error$1
@@ -1321,6 +1490,106 @@ var PgVector = class extends vector.MastraVector {
1321
1490
  client?.release();
1322
1491
  }
1323
1492
  }
1493
+ /**
1494
+ * Delete vectors matching a metadata filter.
1495
+ * @param indexName - The name of the index containing the vectors.
1496
+ * @param filter - The filter to match vectors for deletion.
1497
+ * @returns A promise that resolves when the deletion is complete.
1498
+ * @throws Will throw an error if the deletion operation fails.
1499
+ */
1500
+ async deleteVectors({ indexName, filter, ids }) {
1501
+ let client;
1502
+ try {
1503
+ client = await this.pool.connect();
1504
+ const { tableName } = this.getTableName(indexName);
1505
+ if (!filter && !ids) {
1506
+ throw new error.MastraError({
1507
+ id: "MASTRA_STORAGE_PG_VECTOR_DELETE_MISSING_PARAMS",
1508
+ text: "Either filter or ids must be provided",
1509
+ domain: error.ErrorDomain.MASTRA_VECTOR,
1510
+ category: error.ErrorCategory.USER,
1511
+ details: { indexName }
1512
+ });
1513
+ }
1514
+ if (filter && ids) {
1515
+ throw new error.MastraError({
1516
+ id: "MASTRA_STORAGE_PG_VECTOR_DELETE_CONFLICTING_PARAMS",
1517
+ text: "Cannot provide both filter and ids - they are mutually exclusive",
1518
+ domain: error.ErrorDomain.MASTRA_VECTOR,
1519
+ category: error.ErrorCategory.USER,
1520
+ details: { indexName }
1521
+ });
1522
+ }
1523
+ let query;
1524
+ let values;
1525
+ if (ids) {
1526
+ if (ids.length === 0) {
1527
+ throw new error.MastraError({
1528
+ id: "MASTRA_STORAGE_PG_VECTOR_DELETE_EMPTY_IDS",
1529
+ text: "Cannot delete with empty ids array",
1530
+ domain: error.ErrorDomain.MASTRA_VECTOR,
1531
+ category: error.ErrorCategory.USER,
1532
+ details: { indexName }
1533
+ });
1534
+ }
1535
+ const placeholders = ids.map((_, i) => `$${i + 1}`).join(", ");
1536
+ query = `DELETE FROM ${tableName} WHERE vector_id IN (${placeholders})`;
1537
+ values = ids;
1538
+ } else {
1539
+ if (!filter || Object.keys(filter).length === 0) {
1540
+ throw new error.MastraError({
1541
+ id: "MASTRA_STORAGE_PG_VECTOR_DELETE_EMPTY_FILTER",
1542
+ text: "Cannot delete with empty filter. Use deleteIndex to delete all vectors.",
1543
+ domain: error.ErrorDomain.MASTRA_VECTOR,
1544
+ category: error.ErrorCategory.USER,
1545
+ details: { indexName }
1546
+ });
1547
+ }
1548
+ const translatedFilter = this.transformFilter(filter);
1549
+ const { sql: filterQuery, values: filterValues } = buildDeleteFilterQuery(translatedFilter);
1550
+ const whereClause = filterQuery.trim().replace(/^WHERE\s+/i, "");
1551
+ if (!whereClause) {
1552
+ throw new error.MastraError({
1553
+ id: "MASTRA_STORAGE_PG_VECTOR_DELETE_INVALID_FILTER",
1554
+ text: "Filter produced empty WHERE clause",
1555
+ domain: error.ErrorDomain.MASTRA_VECTOR,
1556
+ category: error.ErrorCategory.USER,
1557
+ details: { indexName, filter: JSON.stringify(filter) }
1558
+ });
1559
+ }
1560
+ query = `DELETE FROM ${tableName} WHERE ${whereClause}`;
1561
+ values = filterValues;
1562
+ }
1563
+ const result = await client.query(query, values);
1564
+ this.logger?.info(`Deleted ${result.rowCount || 0} vectors from ${indexName}`, {
1565
+ indexName,
1566
+ filter: filter ? filter : void 0,
1567
+ ids: ids ? ids : void 0,
1568
+ deletedCount: result.rowCount || 0
1569
+ });
1570
+ } catch (error$1) {
1571
+ if (error$1 instanceof error.MastraError) {
1572
+ throw error$1;
1573
+ }
1574
+ const mastraError = new error.MastraError(
1575
+ {
1576
+ id: "MASTRA_STORAGE_PG_VECTOR_DELETE_VECTORS_FAILED",
1577
+ domain: error.ErrorDomain.MASTRA_VECTOR,
1578
+ category: error.ErrorCategory.THIRD_PARTY,
1579
+ details: {
1580
+ indexName,
1581
+ ...filter && { filter: JSON.stringify(filter) },
1582
+ ...ids && { idsCount: ids.length }
1583
+ }
1584
+ },
1585
+ error$1
1586
+ );
1587
+ this.logger?.trackException(mastraError);
1588
+ throw mastraError;
1589
+ } finally {
1590
+ client?.release();
1591
+ }
1592
+ }
1324
1593
  };
1325
1594
  function getSchemaName(schema) {
1326
1595
  return schema ? `"${utils.parseSqlIdentifier(schema, "schema name")}"` : void 0;
@@ -1642,6 +1911,20 @@ var MemoryPG = class extends storage.MemoryStorage {
1642
1911
  const threadTableName = getTableName({ indexName: storage.TABLE_THREADS, schemaName: getSchemaName(this.schema) });
1643
1912
  await this.client.tx(async (t) => {
1644
1913
  await t.none(`DELETE FROM ${tableName} WHERE thread_id = $1`, [threadId]);
1914
+ const schemaName = this.schema || "public";
1915
+ const vectorTables = await t.manyOrNone(
1916
+ `
1917
+ SELECT tablename
1918
+ FROM pg_tables
1919
+ WHERE schemaname = $1
1920
+ AND (tablename = 'memory_messages' OR tablename LIKE 'memory_messages_%')
1921
+ `,
1922
+ [schemaName]
1923
+ );
1924
+ for (const { tablename } of vectorTables) {
1925
+ const vectorTableName = getTableName({ indexName: tablename, schemaName: getSchemaName(this.schema) });
1926
+ await t.none(`DELETE FROM ${vectorTableName} WHERE metadata->>'thread_id' = $1`, [threadId]);
1927
+ }
1645
1928
  await t.none(`DELETE FROM ${threadTableName} WHERE id = $1`, [threadId]);
1646
1929
  });
1647
1930
  } catch (error$1) {
@@ -1658,28 +1941,26 @@ var MemoryPG = class extends storage.MemoryStorage {
1658
1941
  );
1659
1942
  }
1660
1943
  }
1661
- async _getIncludedMessages({
1662
- threadId,
1663
- include
1664
- }) {
1665
- if (!threadId.trim()) throw new Error("threadId must be a non-empty string");
1666
- if (!include) return null;
1944
+ async _getIncludedMessages({ include }) {
1945
+ if (!include || include.length === 0) return null;
1667
1946
  const unionQueries = [];
1668
1947
  const params = [];
1669
1948
  let paramIdx = 1;
1670
1949
  const tableName = getTableName({ indexName: storage.TABLE_MESSAGES, schemaName: getSchemaName(this.schema) });
1671
1950
  for (const inc of include) {
1672
1951
  const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
1673
- const searchId = inc.threadId || threadId;
1674
1952
  unionQueries.push(
1675
1953
  `
1676
1954
  SELECT * FROM (
1677
- WITH ordered_messages AS (
1955
+ WITH target_thread AS (
1956
+ SELECT thread_id FROM ${tableName} WHERE id = $${paramIdx}
1957
+ ),
1958
+ ordered_messages AS (
1678
1959
  SELECT
1679
1960
  *,
1680
1961
  ROW_NUMBER() OVER (ORDER BY "createdAt" ASC) as row_num
1681
1962
  FROM ${tableName}
1682
- WHERE thread_id = $${paramIdx}
1963
+ WHERE thread_id = (SELECT thread_id FROM target_thread)
1683
1964
  )
1684
1965
  SELECT
1685
1966
  m.id,
@@ -1691,24 +1972,24 @@ var MemoryPG = class extends storage.MemoryStorage {
1691
1972
  m.thread_id AS "threadId",
1692
1973
  m."resourceId"
1693
1974
  FROM ordered_messages m
1694
- WHERE m.id = $${paramIdx + 1}
1975
+ WHERE m.id = $${paramIdx}
1695
1976
  OR EXISTS (
1696
1977
  SELECT 1 FROM ordered_messages target
1697
- WHERE target.id = $${paramIdx + 1}
1978
+ WHERE target.id = $${paramIdx}
1698
1979
  AND (
1699
1980
  -- Get previous messages (messages that come BEFORE the target)
1700
- (m.row_num < target.row_num AND m.row_num >= target.row_num - $${paramIdx + 2})
1981
+ (m.row_num < target.row_num AND m.row_num >= target.row_num - $${paramIdx + 1})
1701
1982
  OR
1702
1983
  -- Get next messages (messages that come AFTER the target)
1703
- (m.row_num > target.row_num AND m.row_num <= target.row_num + $${paramIdx + 3})
1984
+ (m.row_num > target.row_num AND m.row_num <= target.row_num + $${paramIdx + 2})
1704
1985
  )
1705
1986
  )
1706
1987
  ) AS query_${paramIdx}
1707
1988
  `
1708
1989
  // Keep ASC for final sorting after fetching context
1709
1990
  );
1710
- params.push(searchId, id, withPreviousMessages, withNextMessages);
1711
- paramIdx += 4;
1991
+ params.push(id, withPreviousMessages, withNextMessages);
1992
+ paramIdx += 3;
1712
1993
  }
1713
1994
  const finalQuery = unionQueries.join(" UNION ALL ") + ' ORDER BY "createdAt" ASC';
1714
1995
  const includedRows = await this.client.manyOrNone(finalQuery, params);
@@ -1772,15 +2053,18 @@ var MemoryPG = class extends storage.MemoryStorage {
1772
2053
  }
1773
2054
  async listMessages(args) {
1774
2055
  const { threadId, resourceId, include, filter, perPage: perPageInput, page = 0, orderBy } = args;
1775
- if (!threadId.trim()) {
2056
+ const threadIds = (Array.isArray(threadId) ? threadId : [threadId]).filter(
2057
+ (id) => typeof id === "string"
2058
+ );
2059
+ if (threadIds.length === 0 || threadIds.some((id) => !id.trim())) {
1776
2060
  throw new error.MastraError(
1777
2061
  {
1778
2062
  id: "STORAGE_PG_LIST_MESSAGES_INVALID_THREAD_ID",
1779
2063
  domain: error.ErrorDomain.STORAGE,
1780
2064
  category: error.ErrorCategory.THIRD_PARTY,
1781
- details: { threadId }
2065
+ details: { threadId: Array.isArray(threadId) ? String(threadId) : String(threadId) }
1782
2066
  },
1783
- new Error("threadId must be a non-empty string")
2067
+ new Error("threadId must be a non-empty string or array of non-empty strings")
1784
2068
  );
1785
2069
  }
1786
2070
  if (page < 0) {
@@ -1790,7 +2074,7 @@ var MemoryPG = class extends storage.MemoryStorage {
1790
2074
  category: error.ErrorCategory.USER,
1791
2075
  text: "Page number must be non-negative",
1792
2076
  details: {
1793
- threadId,
2077
+ threadId: Array.isArray(threadId) ? threadId.join(",") : threadId,
1794
2078
  page
1795
2079
  }
1796
2080
  });
@@ -1802,9 +2086,10 @@ var MemoryPG = class extends storage.MemoryStorage {
1802
2086
  const orderByStatement = `ORDER BY "${field}" ${direction}`;
1803
2087
  const selectStatement = `SELECT id, content, role, type, "createdAt", "createdAtZ", thread_id AS "threadId", "resourceId"`;
1804
2088
  const tableName = getTableName({ indexName: storage.TABLE_MESSAGES, schemaName: getSchemaName(this.schema) });
1805
- const conditions = [`thread_id = $1`];
1806
- const queryParams = [threadId];
1807
- let paramIndex = 2;
2089
+ const threadPlaceholders = threadIds.map((_, i) => `$${i + 1}`).join(", ");
2090
+ const conditions = [`thread_id IN (${threadPlaceholders})`];
2091
+ const queryParams = [...threadIds];
2092
+ let paramIndex = threadIds.length + 1;
1808
2093
  if (resourceId) {
1809
2094
  conditions.push(`"resourceId" = $${paramIndex++}`);
1810
2095
  queryParams.push(resourceId);
@@ -1836,7 +2121,7 @@ var MemoryPG = class extends storage.MemoryStorage {
1836
2121
  }
1837
2122
  const messageIds = new Set(messages.map((m) => m.id));
1838
2123
  if (include && include.length > 0) {
1839
- const includeMessages = await this._getIncludedMessages({ threadId, include });
2124
+ const includeMessages = await this._getIncludedMessages({ include });
1840
2125
  if (includeMessages) {
1841
2126
  for (const includeMsg of includeMessages) {
1842
2127
  if (!messageIds.has(includeMsg.id)) {
@@ -1863,7 +2148,10 @@ var MemoryPG = class extends storage.MemoryStorage {
1863
2148
  }
1864
2149
  return direction === "ASC" ? String(aValue).localeCompare(String(bValue)) : String(bValue).localeCompare(String(aValue));
1865
2150
  });
1866
- const returnedThreadMessageIds = new Set(finalMessages.filter((m) => m.threadId === threadId).map((m) => m.id));
2151
+ const threadIdSet = new Set(threadIds);
2152
+ const returnedThreadMessageIds = new Set(
2153
+ finalMessages.filter((m) => m.threadId && threadIdSet.has(m.threadId)).map((m) => m.id)
2154
+ );
1867
2155
  const allThreadMessagesReturned = returnedThreadMessageIds.size >= total;
1868
2156
  const hasMore = perPageInput !== false && !allThreadMessagesReturned && offset + perPage < total;
1869
2157
  return {
@@ -1880,7 +2168,7 @@ var MemoryPG = class extends storage.MemoryStorage {
1880
2168
  domain: error.ErrorDomain.STORAGE,
1881
2169
  category: error.ErrorCategory.THIRD_PARTY,
1882
2170
  details: {
1883
- threadId,
2171
+ threadId: Array.isArray(threadId) ? threadId.join(",") : threadId,
1884
2172
  resourceId: resourceId ?? ""
1885
2173
  }
1886
2174
  },
@@ -3335,20 +3623,12 @@ var StoreOperationsPG = class extends storage.StoreOperations {
3335
3623
  }
3336
3624
  };
3337
3625
  function transformScoreRow(row) {
3338
- return {
3339
- ...row,
3340
- input: storage.safelyParseJSON(row.input),
3341
- scorer: storage.safelyParseJSON(row.scorer),
3342
- preprocessStepResult: storage.safelyParseJSON(row.preprocessStepResult),
3343
- analyzeStepResult: storage.safelyParseJSON(row.analyzeStepResult),
3344
- metadata: storage.safelyParseJSON(row.metadata),
3345
- output: storage.safelyParseJSON(row.output),
3346
- additionalContext: storage.safelyParseJSON(row.additionalContext),
3347
- requestContext: storage.safelyParseJSON(row.requestContext),
3348
- entity: storage.safelyParseJSON(row.entity),
3349
- createdAt: row.createdAtZ || row.createdAt,
3350
- updatedAt: row.updatedAtZ || row.updatedAt
3351
- };
3626
+ return storage.transformScoreRow(row, {
3627
+ preferredTimestampFields: {
3628
+ createdAt: "createdAtZ",
3629
+ updatedAt: "updatedAtZ"
3630
+ }
3631
+ });
3352
3632
  }
3353
3633
  var ScoresPG = class extends storage.ScoresStorage {
3354
3634
  client;
@@ -3495,8 +3775,6 @@ var ScoresPG = class extends storage.ScoresStorage {
3495
3775
  scorer: scorer ? JSON.stringify(scorer) : null,
3496
3776
  preprocessStepResult: preprocessStepResult ? JSON.stringify(preprocessStepResult) : null,
3497
3777
  analyzeStepResult: analyzeStepResult ? JSON.stringify(analyzeStepResult) : null,
3498
- metadata: metadata ? JSON.stringify(metadata) : null,
3499
- additionalContext: additionalContext ? JSON.stringify(additionalContext) : null,
3500
3778
  requestContext: requestContext ? JSON.stringify(requestContext) : null,
3501
3779
  entity: entity ? JSON.stringify(entity) : null,
3502
3780
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),