@mastra/pg 1.0.0-beta.0 → 1.0.0-beta.2

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
@@ -126,12 +126,20 @@ var createBasicOperator = (symbol) => {
126
126
  };
127
127
  };
128
128
  var createNumericOperator = (symbol) => {
129
- return (key, paramIndex) => {
129
+ return (key, paramIndex, value) => {
130
130
  const jsonPathKey = parseJsonPathKey(key);
131
- return {
132
- sql: `(metadata#>>'{${jsonPathKey}}')::numeric ${symbol} $${paramIndex}`,
133
- needsValue: true
134
- };
131
+ const isNumeric = typeof value === "number" || typeof value === "string" && !isNaN(Number(value)) && value.trim() !== "";
132
+ if (isNumeric) {
133
+ return {
134
+ sql: `(metadata#>>'{${jsonPathKey}}')::numeric ${symbol} $${paramIndex}::numeric`,
135
+ needsValue: true
136
+ };
137
+ } else {
138
+ return {
139
+ sql: `metadata#>>'{${jsonPathKey}}' ${symbol} $${paramIndex}::text`,
140
+ needsValue: true
141
+ };
142
+ }
135
143
  };
136
144
  };
137
145
  function buildElemMatchConditions(value, paramIndex) {
@@ -312,6 +320,83 @@ var parseJsonPathKey = (key) => {
312
320
  function escapeLikePattern(str) {
313
321
  return str.replace(/([%_\\])/g, "\\$1");
314
322
  }
323
+ function buildDeleteFilterQuery(filter) {
324
+ const values = [];
325
+ function buildCondition(key, value, parentPath) {
326
+ if (["$and", "$or", "$not", "$nor"].includes(key)) {
327
+ return handleLogicalOperator(key, value);
328
+ }
329
+ if (!value || typeof value !== "object") {
330
+ values.push(value);
331
+ return `metadata#>>'{${parseJsonPathKey(key)}}' = $${values.length}`;
332
+ }
333
+ const [[operator, operatorValue] = []] = Object.entries(value);
334
+ if (operator === "$not") {
335
+ const entries = Object.entries(operatorValue);
336
+ const conditions2 = entries.map(([nestedOp, nestedValue]) => {
337
+ if (!FILTER_OPERATORS[nestedOp]) {
338
+ throw new Error(`Invalid operator in $not condition: ${nestedOp}`);
339
+ }
340
+ const operatorFn2 = FILTER_OPERATORS[nestedOp];
341
+ const operatorResult2 = operatorFn2(key, values.length + 1, nestedValue);
342
+ if (operatorResult2.needsValue) {
343
+ values.push(nestedValue);
344
+ }
345
+ return operatorResult2.sql;
346
+ }).join(" AND ");
347
+ return `NOT (${conditions2})`;
348
+ }
349
+ const operatorFn = FILTER_OPERATORS[operator];
350
+ const operatorResult = operatorFn(key, values.length + 1, operatorValue);
351
+ if (operatorResult.needsValue) {
352
+ const transformedValue = operatorResult.transformValue ? operatorResult.transformValue() : operatorValue;
353
+ if (Array.isArray(transformedValue) && operator === "$elemMatch") {
354
+ values.push(...transformedValue);
355
+ } else {
356
+ values.push(transformedValue);
357
+ }
358
+ }
359
+ return operatorResult.sql;
360
+ }
361
+ function handleLogicalOperator(key, value, parentPath) {
362
+ if (key === "$not") {
363
+ const entries = Object.entries(value);
364
+ const conditions3 = entries.map(([fieldKey, fieldValue]) => buildCondition(fieldKey, fieldValue)).join(" AND ");
365
+ return `NOT (${conditions3})`;
366
+ }
367
+ if (!value || value.length === 0) {
368
+ switch (key) {
369
+ case "$and":
370
+ case "$nor":
371
+ return "true";
372
+ // Empty $and/$nor match everything
373
+ case "$or":
374
+ return "false";
375
+ // Empty $or matches nothing
376
+ default:
377
+ return "true";
378
+ }
379
+ }
380
+ const joinOperator = key === "$or" || key === "$nor" ? "OR" : "AND";
381
+ const conditions2 = value.map((f) => {
382
+ const entries = Object.entries(f || {});
383
+ if (entries.length === 0) return "";
384
+ const [firstKey, firstValue] = entries[0] || [];
385
+ if (["$and", "$or", "$not", "$nor"].includes(firstKey)) {
386
+ return buildCondition(firstKey, firstValue);
387
+ }
388
+ return entries.map(([k, v]) => buildCondition(k, v)).join(` ${joinOperator} `);
389
+ });
390
+ const joined = conditions2.join(` ${joinOperator} `);
391
+ const operatorFn = FILTER_OPERATORS[key];
392
+ return operatorFn(joined, 0, value).sql;
393
+ }
394
+ if (!filter) {
395
+ return { sql: "", values };
396
+ }
397
+ const conditions = Object.entries(filter).map(([key, value]) => buildCondition(key, value)).filter(Boolean).join(" AND ");
398
+ return { sql: conditions ? `WHERE ${conditions}` : "", values };
399
+ }
315
400
  function buildFilterQuery(filter, minScore, topK) {
316
401
  const values = [minScore, topK];
317
402
  function buildCondition(key, value, parentPath) {
@@ -633,11 +718,31 @@ var PgVector = class extends MastraVector {
633
718
  client.release();
634
719
  }
635
720
  }
636
- async upsert({ indexName, vectors, metadata, ids }) {
721
+ async upsert({
722
+ indexName,
723
+ vectors,
724
+ metadata,
725
+ ids,
726
+ deleteFilter
727
+ }) {
637
728
  const { tableName } = this.getTableName(indexName);
638
729
  const client = await this.pool.connect();
639
730
  try {
640
731
  await client.query("BEGIN");
732
+ if (deleteFilter) {
733
+ this.logger?.debug(`Deleting vectors matching filter before upsert`, { indexName, deleteFilter });
734
+ const translatedFilter = this.transformFilter(deleteFilter);
735
+ const { sql: filterQuery, values: filterValues } = buildDeleteFilterQuery(translatedFilter);
736
+ const whereClause = filterQuery.trim().replace(/^WHERE\s+/i, "");
737
+ if (whereClause) {
738
+ const deleteQuery = `DELETE FROM ${tableName} WHERE ${whereClause}`;
739
+ const result = await client.query(deleteQuery, filterValues);
740
+ this.logger?.debug(`Deleted ${result.rowCount || 0} vectors before upsert`, {
741
+ indexName,
742
+ deletedCount: result.rowCount || 0
743
+ });
744
+ }
745
+ }
641
746
  const vectorIds = ids || vectors.map(() => crypto.randomUUID());
642
747
  const vectorType = this.getVectorTypeName();
643
748
  for (let i = 0; i < vectors.length; i++) {
@@ -653,6 +758,11 @@ var PgVector = class extends MastraVector {
653
758
  await client.query(query, [vectorIds[i], `[${vectors[i]?.join(",")}]`, JSON.stringify(metadata?.[i] || {})]);
654
759
  }
655
760
  await client.query("COMMIT");
761
+ this.logger?.debug(`Upserted ${vectors.length} vectors to ${indexName}`, {
762
+ indexName,
763
+ vectorCount: vectors.length,
764
+ hadDeleteFilter: !!deleteFilter
765
+ });
656
766
  return vectorIds;
657
767
  } catch (error) {
658
768
  await client.query("ROLLBACK");
@@ -1210,17 +1320,36 @@ var PgVector = class extends MastraVector {
1210
1320
  * @returns A promise that resolves when the update is complete.
1211
1321
  * @throws Will throw an error if no updates are provided or if the update operation fails.
1212
1322
  */
1213
- async updateVector({ indexName, id, update }) {
1323
+ async updateVector({ indexName, id, filter, update }) {
1214
1324
  let client;
1215
1325
  try {
1216
1326
  if (!update.vector && !update.metadata) {
1217
1327
  throw new Error("No updates provided");
1218
1328
  }
1329
+ if (!id && !filter) {
1330
+ throw new MastraError({
1331
+ id: "MASTRA_STORAGE_PG_VECTOR_UPDATE_MISSING_PARAMS",
1332
+ text: "Either id or filter must be provided",
1333
+ domain: ErrorDomain.MASTRA_VECTOR,
1334
+ category: ErrorCategory.USER,
1335
+ details: { indexName }
1336
+ });
1337
+ }
1338
+ if (id && filter) {
1339
+ throw new MastraError({
1340
+ id: "MASTRA_STORAGE_PG_VECTOR_UPDATE_CONFLICTING_PARAMS",
1341
+ text: "Cannot provide both id and filter - they are mutually exclusive",
1342
+ domain: ErrorDomain.MASTRA_VECTOR,
1343
+ category: ErrorCategory.USER,
1344
+ details: { indexName }
1345
+ });
1346
+ }
1219
1347
  client = await this.pool.connect();
1220
- let updateParts = [];
1221
- let values = [id];
1222
- let valueIndex = 2;
1348
+ const { tableName } = this.getTableName(indexName);
1223
1349
  const vectorType = this.getVectorTypeName();
1350
+ let updateParts = [];
1351
+ let values = [];
1352
+ let valueIndex = 1;
1224
1353
  if (update.vector) {
1225
1354
  updateParts.push(`embedding = $${valueIndex}::${vectorType}`);
1226
1355
  values.push(`[${update.vector.join(",")}]`);
@@ -1229,18 +1358,60 @@ var PgVector = class extends MastraVector {
1229
1358
  if (update.metadata) {
1230
1359
  updateParts.push(`metadata = $${valueIndex}::jsonb`);
1231
1360
  values.push(JSON.stringify(update.metadata));
1361
+ valueIndex++;
1232
1362
  }
1233
1363
  if (updateParts.length === 0) {
1234
1364
  return;
1235
1365
  }
1236
- const { tableName } = this.getTableName(indexName);
1366
+ let whereClause;
1367
+ let whereValues;
1368
+ if (id) {
1369
+ whereClause = `vector_id = $${valueIndex}`;
1370
+ whereValues = [id];
1371
+ } else {
1372
+ if (!filter || Object.keys(filter).length === 0) {
1373
+ throw new MastraError({
1374
+ id: "MASTRA_STORAGE_PG_VECTOR_UPDATE_EMPTY_FILTER",
1375
+ text: "Cannot update with empty filter",
1376
+ domain: ErrorDomain.MASTRA_VECTOR,
1377
+ category: ErrorCategory.USER,
1378
+ details: { indexName }
1379
+ });
1380
+ }
1381
+ const translatedFilter = this.transformFilter(filter);
1382
+ const { sql: filterQuery, values: filterValues } = buildDeleteFilterQuery(translatedFilter);
1383
+ whereClause = filterQuery.trim().replace(/^WHERE\s+/i, "");
1384
+ if (!whereClause) {
1385
+ throw new MastraError({
1386
+ id: "MASTRA_STORAGE_PG_VECTOR_UPDATE_INVALID_FILTER",
1387
+ text: "Filter produced empty WHERE clause",
1388
+ domain: ErrorDomain.MASTRA_VECTOR,
1389
+ category: ErrorCategory.USER,
1390
+ details: { indexName, filter: JSON.stringify(filter) }
1391
+ });
1392
+ }
1393
+ whereClause = whereClause.replace(/\$(\d+)/g, (match, num) => {
1394
+ const newIndex = parseInt(num) + valueIndex - 1;
1395
+ return `$${newIndex}`;
1396
+ });
1397
+ whereValues = filterValues;
1398
+ }
1237
1399
  const query = `
1238
1400
  UPDATE ${tableName}
1239
1401
  SET ${updateParts.join(", ")}
1240
- WHERE vector_id = $1
1402
+ WHERE ${whereClause}
1241
1403
  `;
1242
- await client.query(query, values);
1404
+ const result = await client.query(query, [...values, ...whereValues]);
1405
+ this.logger?.info(`Updated ${result.rowCount || 0} vectors in ${indexName}`, {
1406
+ indexName,
1407
+ id: id ? id : void 0,
1408
+ filter: filter ? filter : void 0,
1409
+ updatedCount: result.rowCount || 0
1410
+ });
1243
1411
  } catch (error) {
1412
+ if (error instanceof MastraError) {
1413
+ throw error;
1414
+ }
1244
1415
  const mastraError = new MastraError(
1245
1416
  {
1246
1417
  id: "MASTRA_STORAGE_PG_VECTOR_UPDATE_VECTOR_FAILED",
@@ -1248,7 +1419,8 @@ var PgVector = class extends MastraVector {
1248
1419
  category: ErrorCategory.THIRD_PARTY,
1249
1420
  details: {
1250
1421
  indexName,
1251
- id
1422
+ ...id && { id },
1423
+ ...filter && { filter: JSON.stringify(filter) }
1252
1424
  }
1253
1425
  },
1254
1426
  error
@@ -1295,6 +1467,106 @@ var PgVector = class extends MastraVector {
1295
1467
  client?.release();
1296
1468
  }
1297
1469
  }
1470
+ /**
1471
+ * Delete vectors matching a metadata filter.
1472
+ * @param indexName - The name of the index containing the vectors.
1473
+ * @param filter - The filter to match vectors for deletion.
1474
+ * @returns A promise that resolves when the deletion is complete.
1475
+ * @throws Will throw an error if the deletion operation fails.
1476
+ */
1477
+ async deleteVectors({ indexName, filter, ids }) {
1478
+ let client;
1479
+ try {
1480
+ client = await this.pool.connect();
1481
+ const { tableName } = this.getTableName(indexName);
1482
+ if (!filter && !ids) {
1483
+ throw new MastraError({
1484
+ id: "MASTRA_STORAGE_PG_VECTOR_DELETE_MISSING_PARAMS",
1485
+ text: "Either filter or ids must be provided",
1486
+ domain: ErrorDomain.MASTRA_VECTOR,
1487
+ category: ErrorCategory.USER,
1488
+ details: { indexName }
1489
+ });
1490
+ }
1491
+ if (filter && ids) {
1492
+ throw new MastraError({
1493
+ id: "MASTRA_STORAGE_PG_VECTOR_DELETE_CONFLICTING_PARAMS",
1494
+ text: "Cannot provide both filter and ids - they are mutually exclusive",
1495
+ domain: ErrorDomain.MASTRA_VECTOR,
1496
+ category: ErrorCategory.USER,
1497
+ details: { indexName }
1498
+ });
1499
+ }
1500
+ let query;
1501
+ let values;
1502
+ if (ids) {
1503
+ if (ids.length === 0) {
1504
+ throw new MastraError({
1505
+ id: "MASTRA_STORAGE_PG_VECTOR_DELETE_EMPTY_IDS",
1506
+ text: "Cannot delete with empty ids array",
1507
+ domain: ErrorDomain.MASTRA_VECTOR,
1508
+ category: ErrorCategory.USER,
1509
+ details: { indexName }
1510
+ });
1511
+ }
1512
+ const placeholders = ids.map((_, i) => `$${i + 1}`).join(", ");
1513
+ query = `DELETE FROM ${tableName} WHERE vector_id IN (${placeholders})`;
1514
+ values = ids;
1515
+ } else {
1516
+ if (!filter || Object.keys(filter).length === 0) {
1517
+ throw new MastraError({
1518
+ id: "MASTRA_STORAGE_PG_VECTOR_DELETE_EMPTY_FILTER",
1519
+ text: "Cannot delete with empty filter. Use deleteIndex to delete all vectors.",
1520
+ domain: ErrorDomain.MASTRA_VECTOR,
1521
+ category: ErrorCategory.USER,
1522
+ details: { indexName }
1523
+ });
1524
+ }
1525
+ const translatedFilter = this.transformFilter(filter);
1526
+ const { sql: filterQuery, values: filterValues } = buildDeleteFilterQuery(translatedFilter);
1527
+ const whereClause = filterQuery.trim().replace(/^WHERE\s+/i, "");
1528
+ if (!whereClause) {
1529
+ throw new MastraError({
1530
+ id: "MASTRA_STORAGE_PG_VECTOR_DELETE_INVALID_FILTER",
1531
+ text: "Filter produced empty WHERE clause",
1532
+ domain: ErrorDomain.MASTRA_VECTOR,
1533
+ category: ErrorCategory.USER,
1534
+ details: { indexName, filter: JSON.stringify(filter) }
1535
+ });
1536
+ }
1537
+ query = `DELETE FROM ${tableName} WHERE ${whereClause}`;
1538
+ values = filterValues;
1539
+ }
1540
+ const result = await client.query(query, values);
1541
+ this.logger?.info(`Deleted ${result.rowCount || 0} vectors from ${indexName}`, {
1542
+ indexName,
1543
+ filter: filter ? filter : void 0,
1544
+ ids: ids ? ids : void 0,
1545
+ deletedCount: result.rowCount || 0
1546
+ });
1547
+ } catch (error) {
1548
+ if (error instanceof MastraError) {
1549
+ throw error;
1550
+ }
1551
+ const mastraError = new MastraError(
1552
+ {
1553
+ id: "MASTRA_STORAGE_PG_VECTOR_DELETE_VECTORS_FAILED",
1554
+ domain: ErrorDomain.MASTRA_VECTOR,
1555
+ category: ErrorCategory.THIRD_PARTY,
1556
+ details: {
1557
+ indexName,
1558
+ ...filter && { filter: JSON.stringify(filter) },
1559
+ ...ids && { idsCount: ids.length }
1560
+ }
1561
+ },
1562
+ error
1563
+ );
1564
+ this.logger?.trackException(mastraError);
1565
+ throw mastraError;
1566
+ } finally {
1567
+ client?.release();
1568
+ }
1569
+ }
1298
1570
  };
1299
1571
  function getSchemaName(schema) {
1300
1572
  return schema ? `"${parseSqlIdentifier(schema, "schema name")}"` : void 0;
@@ -3779,7 +4051,8 @@ var WorkflowsPG = class extends WorkflowsStorage {
3779
4051
  toDate,
3780
4052
  perPage,
3781
4053
  page,
3782
- resourceId
4054
+ resourceId,
4055
+ status
3783
4056
  } = {}) {
3784
4057
  try {
3785
4058
  const conditions = [];
@@ -3790,6 +4063,11 @@ var WorkflowsPG = class extends WorkflowsStorage {
3790
4063
  values.push(workflowName);
3791
4064
  paramIndex++;
3792
4065
  }
4066
+ if (status) {
4067
+ conditions.push(`snapshot::jsonb ->> 'status' = $${paramIndex}`);
4068
+ values.push(status);
4069
+ paramIndex++;
4070
+ }
3793
4071
  if (resourceId) {
3794
4072
  const hasResourceId = await this.operations.hasColumn(TABLE_WORKFLOW_SNAPSHOT, "resourceId");
3795
4073
  if (hasResourceId) {
@@ -4076,15 +4354,8 @@ var PostgresStore = class extends MastraStorage {
4076
4354
  }) {
4077
4355
  return this.stores.workflows.loadWorkflowSnapshot({ workflowName, runId });
4078
4356
  }
4079
- async listWorkflowRuns({
4080
- workflowName,
4081
- fromDate,
4082
- toDate,
4083
- perPage,
4084
- page,
4085
- resourceId
4086
- } = {}) {
4087
- return this.stores.workflows.listWorkflowRuns({ workflowName, fromDate, toDate, perPage, page, resourceId });
4357
+ async listWorkflowRuns(args = {}) {
4358
+ return this.stores.workflows.listWorkflowRuns(args);
4088
4359
  }
4089
4360
  async getWorkflowRunById({
4090
4361
  runId,