@mastra/pg 0.12.3 → 0.12.4-alpha.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.cjs CHANGED
@@ -7,9 +7,9 @@ var asyncMutex = require('async-mutex');
7
7
  var pg = require('pg');
8
8
  var xxhash = require('xxhash-wasm');
9
9
  var filter = require('@mastra/core/vector/filter');
10
- var agent = require('@mastra/core/agent');
11
10
  var storage = require('@mastra/core/storage');
12
11
  var pgPromise = require('pg-promise');
12
+ var agent = require('@mastra/core/agent');
13
13
 
14
14
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
15
15
 
@@ -490,6 +490,7 @@ var PgVector = class extends vector.MastraVector {
490
490
  }
491
491
  const client = await this.pool.connect();
492
492
  try {
493
+ await client.query("BEGIN");
493
494
  const vectorStr = `[${queryVector.join(",")}]`;
494
495
  const translatedFilter = this.transformFilter(filter);
495
496
  const { sql: filterQuery, values: filterValues } = buildFilterQuery(translatedFilter, minScore, topK);
@@ -519,6 +520,7 @@ var PgVector = class extends vector.MastraVector {
519
520
  ORDER BY score DESC
520
521
  LIMIT $2`;
521
522
  const result = await client.query(query, filterValues);
523
+ await client.query("COMMIT");
522
524
  return result.rows.map(({ id, score, metadata, embedding }) => ({
523
525
  id,
524
526
  score,
@@ -526,6 +528,7 @@ var PgVector = class extends vector.MastraVector {
526
528
  ...includeVector && embedding && { vector: JSON.parse(embedding) }
527
529
  }));
528
530
  } catch (error$1) {
531
+ await client.query("ROLLBACK");
529
532
  const mastraError = new error.MastraError(
530
533
  {
531
534
  id: "MASTRA_STORAGE_PG_VECTOR_QUERY_FAILED",
@@ -1104,77 +1107,55 @@ var PgVector = class extends vector.MastraVector {
1104
1107
  }
1105
1108
  }
1106
1109
  };
1107
- var PostgresStore = class extends storage.MastraStorage {
1108
- db;
1109
- pgp;
1110
- schema;
1111
- setupSchemaPromise = null;
1112
- schemaSetupComplete = void 0;
1113
- constructor(config) {
1110
+ function getSchemaName(schema) {
1111
+ return schema ? `"${utils.parseSqlIdentifier(schema, "schema name")}"` : void 0;
1112
+ }
1113
+ function getTableName({ indexName, schemaName }) {
1114
+ const parsedIndexName = utils.parseSqlIdentifier(indexName, "index name");
1115
+ const quotedIndexName = `"${parsedIndexName}"`;
1116
+ const quotedSchemaName = schemaName;
1117
+ return quotedSchemaName ? `${quotedSchemaName}.${quotedIndexName}` : quotedIndexName;
1118
+ }
1119
+
1120
+ // src/storage/domains/legacy-evals/index.ts
1121
+ function transformEvalRow(row) {
1122
+ let testInfoValue = null;
1123
+ if (row.test_info) {
1114
1124
  try {
1115
- if ("connectionString" in config) {
1116
- if (!config.connectionString || typeof config.connectionString !== "string" || config.connectionString.trim() === "") {
1117
- throw new Error(
1118
- "PostgresStore: connectionString must be provided and cannot be empty. Passing an empty string may cause fallback to local Postgres defaults."
1119
- );
1120
- }
1121
- } else {
1122
- const required = ["host", "database", "user", "password"];
1123
- for (const key of required) {
1124
- if (!(key in config) || typeof config[key] !== "string" || config[key].trim() === "") {
1125
- throw new Error(
1126
- `PostgresStore: ${key} must be provided and cannot be empty. Passing an empty string may cause fallback to local Postgres defaults.`
1127
- );
1128
- }
1129
- }
1130
- }
1131
- super({ name: "PostgresStore" });
1132
- this.pgp = pgPromise__default.default();
1133
- this.schema = config.schemaName;
1134
- this.db = this.pgp(
1135
- `connectionString` in config ? { connectionString: config.connectionString } : {
1136
- host: config.host,
1137
- port: config.port,
1138
- database: config.database,
1139
- user: config.user,
1140
- password: config.password,
1141
- ssl: config.ssl
1142
- }
1143
- );
1125
+ testInfoValue = typeof row.test_info === "string" ? JSON.parse(row.test_info) : row.test_info;
1144
1126
  } catch (e) {
1145
- throw new error.MastraError(
1146
- {
1147
- id: "MASTRA_STORAGE_PG_STORE_INITIALIZATION_FAILED",
1148
- domain: error.ErrorDomain.STORAGE,
1149
- category: error.ErrorCategory.USER
1150
- },
1151
- e
1152
- );
1127
+ console.warn("Failed to parse test_info:", e);
1153
1128
  }
1154
1129
  }
1155
- get supports() {
1156
- return {
1157
- selectByIncludeResourceScope: true,
1158
- resourceWorkingMemory: true
1159
- };
1160
- }
1161
- getTableName(indexName) {
1162
- const parsedIndexName = utils.parseSqlIdentifier(indexName, "index name");
1163
- const quotedIndexName = `"${parsedIndexName}"`;
1164
- const quotedSchemaName = this.getSchemaName();
1165
- return quotedSchemaName ? `${quotedSchemaName}.${quotedIndexName}` : quotedIndexName;
1166
- }
1167
- getSchemaName() {
1168
- return this.schema ? `"${utils.parseSqlIdentifier(this.schema, "schema name")}"` : void 0;
1130
+ return {
1131
+ agentName: row.agent_name,
1132
+ input: row.input,
1133
+ output: row.output,
1134
+ result: row.result,
1135
+ metricName: row.metric_name,
1136
+ instructions: row.instructions,
1137
+ testInfo: testInfoValue,
1138
+ globalRunId: row.global_run_id,
1139
+ runId: row.run_id,
1140
+ createdAt: row.created_atZ || row.created_at
1141
+ };
1142
+ }
1143
+ var LegacyEvalsPG = class extends storage.LegacyEvalsStorage {
1144
+ client;
1145
+ schema;
1146
+ constructor({ client, schema }) {
1147
+ super();
1148
+ this.client = client;
1149
+ this.schema = schema;
1169
1150
  }
1170
1151
  /** @deprecated use getEvals instead */
1171
1152
  async getEvalsByAgentName(agentName, type) {
1172
1153
  try {
1173
- const baseQuery = `SELECT * FROM ${this.getTableName(storage.TABLE_EVALS)} WHERE agent_name = $1`;
1154
+ const baseQuery = `SELECT * FROM ${getTableName({ indexName: storage.TABLE_EVALS, schemaName: getSchemaName(this.schema) })} WHERE agent_name = $1`;
1174
1155
  const typeCondition = type === "test" ? " AND test_info IS NOT NULL AND test_info->>'testPath' IS NOT NULL" : type === "live" ? " AND (test_info IS NULL OR test_info->>'testPath' IS NULL)" : "";
1175
1156
  const query = `${baseQuery}${typeCondition} ORDER BY created_at DESC`;
1176
- const rows = await this.db.manyOrNone(query, [agentName]);
1177
- return rows?.map((row) => this.transformEvalRow(row)) ?? [];
1157
+ const rows = await this.client.manyOrNone(query, [agentName]);
1158
+ return rows?.map((row) => transformEvalRow(row)) ?? [];
1178
1159
  } catch (error) {
1179
1160
  if (error instanceof Error && error.message.includes("relation") && error.message.includes("does not exist")) {
1180
1161
  return [];
@@ -1183,486 +1164,522 @@ var PostgresStore = class extends storage.MastraStorage {
1183
1164
  throw error;
1184
1165
  }
1185
1166
  }
1186
- transformEvalRow(row) {
1187
- let testInfoValue = null;
1188
- if (row.test_info) {
1189
- try {
1190
- testInfoValue = typeof row.test_info === "string" ? JSON.parse(row.test_info) : row.test_info;
1191
- } catch (e) {
1192
- console.warn("Failed to parse test_info:", e);
1193
- }
1167
+ async getEvals(options = {}) {
1168
+ const tableName = getTableName({ indexName: storage.TABLE_EVALS, schemaName: getSchemaName(this.schema) });
1169
+ const { agentName, type, page = 0, perPage = 100, dateRange } = options;
1170
+ const fromDate = dateRange?.start;
1171
+ const toDate = dateRange?.end;
1172
+ const conditions = [];
1173
+ const queryParams = [];
1174
+ let paramIndex = 1;
1175
+ if (agentName) {
1176
+ conditions.push(`agent_name = $${paramIndex++}`);
1177
+ queryParams.push(agentName);
1194
1178
  }
1195
- return {
1196
- agentName: row.agent_name,
1197
- input: row.input,
1198
- output: row.output,
1199
- result: row.result,
1200
- metricName: row.metric_name,
1201
- instructions: row.instructions,
1202
- testInfo: testInfoValue,
1203
- globalRunId: row.global_run_id,
1204
- runId: row.run_id,
1205
- createdAt: row.created_at
1206
- };
1207
- }
1208
- async batchInsert({ tableName, records }) {
1179
+ if (type === "test") {
1180
+ conditions.push(`(test_info IS NOT NULL AND test_info->>'testPath' IS NOT NULL)`);
1181
+ } else if (type === "live") {
1182
+ conditions.push(`(test_info IS NULL OR test_info->>'testPath' IS NULL)`);
1183
+ }
1184
+ if (fromDate) {
1185
+ conditions.push(`created_at >= $${paramIndex++}`);
1186
+ queryParams.push(fromDate);
1187
+ }
1188
+ if (toDate) {
1189
+ conditions.push(`created_at <= $${paramIndex++}`);
1190
+ queryParams.push(toDate);
1191
+ }
1192
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1193
+ const countQuery = `SELECT COUNT(*) FROM ${tableName} ${whereClause}`;
1209
1194
  try {
1210
- await this.db.query("BEGIN");
1211
- for (const record of records) {
1212
- await this.insert({ tableName, record });
1195
+ const countResult = await this.client.one(countQuery, queryParams);
1196
+ const total = parseInt(countResult.count, 10);
1197
+ const currentOffset = page * perPage;
1198
+ if (total === 0) {
1199
+ return {
1200
+ evals: [],
1201
+ total: 0,
1202
+ page,
1203
+ perPage,
1204
+ hasMore: false
1205
+ };
1213
1206
  }
1214
- await this.db.query("COMMIT");
1207
+ const dataQuery = `SELECT * FROM ${tableName} ${whereClause} ORDER BY created_at DESC LIMIT $${paramIndex++} OFFSET $${paramIndex++}`;
1208
+ const rows = await this.client.manyOrNone(dataQuery, [...queryParams, perPage, currentOffset]);
1209
+ return {
1210
+ evals: rows?.map((row) => transformEvalRow(row)) ?? [],
1211
+ total,
1212
+ page,
1213
+ perPage,
1214
+ hasMore: currentOffset + (rows?.length ?? 0) < total
1215
+ };
1215
1216
  } catch (error$1) {
1216
- await this.db.query("ROLLBACK");
1217
- throw new error.MastraError(
1217
+ const mastraError = new error.MastraError(
1218
1218
  {
1219
- id: "MASTRA_STORAGE_PG_STORE_BATCH_INSERT_FAILED",
1219
+ id: "MASTRA_STORAGE_PG_STORE_GET_EVALS_FAILED",
1220
1220
  domain: error.ErrorDomain.STORAGE,
1221
1221
  category: error.ErrorCategory.THIRD_PARTY,
1222
1222
  details: {
1223
- tableName,
1224
- numberOfRecords: records.length
1223
+ agentName: agentName || "all",
1224
+ type: type || "all",
1225
+ page,
1226
+ perPage
1225
1227
  }
1226
1228
  },
1227
1229
  error$1
1228
1230
  );
1231
+ this.logger?.error?.(mastraError.toString());
1232
+ this.logger?.trackException(mastraError);
1233
+ throw mastraError;
1229
1234
  }
1230
1235
  }
1231
- /**
1232
- * @deprecated use getTracesPaginated instead
1233
- */
1234
- async getTraces(args) {
1235
- if (args.fromDate || args.toDate) {
1236
- args.dateRange = {
1237
- start: args.fromDate,
1238
- end: args.toDate
1239
- };
1240
- }
1241
- const result = await this.getTracesPaginated(args);
1242
- return result.traces;
1236
+ };
1237
+ var MemoryPG = class extends storage.MemoryStorage {
1238
+ client;
1239
+ schema;
1240
+ operations;
1241
+ constructor({
1242
+ client,
1243
+ schema,
1244
+ operations
1245
+ }) {
1246
+ super();
1247
+ this.client = client;
1248
+ this.schema = schema;
1249
+ this.operations = operations;
1243
1250
  }
1244
- async getTracesPaginated(args) {
1245
- const { name, scope, page = 0, perPage: perPageInput, attributes, filters, dateRange } = args;
1246
- const fromDate = dateRange?.start;
1247
- const toDate = dateRange?.end;
1248
- const perPage = perPageInput !== void 0 ? perPageInput : 100;
1249
- const currentOffset = page * perPage;
1250
- const queryParams = [];
1251
- const conditions = [];
1252
- let paramIndex = 1;
1253
- if (name) {
1254
- conditions.push(`name LIKE $${paramIndex++}`);
1255
- queryParams.push(`${name}%`);
1256
- }
1257
- if (scope) {
1258
- conditions.push(`scope = $${paramIndex++}`);
1259
- queryParams.push(scope);
1260
- }
1261
- if (attributes) {
1262
- Object.entries(attributes).forEach(([key, value]) => {
1263
- const parsedKey = utils.parseFieldKey(key);
1264
- conditions.push(`attributes->>'${parsedKey}' = $${paramIndex++}`);
1265
- queryParams.push(value);
1266
- });
1267
- }
1268
- if (filters) {
1269
- Object.entries(filters).forEach(([key, value]) => {
1270
- const parsedKey = utils.parseFieldKey(key);
1271
- conditions.push(`"${parsedKey}" = $${paramIndex++}`);
1272
- queryParams.push(value);
1273
- });
1274
- }
1275
- if (fromDate) {
1276
- conditions.push(`"createdAt" >= $${paramIndex++}`);
1277
- queryParams.push(fromDate);
1278
- }
1279
- if (toDate) {
1280
- conditions.push(`"createdAt" <= $${paramIndex++}`);
1281
- queryParams.push(toDate);
1282
- }
1283
- const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1284
- const countQuery = `SELECT COUNT(*) FROM ${this.getTableName(storage.TABLE_TRACES)} ${whereClause}`;
1285
- let total = 0;
1251
+ async getThreadById({ threadId }) {
1286
1252
  try {
1287
- const countResult = await this.db.one(countQuery, queryParams);
1288
- total = parseInt(countResult.count, 10);
1253
+ const tableName = getTableName({ indexName: storage.TABLE_THREADS, schemaName: getSchemaName(this.schema) });
1254
+ const thread = await this.client.oneOrNone(
1255
+ `SELECT * FROM ${tableName} WHERE id = $1`,
1256
+ [threadId]
1257
+ );
1258
+ if (!thread) {
1259
+ return null;
1260
+ }
1261
+ return {
1262
+ id: thread.id,
1263
+ resourceId: thread.resourceId,
1264
+ title: thread.title,
1265
+ metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
1266
+ createdAt: thread.createdAtZ || thread.createdAt,
1267
+ updatedAt: thread.updatedAtZ || thread.updatedAt
1268
+ };
1289
1269
  } catch (error$1) {
1290
1270
  throw new error.MastraError(
1291
1271
  {
1292
- id: "MASTRA_STORAGE_PG_STORE_GET_TRACES_PAGINATED_FAILED_TO_RETRIEVE_TOTAL_COUNT",
1272
+ id: "MASTRA_STORAGE_PG_STORE_GET_THREAD_BY_ID_FAILED",
1293
1273
  domain: error.ErrorDomain.STORAGE,
1294
1274
  category: error.ErrorCategory.THIRD_PARTY,
1295
1275
  details: {
1296
- name: args.name ?? "",
1297
- scope: args.scope ?? ""
1276
+ threadId
1298
1277
  }
1299
1278
  },
1300
1279
  error$1
1301
1280
  );
1302
1281
  }
1303
- if (total === 0) {
1304
- return {
1305
- traces: [],
1306
- total: 0,
1307
- page,
1308
- perPage,
1309
- hasMore: false
1310
- };
1282
+ }
1283
+ /**
1284
+ * @deprecated use getThreadsByResourceIdPaginated instead
1285
+ */
1286
+ async getThreadsByResourceId(args) {
1287
+ const { resourceId } = args;
1288
+ try {
1289
+ const tableName = getTableName({ indexName: storage.TABLE_THREADS, schemaName: getSchemaName(this.schema) });
1290
+ const baseQuery = `FROM ${tableName} WHERE "resourceId" = $1`;
1291
+ const queryParams = [resourceId];
1292
+ const dataQuery = `SELECT id, "resourceId", title, metadata, "createdAt", "updatedAt" ${baseQuery} ORDER BY "createdAt" DESC`;
1293
+ const rows = await this.client.manyOrNone(dataQuery, queryParams);
1294
+ return (rows || []).map((thread) => ({
1295
+ ...thread,
1296
+ metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
1297
+ createdAt: thread.createdAt,
1298
+ updatedAt: thread.updatedAt
1299
+ }));
1300
+ } catch (error) {
1301
+ this.logger.error(`Error getting threads for resource ${resourceId}:`, error);
1302
+ return [];
1311
1303
  }
1312
- const dataQuery = `SELECT * FROM ${this.getTableName(
1313
- storage.TABLE_TRACES
1314
- )} ${whereClause} ORDER BY "createdAt" DESC LIMIT $${paramIndex++} OFFSET $${paramIndex++}`;
1315
- const finalQueryParams = [...queryParams, perPage, currentOffset];
1304
+ }
1305
+ async getThreadsByResourceIdPaginated(args) {
1306
+ const { resourceId, page = 0, perPage: perPageInput } = args;
1316
1307
  try {
1317
- const rows = await this.db.manyOrNone(dataQuery, finalQueryParams);
1318
- const traces = rows.map((row) => ({
1319
- id: row.id,
1320
- parentSpanId: row.parentSpanId,
1321
- traceId: row.traceId,
1322
- name: row.name,
1323
- scope: row.scope,
1324
- kind: row.kind,
1325
- status: row.status,
1326
- events: row.events,
1327
- links: row.links,
1328
- attributes: row.attributes,
1329
- startTime: row.startTime,
1330
- endTime: row.endTime,
1331
- other: row.other,
1332
- createdAt: row.createdAt
1308
+ const tableName = getTableName({ indexName: storage.TABLE_THREADS, schemaName: getSchemaName(this.schema) });
1309
+ const baseQuery = `FROM ${tableName} WHERE "resourceId" = $1`;
1310
+ const queryParams = [resourceId];
1311
+ const perPage = perPageInput !== void 0 ? perPageInput : 100;
1312
+ const currentOffset = page * perPage;
1313
+ const countQuery = `SELECT COUNT(*) ${baseQuery}`;
1314
+ const countResult = await this.client.one(countQuery, queryParams);
1315
+ const total = parseInt(countResult.count, 10);
1316
+ if (total === 0) {
1317
+ return {
1318
+ threads: [],
1319
+ total: 0,
1320
+ page,
1321
+ perPage,
1322
+ hasMore: false
1323
+ };
1324
+ }
1325
+ const dataQuery = `SELECT id, "resourceId", title, metadata, "createdAt", "updatedAt" ${baseQuery} ORDER BY "createdAt" DESC LIMIT $2 OFFSET $3`;
1326
+ const rows = await this.client.manyOrNone(dataQuery, [...queryParams, perPage, currentOffset]);
1327
+ const threads = (rows || []).map((thread) => ({
1328
+ ...thread,
1329
+ metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
1330
+ createdAt: thread.createdAt,
1331
+ // Assuming already Date objects or ISO strings
1332
+ updatedAt: thread.updatedAt
1333
1333
  }));
1334
1334
  return {
1335
- traces,
1335
+ threads,
1336
1336
  total,
1337
1337
  page,
1338
1338
  perPage,
1339
- hasMore: currentOffset + traces.length < total
1339
+ hasMore: currentOffset + threads.length < total
1340
1340
  };
1341
1341
  } catch (error$1) {
1342
- throw new error.MastraError(
1342
+ const mastraError = new error.MastraError(
1343
1343
  {
1344
- id: "MASTRA_STORAGE_PG_STORE_GET_TRACES_PAGINATED_FAILED_TO_RETRIEVE_TRACES",
1344
+ id: "MASTRA_STORAGE_PG_STORE_GET_THREADS_BY_RESOURCE_ID_PAGINATED_FAILED",
1345
1345
  domain: error.ErrorDomain.STORAGE,
1346
1346
  category: error.ErrorCategory.THIRD_PARTY,
1347
1347
  details: {
1348
- name: args.name ?? "",
1349
- scope: args.scope ?? ""
1348
+ resourceId,
1349
+ page
1350
1350
  }
1351
1351
  },
1352
1352
  error$1
1353
1353
  );
1354
+ this.logger?.error?.(mastraError.toString());
1355
+ this.logger?.trackException(mastraError);
1356
+ return { threads: [], total: 0, page, perPage: perPageInput || 100, hasMore: false };
1354
1357
  }
1355
1358
  }
1356
- async setupSchema() {
1357
- if (!this.schema || this.schemaSetupComplete) {
1358
- return;
1359
- }
1360
- if (!this.setupSchemaPromise) {
1361
- this.setupSchemaPromise = (async () => {
1362
- try {
1363
- const schemaExists = await this.db.oneOrNone(
1364
- `
1365
- SELECT EXISTS (
1366
- SELECT 1 FROM information_schema.schemata
1367
- WHERE schema_name = $1
1368
- )
1369
- `,
1370
- [this.schema]
1371
- );
1372
- if (!schemaExists?.exists) {
1373
- try {
1374
- await this.db.none(`CREATE SCHEMA IF NOT EXISTS ${this.getSchemaName()}`);
1375
- this.logger.info(`Schema "${this.schema}" created successfully`);
1376
- } catch (error) {
1377
- this.logger.error(`Failed to create schema "${this.schema}"`, { error });
1378
- throw new Error(
1379
- `Unable to create schema "${this.schema}". This requires CREATE privilege on the database. Either create the schema manually or grant CREATE privilege to the user.`
1380
- );
1381
- }
1382
- }
1383
- this.schemaSetupComplete = true;
1384
- this.logger.debug(`Schema "${this.schema}" is ready for use`);
1385
- } catch (error) {
1386
- this.schemaSetupComplete = void 0;
1387
- this.setupSchemaPromise = null;
1388
- throw error;
1389
- } finally {
1390
- this.setupSchemaPromise = null;
1391
- }
1392
- })();
1393
- }
1394
- await this.setupSchemaPromise;
1395
- }
1396
- async createTable({
1397
- tableName,
1398
- schema
1399
- }) {
1359
+ async saveThread({ thread }) {
1400
1360
  try {
1401
- const columns = Object.entries(schema).map(([name, def]) => {
1402
- const parsedName = utils.parseSqlIdentifier(name, "column name");
1403
- const constraints = [];
1404
- if (def.primaryKey) constraints.push("PRIMARY KEY");
1405
- if (!def.nullable) constraints.push("NOT NULL");
1406
- return `"${parsedName}" ${def.type.toUpperCase()} ${constraints.join(" ")}`;
1407
- }).join(",\n");
1408
- if (this.schema) {
1409
- await this.setupSchema();
1410
- }
1411
- const sql = `
1412
- CREATE TABLE IF NOT EXISTS ${this.getTableName(tableName)} (
1413
- ${columns}
1414
- );
1415
- ${tableName === storage.TABLE_WORKFLOW_SNAPSHOT ? `
1416
- DO $$ BEGIN
1417
- IF NOT EXISTS (
1418
- SELECT 1 FROM pg_constraint WHERE conname = 'mastra_workflow_snapshot_workflow_name_run_id_key'
1419
- ) THEN
1420
- ALTER TABLE ${this.getTableName(tableName)}
1421
- ADD CONSTRAINT mastra_workflow_snapshot_workflow_name_run_id_key
1422
- UNIQUE (workflow_name, run_id);
1423
- END IF;
1424
- END $$;
1425
- ` : ""}
1426
- `;
1427
- await this.db.none(sql);
1361
+ const tableName = getTableName({ indexName: storage.TABLE_THREADS, schemaName: getSchemaName(this.schema) });
1362
+ await this.client.none(
1363
+ `INSERT INTO ${tableName} (
1364
+ id,
1365
+ "resourceId",
1366
+ title,
1367
+ metadata,
1368
+ "createdAt",
1369
+ "createdAtZ",
1370
+ "updatedAt",
1371
+ "updatedAtZ"
1372
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
1373
+ ON CONFLICT (id) DO UPDATE SET
1374
+ "resourceId" = EXCLUDED."resourceId",
1375
+ title = EXCLUDED.title,
1376
+ metadata = EXCLUDED.metadata,
1377
+ "createdAt" = EXCLUDED."createdAt",
1378
+ "createdAtZ" = EXCLUDED."createdAtZ",
1379
+ "updatedAt" = EXCLUDED."updatedAt",
1380
+ "updatedAtZ" = EXCLUDED."updatedAtZ"`,
1381
+ [
1382
+ thread.id,
1383
+ thread.resourceId,
1384
+ thread.title,
1385
+ thread.metadata ? JSON.stringify(thread.metadata) : null,
1386
+ thread.createdAt,
1387
+ thread.createdAt,
1388
+ thread.updatedAt,
1389
+ thread.updatedAt
1390
+ ]
1391
+ );
1392
+ return thread;
1428
1393
  } catch (error$1) {
1429
1394
  throw new error.MastraError(
1430
1395
  {
1431
- id: "MASTRA_STORAGE_PG_STORE_CREATE_TABLE_FAILED",
1396
+ id: "MASTRA_STORAGE_PG_STORE_SAVE_THREAD_FAILED",
1432
1397
  domain: error.ErrorDomain.STORAGE,
1433
1398
  category: error.ErrorCategory.THIRD_PARTY,
1434
1399
  details: {
1435
- tableName
1400
+ threadId: thread.id
1436
1401
  }
1437
1402
  },
1438
1403
  error$1
1439
1404
  );
1440
1405
  }
1441
1406
  }
1442
- getDefaultValue(type) {
1443
- switch (type) {
1444
- case "timestamp":
1445
- return "DEFAULT NOW()";
1446
- case "jsonb":
1447
- return "DEFAULT '{}'::jsonb";
1448
- default:
1449
- return super.getDefaultValue(type);
1450
- }
1451
- }
1452
- /**
1453
- * Alters table schema to add columns if they don't exist
1454
- * @param tableName Name of the table
1455
- * @param schema Schema of the table
1456
- * @param ifNotExists Array of column names to add if they don't exist
1457
- */
1458
- async alterTable({
1459
- tableName,
1460
- schema,
1461
- ifNotExists
1407
+ async updateThread({
1408
+ id,
1409
+ title,
1410
+ metadata
1462
1411
  }) {
1463
- const fullTableName = this.getTableName(tableName);
1464
- try {
1465
- for (const columnName of ifNotExists) {
1466
- if (schema[columnName]) {
1467
- const columnDef = schema[columnName];
1468
- const sqlType = this.getSqlType(columnDef.type);
1469
- const nullable = columnDef.nullable === false ? "NOT NULL" : "";
1470
- const defaultValue = columnDef.nullable === false ? this.getDefaultValue(columnDef.type) : "";
1471
- const parsedColumnName = utils.parseSqlIdentifier(columnName, "column name");
1472
- const alterSql = `ALTER TABLE ${fullTableName} ADD COLUMN IF NOT EXISTS "${parsedColumnName}" ${sqlType} ${nullable} ${defaultValue}`.trim();
1473
- await this.db.none(alterSql);
1474
- this.logger?.debug?.(`Ensured column ${parsedColumnName} exists in table ${fullTableName}`);
1412
+ const threadTableName = getTableName({ indexName: storage.TABLE_THREADS, schemaName: getSchemaName(this.schema) });
1413
+ const existingThread = await this.getThreadById({ threadId: id });
1414
+ if (!existingThread) {
1415
+ throw new error.MastraError({
1416
+ id: "MASTRA_STORAGE_PG_STORE_UPDATE_THREAD_FAILED",
1417
+ domain: error.ErrorDomain.STORAGE,
1418
+ category: error.ErrorCategory.USER,
1419
+ text: `Thread ${id} not found`,
1420
+ details: {
1421
+ threadId: id,
1422
+ title
1475
1423
  }
1476
- }
1424
+ });
1425
+ }
1426
+ const mergedMetadata = {
1427
+ ...existingThread.metadata,
1428
+ ...metadata
1429
+ };
1430
+ try {
1431
+ const thread = await this.client.one(
1432
+ `UPDATE ${threadTableName}
1433
+ SET
1434
+ title = $1,
1435
+ metadata = $2,
1436
+ "updatedAt" = $3,
1437
+ "updatedAtZ" = $3
1438
+ WHERE id = $4
1439
+ RETURNING *
1440
+ `,
1441
+ [title, mergedMetadata, (/* @__PURE__ */ new Date()).toISOString(), id]
1442
+ );
1443
+ return {
1444
+ id: thread.id,
1445
+ resourceId: thread.resourceId,
1446
+ title: thread.title,
1447
+ metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
1448
+ createdAt: thread.createdAtZ || thread.createdAt,
1449
+ updatedAt: thread.updatedAtZ || thread.updatedAt
1450
+ };
1477
1451
  } catch (error$1) {
1478
1452
  throw new error.MastraError(
1479
1453
  {
1480
- id: "MASTRA_STORAGE_PG_STORE_ALTER_TABLE_FAILED",
1454
+ id: "MASTRA_STORAGE_PG_STORE_UPDATE_THREAD_FAILED",
1481
1455
  domain: error.ErrorDomain.STORAGE,
1482
1456
  category: error.ErrorCategory.THIRD_PARTY,
1483
1457
  details: {
1484
- tableName
1458
+ threadId: id,
1459
+ title
1485
1460
  }
1486
1461
  },
1487
1462
  error$1
1488
1463
  );
1489
1464
  }
1490
1465
  }
1491
- async clearTable({ tableName }) {
1466
+ async deleteThread({ threadId }) {
1492
1467
  try {
1493
- await this.db.none(`TRUNCATE TABLE ${this.getTableName(tableName)} CASCADE`);
1468
+ const tableName = getTableName({ indexName: storage.TABLE_MESSAGES, schemaName: getSchemaName(this.schema) });
1469
+ const threadTableName = getTableName({ indexName: storage.TABLE_THREADS, schemaName: getSchemaName(this.schema) });
1470
+ await this.client.tx(async (t) => {
1471
+ await t.none(`DELETE FROM ${tableName} WHERE thread_id = $1`, [threadId]);
1472
+ await t.none(`DELETE FROM ${threadTableName} WHERE id = $1`, [threadId]);
1473
+ });
1494
1474
  } catch (error$1) {
1495
1475
  throw new error.MastraError(
1496
1476
  {
1497
- id: "MASTRA_STORAGE_PG_STORE_CLEAR_TABLE_FAILED",
1477
+ id: "MASTRA_STORAGE_PG_STORE_DELETE_THREAD_FAILED",
1498
1478
  domain: error.ErrorDomain.STORAGE,
1499
1479
  category: error.ErrorCategory.THIRD_PARTY,
1500
1480
  details: {
1501
- tableName
1481
+ threadId
1502
1482
  }
1503
1483
  },
1504
1484
  error$1
1505
1485
  );
1506
1486
  }
1507
1487
  }
1508
- async insert({ tableName, record }) {
1488
+ async _getIncludedMessages({
1489
+ threadId,
1490
+ selectBy,
1491
+ orderByStatement
1492
+ }) {
1493
+ const include = selectBy?.include;
1494
+ if (!include) return null;
1495
+ const unionQueries = [];
1496
+ const params = [];
1497
+ let paramIdx = 1;
1498
+ const tableName = getTableName({ indexName: storage.TABLE_MESSAGES, schemaName: getSchemaName(this.schema) });
1499
+ for (const inc of include) {
1500
+ const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
1501
+ const searchId = inc.threadId || threadId;
1502
+ unionQueries.push(
1503
+ `
1504
+ SELECT * FROM (
1505
+ WITH ordered_messages AS (
1506
+ SELECT
1507
+ *,
1508
+ ROW_NUMBER() OVER (${orderByStatement}) as row_num
1509
+ FROM ${tableName}
1510
+ WHERE thread_id = $${paramIdx}
1511
+ )
1512
+ SELECT
1513
+ m.id,
1514
+ m.content,
1515
+ m.role,
1516
+ m.type,
1517
+ m."createdAt",
1518
+ m.thread_id AS "threadId",
1519
+ m."resourceId"
1520
+ FROM ordered_messages m
1521
+ WHERE m.id = $${paramIdx + 1}
1522
+ OR EXISTS (
1523
+ SELECT 1 FROM ordered_messages target
1524
+ WHERE target.id = $${paramIdx + 1}
1525
+ AND (
1526
+ -- Get previous messages based on the max withPreviousMessages
1527
+ (m.row_num <= target.row_num + $${paramIdx + 2} AND m.row_num > target.row_num)
1528
+ OR
1529
+ -- Get next messages based on the max withNextMessages
1530
+ (m.row_num >= target.row_num - $${paramIdx + 3} AND m.row_num < target.row_num)
1531
+ )
1532
+ )
1533
+ ) AS query_${paramIdx}
1534
+ `
1535
+ // Keep ASC for final sorting after fetching context
1536
+ );
1537
+ params.push(searchId, id, withPreviousMessages, withNextMessages);
1538
+ paramIdx += 4;
1539
+ }
1540
+ const finalQuery = unionQueries.join(" UNION ALL ") + ' ORDER BY "createdAt" ASC';
1541
+ const includedRows = await this.client.manyOrNone(finalQuery, params);
1542
+ const seen = /* @__PURE__ */ new Set();
1543
+ const dedupedRows = includedRows.filter((row) => {
1544
+ if (seen.has(row.id)) return false;
1545
+ seen.add(row.id);
1546
+ return true;
1547
+ });
1548
+ return dedupedRows;
1549
+ }
1550
+ async getMessages(args) {
1551
+ const { threadId, format, selectBy } = args;
1552
+ const selectStatement = `SELECT id, content, role, type, "createdAt", thread_id AS "threadId", "resourceId"`;
1553
+ const orderByStatement = `ORDER BY "createdAt" DESC`;
1554
+ const limit = storage.resolveMessageLimit({ last: selectBy?.last, defaultLimit: 40 });
1509
1555
  try {
1510
- const columns = Object.keys(record).map((col) => utils.parseSqlIdentifier(col, "column name"));
1511
- const values = Object.values(record);
1512
- const placeholders = values.map((_, i) => `$${i + 1}`).join(", ");
1513
- await this.db.none(
1514
- `INSERT INTO ${this.getTableName(tableName)} (${columns.map((c) => `"${c}"`).join(", ")}) VALUES (${placeholders})`,
1515
- values
1556
+ let rows = [];
1557
+ const include = selectBy?.include || [];
1558
+ if (include?.length) {
1559
+ const includeMessages = await this._getIncludedMessages({ threadId, selectBy, orderByStatement });
1560
+ if (includeMessages) {
1561
+ rows.push(...includeMessages);
1562
+ }
1563
+ }
1564
+ const excludeIds = rows.map((m) => m.id);
1565
+ const tableName = getTableName({ indexName: storage.TABLE_MESSAGES, schemaName: getSchemaName(this.schema) });
1566
+ const excludeIdsParam = excludeIds.map((_, idx) => `$${idx + 2}`).join(", ");
1567
+ let query = `${selectStatement} FROM ${tableName} WHERE thread_id = $1
1568
+ ${excludeIds.length ? `AND id NOT IN (${excludeIdsParam})` : ""}
1569
+ ${orderByStatement}
1570
+ LIMIT $${excludeIds.length + 2}
1571
+ `;
1572
+ const queryParams = [threadId, ...excludeIds, limit];
1573
+ const remainingRows = await this.client.manyOrNone(query, queryParams);
1574
+ rows.push(...remainingRows);
1575
+ const fetchedMessages = (rows || []).map((message) => {
1576
+ if (typeof message.content === "string") {
1577
+ try {
1578
+ message.content = JSON.parse(message.content);
1579
+ } catch {
1580
+ }
1581
+ }
1582
+ if (message.type === "v2") delete message.type;
1583
+ return message;
1584
+ });
1585
+ const sortedMessages = fetchedMessages.sort(
1586
+ (a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
1516
1587
  );
1588
+ return format === "v2" ? sortedMessages.map(
1589
+ (m) => ({ ...m, content: m.content || { format: 2, parts: [{ type: "text", text: "" }] } })
1590
+ ) : sortedMessages;
1517
1591
  } catch (error$1) {
1518
- throw new error.MastraError(
1592
+ const mastraError = new error.MastraError(
1519
1593
  {
1520
- id: "MASTRA_STORAGE_PG_STORE_INSERT_FAILED",
1594
+ id: "MASTRA_STORAGE_PG_STORE_GET_MESSAGES_FAILED",
1521
1595
  domain: error.ErrorDomain.STORAGE,
1522
1596
  category: error.ErrorCategory.THIRD_PARTY,
1523
1597
  details: {
1524
- tableName
1525
- }
1526
- },
1527
- error$1
1528
- );
1529
- }
1530
- }
1531
- async load({ tableName, keys }) {
1532
- try {
1533
- const keyEntries = Object.entries(keys).map(([key, value]) => [utils.parseSqlIdentifier(key, "column name"), value]);
1534
- const conditions = keyEntries.map(([key], index) => `"${key}" = $${index + 1}`).join(" AND ");
1535
- const values = keyEntries.map(([_, value]) => value);
1536
- const result = await this.db.oneOrNone(
1537
- `SELECT * FROM ${this.getTableName(tableName)} WHERE ${conditions}`,
1538
- values
1539
- );
1540
- if (!result) {
1541
- return null;
1542
- }
1543
- if (tableName === storage.TABLE_WORKFLOW_SNAPSHOT) {
1544
- const snapshot = result;
1545
- if (typeof snapshot.snapshot === "string") {
1546
- snapshot.snapshot = JSON.parse(snapshot.snapshot);
1547
- }
1548
- return snapshot;
1549
- }
1550
- return result;
1551
- } catch (error$1) {
1552
- throw new error.MastraError(
1553
- {
1554
- id: "MASTRA_STORAGE_PG_STORE_LOAD_FAILED",
1555
- domain: error.ErrorDomain.STORAGE,
1556
- category: error.ErrorCategory.THIRD_PARTY,
1557
- details: {
1558
- tableName
1598
+ threadId
1559
1599
  }
1560
1600
  },
1561
1601
  error$1
1562
1602
  );
1603
+ this.logger?.error?.(mastraError.toString());
1604
+ this.logger?.trackException(mastraError);
1605
+ return [];
1563
1606
  }
1564
1607
  }
1565
- async getThreadById({ threadId }) {
1566
- try {
1567
- const thread = await this.db.oneOrNone(
1568
- `SELECT
1569
- id,
1570
- "resourceId",
1571
- title,
1572
- metadata,
1573
- "createdAt",
1574
- "updatedAt"
1575
- FROM ${this.getTableName(storage.TABLE_THREADS)}
1576
- WHERE id = $1`,
1577
- [threadId]
1578
- );
1579
- if (!thread) {
1580
- return null;
1608
+ async getMessagesPaginated(args) {
1609
+ const { threadId, format, selectBy } = args;
1610
+ const { page = 0, perPage: perPageInput, dateRange } = selectBy?.pagination || {};
1611
+ const fromDate = dateRange?.start;
1612
+ const toDate = dateRange?.end;
1613
+ const selectStatement = `SELECT id, content, role, type, "createdAt", thread_id AS "threadId", "resourceId"`;
1614
+ const orderByStatement = `ORDER BY "createdAt" DESC`;
1615
+ const messages = [];
1616
+ if (selectBy?.include?.length) {
1617
+ const includeMessages = await this._getIncludedMessages({ threadId, selectBy, orderByStatement });
1618
+ if (includeMessages) {
1619
+ messages.push(...includeMessages);
1581
1620
  }
1582
- return {
1583
- ...thread,
1584
- metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
1585
- createdAt: thread.createdAt,
1586
- updatedAt: thread.updatedAt
1587
- };
1588
- } catch (error$1) {
1589
- throw new error.MastraError(
1590
- {
1591
- id: "MASTRA_STORAGE_PG_STORE_GET_THREAD_BY_ID_FAILED",
1592
- domain: error.ErrorDomain.STORAGE,
1593
- category: error.ErrorCategory.THIRD_PARTY,
1594
- details: {
1595
- threadId
1596
- }
1597
- },
1598
- error$1
1599
- );
1600
- }
1601
- }
1602
- /**
1603
- * @deprecated use getThreadsByResourceIdPaginated instead
1604
- */
1605
- async getThreadsByResourceId(args) {
1606
- const { resourceId } = args;
1607
- try {
1608
- const baseQuery = `FROM ${this.getTableName(storage.TABLE_THREADS)} WHERE "resourceId" = $1`;
1609
- const queryParams = [resourceId];
1610
- const dataQuery = `SELECT id, "resourceId", title, metadata, "createdAt", "updatedAt" ${baseQuery} ORDER BY "createdAt" DESC`;
1611
- const rows = await this.db.manyOrNone(dataQuery, queryParams);
1612
- return (rows || []).map((thread) => ({
1613
- ...thread,
1614
- metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
1615
- createdAt: thread.createdAt,
1616
- updatedAt: thread.updatedAt
1617
- }));
1618
- } catch (error) {
1619
- this.logger.error(`Error getting threads for resource ${resourceId}:`, error);
1620
- return [];
1621
1621
  }
1622
- }
1623
- async getThreadsByResourceIdPaginated(args) {
1624
- const { resourceId, page = 0, perPage: perPageInput } = args;
1625
1622
  try {
1626
- const baseQuery = `FROM ${this.getTableName(storage.TABLE_THREADS)} WHERE "resourceId" = $1`;
1627
- const queryParams = [resourceId];
1628
- const perPage = perPageInput !== void 0 ? perPageInput : 100;
1623
+ const perPage = perPageInput !== void 0 ? perPageInput : storage.resolveMessageLimit({ last: selectBy?.last, defaultLimit: 40 });
1629
1624
  const currentOffset = page * perPage;
1630
- const countQuery = `SELECT COUNT(*) ${baseQuery}`;
1631
- const countResult = await this.db.one(countQuery, queryParams);
1625
+ const conditions = [`thread_id = $1`];
1626
+ const queryParams = [threadId];
1627
+ let paramIndex = 2;
1628
+ if (fromDate) {
1629
+ conditions.push(`"createdAt" >= $${paramIndex++}`);
1630
+ queryParams.push(fromDate);
1631
+ }
1632
+ if (toDate) {
1633
+ conditions.push(`"createdAt" <= $${paramIndex++}`);
1634
+ queryParams.push(toDate);
1635
+ }
1636
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1637
+ const tableName = getTableName({ indexName: storage.TABLE_MESSAGES, schemaName: getSchemaName(this.schema) });
1638
+ const countQuery = `SELECT COUNT(*) FROM ${tableName} ${whereClause}`;
1639
+ const countResult = await this.client.one(countQuery, queryParams);
1632
1640
  const total = parseInt(countResult.count, 10);
1633
- if (total === 0) {
1641
+ if (total === 0 && messages.length === 0) {
1634
1642
  return {
1635
- threads: [],
1643
+ messages: [],
1636
1644
  total: 0,
1637
1645
  page,
1638
1646
  perPage,
1639
1647
  hasMore: false
1640
1648
  };
1641
1649
  }
1642
- const dataQuery = `SELECT id, "resourceId", title, metadata, "createdAt", "updatedAt" ${baseQuery} ORDER BY "createdAt" DESC LIMIT $2 OFFSET $3`;
1643
- const rows = await this.db.manyOrNone(dataQuery, [...queryParams, perPage, currentOffset]);
1644
- const threads = (rows || []).map((thread) => ({
1645
- ...thread,
1646
- metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
1647
- createdAt: thread.createdAt,
1648
- // Assuming already Date objects or ISO strings
1649
- updatedAt: thread.updatedAt
1650
- }));
1650
+ const excludeIds = messages.map((m) => m.id);
1651
+ const excludeIdsParam = excludeIds.map((_, idx) => `$${idx + paramIndex}`).join(", ");
1652
+ paramIndex += excludeIds.length;
1653
+ const dataQuery = `${selectStatement} FROM ${tableName} ${whereClause} ${excludeIds.length ? `AND id NOT IN (${excludeIdsParam})` : ""}${orderByStatement} LIMIT $${paramIndex++} OFFSET $${paramIndex++}`;
1654
+ const rows = await this.client.manyOrNone(dataQuery, [...queryParams, ...excludeIds, perPage, currentOffset]);
1655
+ messages.push(...rows || []);
1656
+ const messagesWithParsedContent = messages.map((message) => {
1657
+ if (typeof message.content === "string") {
1658
+ try {
1659
+ return { ...message, content: JSON.parse(message.content) };
1660
+ } catch {
1661
+ return message;
1662
+ }
1663
+ }
1664
+ return message;
1665
+ });
1666
+ const list = new agent.MessageList().add(messagesWithParsedContent, "memory");
1667
+ const messagesToReturn = format === `v2` ? list.get.all.v2() : list.get.all.v1();
1651
1668
  return {
1652
- threads,
1669
+ messages: messagesToReturn,
1653
1670
  total,
1654
1671
  page,
1655
1672
  perPage,
1656
- hasMore: currentOffset + threads.length < total
1673
+ hasMore: currentOffset + rows.length < total
1657
1674
  };
1658
1675
  } catch (error$1) {
1659
1676
  const mastraError = new error.MastraError(
1660
1677
  {
1661
- id: "MASTRA_STORAGE_PG_STORE_GET_THREADS_BY_RESOURCE_ID_PAGINATED_FAILED",
1678
+ id: "MASTRA_STORAGE_PG_STORE_GET_MESSAGES_PAGINATED_FAILED",
1662
1679
  domain: error.ErrorDomain.STORAGE,
1663
1680
  category: error.ErrorCategory.THIRD_PARTY,
1664
1681
  details: {
1665
- resourceId,
1682
+ threadId,
1666
1683
  page
1667
1684
  }
1668
1685
  },
@@ -1670,872 +1687,1348 @@ var PostgresStore = class extends storage.MastraStorage {
1670
1687
  );
1671
1688
  this.logger?.error?.(mastraError.toString());
1672
1689
  this.logger?.trackException(mastraError);
1673
- return { threads: [], total: 0, page, perPage: perPageInput || 100, hasMore: false };
1690
+ return { messages: [], total: 0, page, perPage: perPageInput || 40, hasMore: false };
1674
1691
  }
1675
1692
  }
1676
- async saveThread({ thread }) {
1693
+ async saveMessages({
1694
+ messages,
1695
+ format
1696
+ }) {
1697
+ if (messages.length === 0) return messages;
1698
+ const threadId = messages[0]?.threadId;
1699
+ if (!threadId) {
1700
+ throw new error.MastraError({
1701
+ id: "MASTRA_STORAGE_PG_STORE_SAVE_MESSAGES_FAILED",
1702
+ domain: error.ErrorDomain.STORAGE,
1703
+ category: error.ErrorCategory.THIRD_PARTY,
1704
+ text: `Thread ID is required`
1705
+ });
1706
+ }
1707
+ const thread = await this.getThreadById({ threadId });
1708
+ if (!thread) {
1709
+ throw new error.MastraError({
1710
+ id: "MASTRA_STORAGE_PG_STORE_SAVE_MESSAGES_FAILED",
1711
+ domain: error.ErrorDomain.STORAGE,
1712
+ category: error.ErrorCategory.THIRD_PARTY,
1713
+ text: `Thread ${threadId} not found`,
1714
+ details: {
1715
+ threadId
1716
+ }
1717
+ });
1718
+ }
1677
1719
  try {
1678
- await this.db.none(
1679
- `INSERT INTO ${this.getTableName(storage.TABLE_THREADS)} (
1680
- id,
1681
- "resourceId",
1682
- title,
1683
- metadata,
1684
- "createdAt",
1685
- "updatedAt"
1686
- ) VALUES ($1, $2, $3, $4, $5, $6)
1687
- ON CONFLICT (id) DO UPDATE SET
1688
- "resourceId" = EXCLUDED."resourceId",
1689
- title = EXCLUDED.title,
1690
- metadata = EXCLUDED.metadata,
1691
- "createdAt" = EXCLUDED."createdAt",
1692
- "updatedAt" = EXCLUDED."updatedAt"`,
1693
- [
1694
- thread.id,
1695
- thread.resourceId,
1696
- thread.title,
1697
- thread.metadata ? JSON.stringify(thread.metadata) : null,
1698
- thread.createdAt,
1699
- thread.updatedAt
1700
- ]
1701
- );
1702
- return thread;
1720
+ const tableName = getTableName({ indexName: storage.TABLE_MESSAGES, schemaName: getSchemaName(this.schema) });
1721
+ await this.client.tx(async (t) => {
1722
+ const messageInserts = messages.map((message) => {
1723
+ if (!message.threadId) {
1724
+ throw new Error(
1725
+ `Expected to find a threadId for message, but couldn't find one. An unexpected error has occurred.`
1726
+ );
1727
+ }
1728
+ if (!message.resourceId) {
1729
+ throw new Error(
1730
+ `Expected to find a resourceId for message, but couldn't find one. An unexpected error has occurred.`
1731
+ );
1732
+ }
1733
+ return t.none(
1734
+ `INSERT INTO ${tableName} (id, thread_id, content, "createdAt", "createdAtZ", role, type, "resourceId")
1735
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
1736
+ ON CONFLICT (id) DO UPDATE SET
1737
+ thread_id = EXCLUDED.thread_id,
1738
+ content = EXCLUDED.content,
1739
+ role = EXCLUDED.role,
1740
+ type = EXCLUDED.type,
1741
+ "resourceId" = EXCLUDED."resourceId"`,
1742
+ [
1743
+ message.id,
1744
+ message.threadId,
1745
+ typeof message.content === "string" ? message.content : JSON.stringify(message.content),
1746
+ message.createdAt || (/* @__PURE__ */ new Date()).toISOString(),
1747
+ message.createdAt || (/* @__PURE__ */ new Date()).toISOString(),
1748
+ message.role,
1749
+ message.type || "v2",
1750
+ message.resourceId
1751
+ ]
1752
+ );
1753
+ });
1754
+ const threadTableName = getTableName({ indexName: storage.TABLE_THREADS, schemaName: getSchemaName(this.schema) });
1755
+ const threadUpdate = t.none(
1756
+ `UPDATE ${threadTableName}
1757
+ SET
1758
+ "updatedAt" = $1,
1759
+ "updatedAtZ" = $1
1760
+ WHERE id = $2
1761
+ `,
1762
+ [(/* @__PURE__ */ new Date()).toISOString(), threadId]
1763
+ );
1764
+ await Promise.all([...messageInserts, threadUpdate]);
1765
+ });
1766
+ const messagesWithParsedContent = messages.map((message) => {
1767
+ if (typeof message.content === "string") {
1768
+ try {
1769
+ return { ...message, content: JSON.parse(message.content) };
1770
+ } catch {
1771
+ return message;
1772
+ }
1773
+ }
1774
+ return message;
1775
+ });
1776
+ const list = new agent.MessageList().add(messagesWithParsedContent, "memory");
1777
+ if (format === `v2`) return list.get.all.v2();
1778
+ return list.get.all.v1();
1703
1779
  } catch (error$1) {
1704
1780
  throw new error.MastraError(
1705
1781
  {
1706
- id: "MASTRA_STORAGE_PG_STORE_SAVE_THREAD_FAILED",
1782
+ id: "MASTRA_STORAGE_PG_STORE_SAVE_MESSAGES_FAILED",
1707
1783
  domain: error.ErrorDomain.STORAGE,
1708
1784
  category: error.ErrorCategory.THIRD_PARTY,
1709
1785
  details: {
1710
- threadId: thread.id
1786
+ threadId
1711
1787
  }
1712
1788
  },
1713
1789
  error$1
1714
1790
  );
1715
1791
  }
1716
1792
  }
1717
- async updateThread({
1718
- id,
1719
- title,
1720
- metadata
1793
+ async updateMessages({
1794
+ messages
1721
1795
  }) {
1722
- const existingThread = await this.getThreadById({ threadId: id });
1723
- if (!existingThread) {
1724
- throw new error.MastraError({
1725
- id: "MASTRA_STORAGE_PG_STORE_UPDATE_THREAD_FAILED",
1726
- domain: error.ErrorDomain.STORAGE,
1727
- category: error.ErrorCategory.USER,
1728
- text: `Thread ${id} not found`,
1729
- details: {
1730
- threadId: id,
1731
- title
1796
+ if (messages.length === 0) {
1797
+ return [];
1798
+ }
1799
+ const messageIds = messages.map((m) => m.id);
1800
+ const selectQuery = `SELECT id, content, role, type, "createdAt", thread_id AS "threadId", "resourceId" FROM ${getTableName({ indexName: storage.TABLE_MESSAGES, schemaName: getSchemaName(this.schema) })} WHERE id IN ($1:list)`;
1801
+ const existingMessagesDb = await this.client.manyOrNone(selectQuery, [messageIds]);
1802
+ if (existingMessagesDb.length === 0) {
1803
+ return [];
1804
+ }
1805
+ const existingMessages = existingMessagesDb.map((msg) => {
1806
+ if (typeof msg.content === "string") {
1807
+ try {
1808
+ msg.content = JSON.parse(msg.content);
1809
+ } catch {
1732
1810
  }
1733
- });
1811
+ }
1812
+ return msg;
1813
+ });
1814
+ const threadIdsToUpdate = /* @__PURE__ */ new Set();
1815
+ await this.client.tx(async (t) => {
1816
+ const queries = [];
1817
+ const columnMapping = {
1818
+ threadId: "thread_id"
1819
+ };
1820
+ for (const existingMessage of existingMessages) {
1821
+ const updatePayload = messages.find((m) => m.id === existingMessage.id);
1822
+ if (!updatePayload) continue;
1823
+ const { id, ...fieldsToUpdate } = updatePayload;
1824
+ if (Object.keys(fieldsToUpdate).length === 0) continue;
1825
+ threadIdsToUpdate.add(existingMessage.threadId);
1826
+ if (updatePayload.threadId && updatePayload.threadId !== existingMessage.threadId) {
1827
+ threadIdsToUpdate.add(updatePayload.threadId);
1828
+ }
1829
+ const setClauses = [];
1830
+ const values = [];
1831
+ let paramIndex = 1;
1832
+ const updatableFields = { ...fieldsToUpdate };
1833
+ if (updatableFields.content) {
1834
+ const newContent = {
1835
+ ...existingMessage.content,
1836
+ ...updatableFields.content,
1837
+ // Deep merge metadata if it exists on both
1838
+ ...existingMessage.content?.metadata && updatableFields.content.metadata ? {
1839
+ metadata: {
1840
+ ...existingMessage.content.metadata,
1841
+ ...updatableFields.content.metadata
1842
+ }
1843
+ } : {}
1844
+ };
1845
+ setClauses.push(`content = $${paramIndex++}`);
1846
+ values.push(newContent);
1847
+ delete updatableFields.content;
1848
+ }
1849
+ for (const key in updatableFields) {
1850
+ if (Object.prototype.hasOwnProperty.call(updatableFields, key)) {
1851
+ const dbColumn = columnMapping[key] || key;
1852
+ setClauses.push(`"${dbColumn}" = $${paramIndex++}`);
1853
+ values.push(updatableFields[key]);
1854
+ }
1855
+ }
1856
+ if (setClauses.length > 0) {
1857
+ values.push(id);
1858
+ const sql = `UPDATE ${getTableName({ indexName: storage.TABLE_MESSAGES, schemaName: getSchemaName(this.schema) })} SET ${setClauses.join(", ")} WHERE id = $${paramIndex}`;
1859
+ queries.push(t.none(sql, values));
1860
+ }
1861
+ }
1862
+ if (threadIdsToUpdate.size > 0) {
1863
+ queries.push(
1864
+ t.none(
1865
+ `UPDATE ${getTableName({ indexName: storage.TABLE_THREADS, schemaName: getSchemaName(this.schema) })} SET "updatedAt" = NOW(), "updatedAtZ" = NOW() WHERE id IN ($1:list)`,
1866
+ [Array.from(threadIdsToUpdate)]
1867
+ )
1868
+ );
1869
+ }
1870
+ if (queries.length > 0) {
1871
+ await t.batch(queries);
1872
+ }
1873
+ });
1874
+ const updatedMessages = await this.client.manyOrNone(selectQuery, [messageIds]);
1875
+ return (updatedMessages || []).map((message) => {
1876
+ if (typeof message.content === "string") {
1877
+ try {
1878
+ message.content = JSON.parse(message.content);
1879
+ } catch {
1880
+ }
1881
+ }
1882
+ return message;
1883
+ });
1884
+ }
1885
+ async getResourceById({ resourceId }) {
1886
+ const tableName = getTableName({ indexName: storage.TABLE_RESOURCES, schemaName: getSchemaName(this.schema) });
1887
+ const result = await this.client.oneOrNone(
1888
+ `SELECT * FROM ${tableName} WHERE id = $1`,
1889
+ [resourceId]
1890
+ );
1891
+ if (!result) {
1892
+ return null;
1734
1893
  }
1735
- const mergedMetadata = {
1736
- ...existingThread.metadata,
1737
- ...metadata
1894
+ return {
1895
+ id: result.id,
1896
+ createdAt: result.createdAtZ || result.createdAt,
1897
+ updatedAt: result.updatedAtZ || result.updatedAt,
1898
+ workingMemory: result.workingMemory,
1899
+ metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata
1900
+ };
1901
+ }
1902
+ async saveResource({ resource }) {
1903
+ await this.operations.insert({
1904
+ tableName: storage.TABLE_RESOURCES,
1905
+ record: {
1906
+ ...resource,
1907
+ metadata: JSON.stringify(resource.metadata)
1908
+ }
1909
+ });
1910
+ return resource;
1911
+ }
1912
+ async updateResource({
1913
+ resourceId,
1914
+ workingMemory,
1915
+ metadata
1916
+ }) {
1917
+ const existingResource = await this.getResourceById({ resourceId });
1918
+ if (!existingResource) {
1919
+ const newResource = {
1920
+ id: resourceId,
1921
+ workingMemory,
1922
+ metadata: metadata || {},
1923
+ createdAt: /* @__PURE__ */ new Date(),
1924
+ updatedAt: /* @__PURE__ */ new Date()
1925
+ };
1926
+ return this.saveResource({ resource: newResource });
1927
+ }
1928
+ const updatedResource = {
1929
+ ...existingResource,
1930
+ workingMemory: workingMemory !== void 0 ? workingMemory : existingResource.workingMemory,
1931
+ metadata: {
1932
+ ...existingResource.metadata,
1933
+ ...metadata
1934
+ },
1935
+ updatedAt: /* @__PURE__ */ new Date()
1738
1936
  };
1937
+ const tableName = getTableName({ indexName: storage.TABLE_RESOURCES, schemaName: getSchemaName(this.schema) });
1938
+ const updates = [];
1939
+ const values = [];
1940
+ let paramIndex = 1;
1941
+ if (workingMemory !== void 0) {
1942
+ updates.push(`"workingMemory" = $${paramIndex}`);
1943
+ values.push(workingMemory);
1944
+ paramIndex++;
1945
+ }
1946
+ if (metadata) {
1947
+ updates.push(`metadata = $${paramIndex}`);
1948
+ values.push(JSON.stringify(updatedResource.metadata));
1949
+ paramIndex++;
1950
+ }
1951
+ updates.push(`"updatedAt" = $${paramIndex}`);
1952
+ values.push(updatedResource.updatedAt.toISOString());
1953
+ updates.push(`"updatedAtZ" = $${paramIndex++}`);
1954
+ values.push(updatedResource.updatedAt.toISOString());
1955
+ paramIndex++;
1956
+ values.push(resourceId);
1957
+ await this.client.none(`UPDATE ${tableName} SET ${updates.join(", ")} WHERE id = $${paramIndex}`, values);
1958
+ return updatedResource;
1959
+ }
1960
+ };
1961
+ var StoreOperationsPG = class extends storage.StoreOperations {
1962
+ client;
1963
+ schemaName;
1964
+ setupSchemaPromise = null;
1965
+ schemaSetupComplete = void 0;
1966
+ constructor({ client, schemaName }) {
1967
+ super();
1968
+ this.client = client;
1969
+ this.schemaName = schemaName;
1970
+ }
1971
+ async hasColumn(table, column) {
1972
+ const schema = this.schemaName || "public";
1973
+ const result = await this.client.oneOrNone(
1974
+ `SELECT 1 FROM information_schema.columns WHERE table_schema = $1 AND table_name = $2 AND (column_name = $3 OR column_name = $4)`,
1975
+ [schema, table, column, column.toLowerCase()]
1976
+ );
1977
+ return !!result;
1978
+ }
1979
+ async setupSchema() {
1980
+ if (!this.schemaName || this.schemaSetupComplete) {
1981
+ return;
1982
+ }
1983
+ const schemaName = getSchemaName(this.schemaName);
1984
+ if (!this.setupSchemaPromise) {
1985
+ this.setupSchemaPromise = (async () => {
1986
+ try {
1987
+ const schemaExists = await this.client.oneOrNone(
1988
+ `
1989
+ SELECT EXISTS (
1990
+ SELECT 1 FROM information_schema.schemata
1991
+ WHERE schema_name = $1
1992
+ )
1993
+ `,
1994
+ [this.schemaName]
1995
+ );
1996
+ if (!schemaExists?.exists) {
1997
+ try {
1998
+ await this.client.none(`CREATE SCHEMA IF NOT EXISTS ${schemaName}`);
1999
+ this.logger.info(`Schema "${this.schemaName}" created successfully`);
2000
+ } catch (error) {
2001
+ this.logger.error(`Failed to create schema "${this.schemaName}"`, { error });
2002
+ throw new Error(
2003
+ `Unable to create schema "${this.schemaName}". This requires CREATE privilege on the database. Either create the schema manually or grant CREATE privilege to the user.`
2004
+ );
2005
+ }
2006
+ }
2007
+ this.schemaSetupComplete = true;
2008
+ this.logger.debug(`Schema "${schemaName}" is ready for use`);
2009
+ } catch (error) {
2010
+ this.schemaSetupComplete = void 0;
2011
+ this.setupSchemaPromise = null;
2012
+ throw error;
2013
+ } finally {
2014
+ this.setupSchemaPromise = null;
2015
+ }
2016
+ })();
2017
+ }
2018
+ await this.setupSchemaPromise;
2019
+ }
2020
+ async insert({ tableName, record }) {
1739
2021
  try {
1740
- const thread = await this.db.one(
1741
- `UPDATE ${this.getTableName(storage.TABLE_THREADS)}
1742
- SET title = $1,
1743
- metadata = $2,
1744
- "updatedAt" = $3
1745
- WHERE id = $4
1746
- RETURNING *`,
1747
- [title, mergedMetadata, (/* @__PURE__ */ new Date()).toISOString(), id]
2022
+ if (record.createdAt) {
2023
+ record.createdAtZ = record.createdAt;
2024
+ }
2025
+ if (record.created_at) {
2026
+ record.created_atZ = record.created_at;
2027
+ }
2028
+ if (record.updatedAt) {
2029
+ record.updatedAtZ = record.updatedAt;
2030
+ }
2031
+ const schemaName = getSchemaName(this.schemaName);
2032
+ const columns = Object.keys(record).map((col) => utils.parseSqlIdentifier(col, "column name"));
2033
+ const values = Object.values(record);
2034
+ const placeholders = values.map((_, i) => `$${i + 1}`).join(", ");
2035
+ await this.client.none(
2036
+ `INSERT INTO ${getTableName({ indexName: tableName, schemaName })} (${columns.map((c) => `"${c}"`).join(", ")}) VALUES (${placeholders})`,
2037
+ values
1748
2038
  );
1749
- return {
1750
- ...thread,
1751
- metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
1752
- createdAt: thread.createdAt,
1753
- updatedAt: thread.updatedAt
1754
- };
1755
2039
  } catch (error$1) {
1756
2040
  throw new error.MastraError(
1757
2041
  {
1758
- id: "MASTRA_STORAGE_PG_STORE_UPDATE_THREAD_FAILED",
2042
+ id: "MASTRA_STORAGE_PG_STORE_INSERT_FAILED",
1759
2043
  domain: error.ErrorDomain.STORAGE,
1760
2044
  category: error.ErrorCategory.THIRD_PARTY,
1761
2045
  details: {
1762
- threadId: id,
1763
- title
2046
+ tableName
1764
2047
  }
1765
2048
  },
1766
2049
  error$1
1767
2050
  );
1768
2051
  }
1769
2052
  }
1770
- async deleteThread({ threadId }) {
2053
+ async clearTable({ tableName }) {
1771
2054
  try {
1772
- await this.db.tx(async (t) => {
1773
- await t.none(`DELETE FROM ${this.getTableName(storage.TABLE_MESSAGES)} WHERE thread_id = $1`, [threadId]);
1774
- await t.none(`DELETE FROM ${this.getTableName(storage.TABLE_THREADS)} WHERE id = $1`, [threadId]);
1775
- });
2055
+ const schemaName = getSchemaName(this.schemaName);
2056
+ const tableNameWithSchema = getTableName({ indexName: tableName, schemaName });
2057
+ await this.client.none(`TRUNCATE TABLE ${tableNameWithSchema} CASCADE`);
1776
2058
  } catch (error$1) {
1777
2059
  throw new error.MastraError(
1778
2060
  {
1779
- id: "MASTRA_STORAGE_PG_STORE_DELETE_THREAD_FAILED",
2061
+ id: "MASTRA_STORAGE_PG_STORE_CLEAR_TABLE_FAILED",
1780
2062
  domain: error.ErrorDomain.STORAGE,
1781
2063
  category: error.ErrorCategory.THIRD_PARTY,
1782
2064
  details: {
1783
- threadId
2065
+ tableName
1784
2066
  }
1785
2067
  },
1786
2068
  error$1
1787
2069
  );
1788
2070
  }
1789
2071
  }
1790
- async _getIncludedMessages({
1791
- threadId,
1792
- selectBy,
1793
- orderByStatement
2072
+ getDefaultValue(type) {
2073
+ switch (type) {
2074
+ case "timestamp":
2075
+ return "DEFAULT NOW()";
2076
+ case "jsonb":
2077
+ return "DEFAULT '{}'::jsonb";
2078
+ default:
2079
+ return super.getDefaultValue(type);
2080
+ }
2081
+ }
2082
+ async createTable({
2083
+ tableName,
2084
+ schema
1794
2085
  }) {
1795
- const include = selectBy?.include;
1796
- if (!include) return null;
1797
- const unionQueries = [];
1798
- const params = [];
1799
- let paramIdx = 1;
1800
- for (const inc of include) {
1801
- const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
1802
- const searchId = inc.threadId || threadId;
1803
- unionQueries.push(
1804
- `
1805
- SELECT * FROM (
1806
- WITH ordered_messages AS (
1807
- SELECT
1808
- *,
1809
- ROW_NUMBER() OVER (${orderByStatement}) as row_num
1810
- FROM ${this.getTableName(storage.TABLE_MESSAGES)}
1811
- WHERE thread_id = $${paramIdx}
1812
- )
1813
- SELECT
1814
- m.id,
1815
- m.content,
1816
- m.role,
1817
- m.type,
1818
- m."createdAt",
1819
- m.thread_id AS "threadId",
1820
- m."resourceId"
1821
- FROM ordered_messages m
1822
- WHERE m.id = $${paramIdx + 1}
1823
- OR EXISTS (
1824
- SELECT 1 FROM ordered_messages target
1825
- WHERE target.id = $${paramIdx + 1}
1826
- AND (
1827
- -- Get previous messages based on the max withPreviousMessages
1828
- (m.row_num <= target.row_num + $${paramIdx + 2} AND m.row_num > target.row_num)
1829
- OR
1830
- -- Get next messages based on the max withNextMessages
1831
- (m.row_num >= target.row_num - $${paramIdx + 3} AND m.row_num < target.row_num)
1832
- )
1833
- )
1834
- ) AS query_${paramIdx}
1835
- `
1836
- // Keep ASC for final sorting after fetching context
2086
+ try {
2087
+ const timeZColumnNames = Object.entries(schema).filter(([_, def]) => def.type === "timestamp").map(([name]) => name);
2088
+ const timeZColumns = Object.entries(schema).filter(([_, def]) => def.type === "timestamp").map(([name]) => {
2089
+ const parsedName = utils.parseSqlIdentifier(name, "column name");
2090
+ return `"${parsedName}Z" TIMESTAMPTZ DEFAULT NOW()`;
2091
+ });
2092
+ const columns = Object.entries(schema).map(([name, def]) => {
2093
+ const parsedName = utils.parseSqlIdentifier(name, "column name");
2094
+ const constraints = [];
2095
+ if (def.primaryKey) constraints.push("PRIMARY KEY");
2096
+ if (!def.nullable) constraints.push("NOT NULL");
2097
+ return `"${parsedName}" ${def.type.toUpperCase()} ${constraints.join(" ")}`;
2098
+ });
2099
+ if (this.schemaName) {
2100
+ await this.setupSchema();
2101
+ }
2102
+ const finalColumns = [...columns, ...timeZColumns].join(",\n");
2103
+ const sql = `
2104
+ CREATE TABLE IF NOT EXISTS ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} (
2105
+ ${finalColumns}
2106
+ );
2107
+ ${tableName === storage.TABLE_WORKFLOW_SNAPSHOT ? `
2108
+ DO $$ BEGIN
2109
+ IF NOT EXISTS (
2110
+ SELECT 1 FROM pg_constraint WHERE conname = 'mastra_workflow_snapshot_workflow_name_run_id_key'
2111
+ ) THEN
2112
+ ALTER TABLE ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })}
2113
+ ADD CONSTRAINT mastra_workflow_snapshot_workflow_name_run_id_key
2114
+ UNIQUE (workflow_name, run_id);
2115
+ END IF;
2116
+ END $$;
2117
+ ` : ""}
2118
+ `;
2119
+ await this.client.none(sql);
2120
+ console.log("Alter Table SQL", tableName);
2121
+ console.log("timeZColumnNames", timeZColumnNames);
2122
+ await this.alterTable({
2123
+ tableName,
2124
+ schema,
2125
+ ifNotExists: timeZColumnNames
2126
+ });
2127
+ } catch (error$1) {
2128
+ throw new error.MastraError(
2129
+ {
2130
+ id: "MASTRA_STORAGE_PG_STORE_CREATE_TABLE_FAILED",
2131
+ domain: error.ErrorDomain.STORAGE,
2132
+ category: error.ErrorCategory.THIRD_PARTY,
2133
+ details: {
2134
+ tableName
2135
+ }
2136
+ },
2137
+ error$1
1837
2138
  );
1838
- params.push(searchId, id, withPreviousMessages, withNextMessages);
1839
- paramIdx += 4;
1840
2139
  }
1841
- const finalQuery = unionQueries.join(" UNION ALL ") + ' ORDER BY "createdAt" ASC';
1842
- const includedRows = await this.db.manyOrNone(finalQuery, params);
1843
- const seen = /* @__PURE__ */ new Set();
1844
- const dedupedRows = includedRows.filter((row) => {
1845
- if (seen.has(row.id)) return false;
1846
- seen.add(row.id);
1847
- return true;
1848
- });
1849
- return dedupedRows;
1850
2140
  }
1851
- async getMessages(args) {
1852
- const { threadId, format, selectBy } = args;
1853
- const selectStatement = `SELECT id, content, role, type, "createdAt", thread_id AS "threadId", "resourceId"`;
1854
- const orderByStatement = `ORDER BY "createdAt" DESC`;
1855
- const limit = this.resolveMessageLimit({ last: selectBy?.last, defaultLimit: 40 });
2141
+ /**
2142
+ * Alters table schema to add columns if they don't exist
2143
+ * @param tableName Name of the table
2144
+ * @param schema Schema of the table
2145
+ * @param ifNotExists Array of column names to add if they don't exist
2146
+ */
2147
+ async alterTable({
2148
+ tableName,
2149
+ schema,
2150
+ ifNotExists
2151
+ }) {
2152
+ const fullTableName = getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) });
1856
2153
  try {
1857
- let rows = [];
1858
- const include = selectBy?.include || [];
1859
- if (include?.length) {
1860
- const includeMessages = await this._getIncludedMessages({ threadId, selectBy, orderByStatement });
1861
- if (includeMessages) {
1862
- rows.push(...includeMessages);
1863
- }
1864
- }
1865
- const excludeIds = rows.map((m) => m.id);
1866
- const excludeIdsParam = excludeIds.map((_, idx) => `$${idx + 2}`).join(", ");
1867
- let query = `${selectStatement} FROM ${this.getTableName(storage.TABLE_MESSAGES)} WHERE thread_id = $1
1868
- ${excludeIds.length ? `AND id NOT IN (${excludeIdsParam})` : ""}
1869
- ${orderByStatement}
1870
- LIMIT $${excludeIds.length + 2}
1871
- `;
1872
- const queryParams = [threadId, ...excludeIds, limit];
1873
- const remainingRows = await this.db.manyOrNone(query, queryParams);
1874
- rows.push(...remainingRows);
1875
- const fetchedMessages = (rows || []).map((message) => {
1876
- if (typeof message.content === "string") {
1877
- try {
1878
- message.content = JSON.parse(message.content);
1879
- } catch {
2154
+ for (const columnName of ifNotExists) {
2155
+ if (schema[columnName]) {
2156
+ const columnDef = schema[columnName];
2157
+ const sqlType = this.getSqlType(columnDef.type);
2158
+ const nullable = columnDef.nullable === false ? "NOT NULL" : "";
2159
+ const defaultValue = columnDef.nullable === false ? this.getDefaultValue(columnDef.type) : "";
2160
+ const parsedColumnName = utils.parseSqlIdentifier(columnName, "column name");
2161
+ const alterSql = `ALTER TABLE ${fullTableName} ADD COLUMN IF NOT EXISTS "${parsedColumnName}" ${sqlType} ${nullable} ${defaultValue}`.trim();
2162
+ await this.client.none(alterSql);
2163
+ if (sqlType === "TIMESTAMP") {
2164
+ const alterSql2 = `ALTER TABLE ${fullTableName} ADD COLUMN IF NOT EXISTS "${parsedColumnName}Z" TIMESTAMPTZ DEFAULT NOW()`.trim();
2165
+ await this.client.none(alterSql2);
1880
2166
  }
2167
+ this.logger?.debug?.(`Ensured column ${parsedColumnName} exists in table ${fullTableName}`);
1881
2168
  }
1882
- if (message.type === "v2") delete message.type;
1883
- return message;
1884
- });
1885
- const sortedMessages = fetchedMessages.sort(
1886
- (a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
1887
- );
1888
- return format === "v2" ? sortedMessages.map(
1889
- (m) => ({ ...m, content: m.content || { format: 2, parts: [{ type: "text", text: "" }] } })
1890
- ) : sortedMessages;
2169
+ }
1891
2170
  } catch (error$1) {
1892
- const mastraError = new error.MastraError(
2171
+ throw new error.MastraError(
1893
2172
  {
1894
- id: "MASTRA_STORAGE_PG_STORE_GET_MESSAGES_FAILED",
2173
+ id: "MASTRA_STORAGE_PG_STORE_ALTER_TABLE_FAILED",
1895
2174
  domain: error.ErrorDomain.STORAGE,
1896
2175
  category: error.ErrorCategory.THIRD_PARTY,
1897
2176
  details: {
1898
- threadId
2177
+ tableName
1899
2178
  }
1900
2179
  },
1901
2180
  error$1
1902
2181
  );
1903
- this.logger?.error?.(mastraError.toString());
1904
- this.logger?.trackException(mastraError);
1905
- return [];
1906
2182
  }
1907
2183
  }
1908
- async getMessagesPaginated(args) {
1909
- const { threadId, format, selectBy } = args;
1910
- const { page = 0, perPage: perPageInput, dateRange } = selectBy?.pagination || {};
1911
- const fromDate = dateRange?.start;
1912
- const toDate = dateRange?.end;
1913
- const selectStatement = `SELECT id, content, role, type, "createdAt", thread_id AS "threadId", "resourceId"`;
1914
- const orderByStatement = `ORDER BY "createdAt" DESC`;
1915
- const messages = [];
1916
- if (selectBy?.include?.length) {
1917
- const includeMessages = await this._getIncludedMessages({ threadId, selectBy, orderByStatement });
1918
- if (includeMessages) {
1919
- messages.push(...includeMessages);
1920
- }
1921
- }
2184
+ async load({ tableName, keys }) {
1922
2185
  try {
1923
- const perPage = perPageInput !== void 0 ? perPageInput : this.resolveMessageLimit({ last: selectBy?.last, defaultLimit: 40 });
1924
- const currentOffset = page * perPage;
1925
- const conditions = [`thread_id = $1`];
1926
- const queryParams = [threadId];
1927
- let paramIndex = 2;
1928
- if (fromDate) {
1929
- conditions.push(`"createdAt" >= $${paramIndex++}`);
1930
- queryParams.push(fromDate);
1931
- }
1932
- if (toDate) {
1933
- conditions.push(`"createdAt" <= $${paramIndex++}`);
1934
- queryParams.push(toDate);
1935
- }
1936
- const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1937
- const countQuery = `SELECT COUNT(*) FROM ${this.getTableName(storage.TABLE_MESSAGES)} ${whereClause}`;
1938
- const countResult = await this.db.one(countQuery, queryParams);
1939
- const total = parseInt(countResult.count, 10);
1940
- if (total === 0 && messages.length === 0) {
1941
- return {
1942
- messages: [],
1943
- total: 0,
1944
- page,
1945
- perPage,
1946
- hasMore: false
1947
- };
2186
+ const keyEntries = Object.entries(keys).map(([key, value]) => [utils.parseSqlIdentifier(key, "column name"), value]);
2187
+ const conditions = keyEntries.map(([key], index) => `"${key}" = $${index + 1}`).join(" AND ");
2188
+ const values = keyEntries.map(([_, value]) => value);
2189
+ const result = await this.client.oneOrNone(
2190
+ `SELECT * FROM ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} WHERE ${conditions}`,
2191
+ values
2192
+ );
2193
+ if (!result) {
2194
+ return null;
1948
2195
  }
1949
- const excludeIds = messages.map((m) => m.id);
1950
- const excludeIdsParam = excludeIds.map((_, idx) => `$${idx + paramIndex}`).join(", ");
1951
- paramIndex += excludeIds.length;
1952
- const dataQuery = `${selectStatement} FROM ${this.getTableName(
1953
- storage.TABLE_MESSAGES
1954
- )} ${whereClause} ${excludeIds.length ? `AND id NOT IN (${excludeIdsParam})` : ""}${orderByStatement} LIMIT $${paramIndex++} OFFSET $${paramIndex++}`;
1955
- const rows = await this.db.manyOrNone(dataQuery, [...queryParams, ...excludeIds, perPage, currentOffset]);
1956
- messages.push(...rows || []);
1957
- const messagesWithParsedContent = messages.map((message) => {
1958
- if (typeof message.content === "string") {
1959
- try {
1960
- return { ...message, content: JSON.parse(message.content) };
1961
- } catch {
1962
- return message;
1963
- }
2196
+ if (tableName === storage.TABLE_WORKFLOW_SNAPSHOT) {
2197
+ const snapshot = result;
2198
+ if (typeof snapshot.snapshot === "string") {
2199
+ snapshot.snapshot = JSON.parse(snapshot.snapshot);
1964
2200
  }
1965
- return message;
1966
- });
1967
- const list = new agent.MessageList().add(messagesWithParsedContent, "memory");
1968
- const messagesToReturn = format === `v2` ? list.get.all.v2() : list.get.all.v1();
1969
- return {
1970
- messages: messagesToReturn,
1971
- total,
1972
- page,
1973
- perPage,
1974
- hasMore: currentOffset + rows.length < total
1975
- };
2201
+ return snapshot;
2202
+ }
2203
+ return result;
1976
2204
  } catch (error$1) {
1977
- const mastraError = new error.MastraError(
2205
+ throw new error.MastraError(
1978
2206
  {
1979
- id: "MASTRA_STORAGE_PG_STORE_GET_MESSAGES_PAGINATED_FAILED",
2207
+ id: "MASTRA_STORAGE_PG_STORE_LOAD_FAILED",
1980
2208
  domain: error.ErrorDomain.STORAGE,
1981
2209
  category: error.ErrorCategory.THIRD_PARTY,
1982
2210
  details: {
1983
- threadId,
1984
- page
2211
+ tableName
1985
2212
  }
1986
2213
  },
1987
2214
  error$1
1988
2215
  );
1989
- this.logger?.error?.(mastraError.toString());
1990
- this.logger?.trackException(mastraError);
1991
- return { messages: [], total: 0, page, perPage: perPageInput || 40, hasMore: false };
1992
2216
  }
1993
2217
  }
1994
- async saveMessages({
1995
- messages,
1996
- format
1997
- }) {
1998
- if (messages.length === 0) return messages;
1999
- const threadId = messages[0]?.threadId;
2000
- if (!threadId) {
2001
- throw new error.MastraError({
2002
- id: "MASTRA_STORAGE_PG_STORE_SAVE_MESSAGES_FAILED",
2003
- domain: error.ErrorDomain.STORAGE,
2004
- category: error.ErrorCategory.THIRD_PARTY,
2005
- text: `Thread ID is required`
2006
- });
2007
- }
2008
- const thread = await this.getThreadById({ threadId });
2009
- if (!thread) {
2010
- throw new error.MastraError({
2011
- id: "MASTRA_STORAGE_PG_STORE_SAVE_MESSAGES_FAILED",
2012
- domain: error.ErrorDomain.STORAGE,
2013
- category: error.ErrorCategory.THIRD_PARTY,
2014
- text: `Thread ${threadId} not found`,
2015
- details: {
2016
- threadId
2017
- }
2018
- });
2218
+ async batchInsert({ tableName, records }) {
2219
+ try {
2220
+ await this.client.query("BEGIN");
2221
+ for (const record of records) {
2222
+ await this.insert({ tableName, record });
2223
+ }
2224
+ await this.client.query("COMMIT");
2225
+ } catch (error$1) {
2226
+ await this.client.query("ROLLBACK");
2227
+ throw new error.MastraError(
2228
+ {
2229
+ id: "MASTRA_STORAGE_PG_STORE_BATCH_INSERT_FAILED",
2230
+ domain: error.ErrorDomain.STORAGE,
2231
+ category: error.ErrorCategory.THIRD_PARTY,
2232
+ details: {
2233
+ tableName,
2234
+ numberOfRecords: records.length
2235
+ }
2236
+ },
2237
+ error$1
2238
+ );
2019
2239
  }
2240
+ }
2241
+ async dropTable({ tableName }) {
2020
2242
  try {
2021
- await this.db.tx(async (t) => {
2022
- const messageInserts = messages.map((message) => {
2023
- if (!message.threadId) {
2024
- throw new Error(
2025
- `Expected to find a threadId for message, but couldn't find one. An unexpected error has occurred.`
2026
- );
2027
- }
2028
- if (!message.resourceId) {
2029
- throw new Error(
2030
- `Expected to find a resourceId for message, but couldn't find one. An unexpected error has occurred.`
2031
- );
2032
- }
2033
- return t.none(
2034
- `INSERT INTO ${this.getTableName(storage.TABLE_MESSAGES)} (id, thread_id, content, "createdAt", role, type, "resourceId")
2035
- VALUES ($1, $2, $3, $4, $5, $6, $7)
2036
- ON CONFLICT (id) DO UPDATE SET
2037
- thread_id = EXCLUDED.thread_id,
2038
- content = EXCLUDED.content,
2039
- role = EXCLUDED.role,
2040
- type = EXCLUDED.type,
2041
- "resourceId" = EXCLUDED."resourceId"`,
2042
- [
2043
- message.id,
2044
- message.threadId,
2045
- typeof message.content === "string" ? message.content : JSON.stringify(message.content),
2046
- message.createdAt || (/* @__PURE__ */ new Date()).toISOString(),
2047
- message.role,
2048
- message.type || "v2",
2049
- message.resourceId
2050
- ]
2051
- );
2052
- });
2053
- const threadUpdate = t.none(
2054
- `UPDATE ${this.getTableName(storage.TABLE_THREADS)}
2055
- SET "updatedAt" = $1
2056
- WHERE id = $2`,
2057
- [(/* @__PURE__ */ new Date()).toISOString(), threadId]
2058
- );
2059
- await Promise.all([...messageInserts, threadUpdate]);
2060
- });
2061
- const messagesWithParsedContent = messages.map((message) => {
2062
- if (typeof message.content === "string") {
2063
- try {
2064
- return { ...message, content: JSON.parse(message.content) };
2065
- } catch {
2066
- return message;
2067
- }
2068
- }
2069
- return message;
2070
- });
2071
- const list = new agent.MessageList().add(messagesWithParsedContent, "memory");
2072
- if (format === `v2`) return list.get.all.v2();
2073
- return list.get.all.v1();
2074
- } catch (error$1) {
2075
- throw new error.MastraError(
2076
- {
2077
- id: "MASTRA_STORAGE_PG_STORE_SAVE_MESSAGES_FAILED",
2078
- domain: error.ErrorDomain.STORAGE,
2079
- category: error.ErrorCategory.THIRD_PARTY,
2080
- details: {
2081
- threadId
2243
+ const schemaName = getSchemaName(this.schemaName);
2244
+ const tableNameWithSchema = getTableName({ indexName: tableName, schemaName });
2245
+ await this.client.none(`DROP TABLE IF EXISTS ${tableNameWithSchema}`);
2246
+ } catch (error$1) {
2247
+ throw new error.MastraError(
2248
+ {
2249
+ id: "MASTRA_STORAGE_PG_STORE_DROP_TABLE_FAILED",
2250
+ domain: error.ErrorDomain.STORAGE,
2251
+ category: error.ErrorCategory.THIRD_PARTY,
2252
+ details: {
2253
+ tableName
2082
2254
  }
2083
2255
  },
2084
2256
  error$1
2085
2257
  );
2086
2258
  }
2087
2259
  }
2088
- async persistWorkflowSnapshot({
2089
- workflowName,
2090
- runId,
2091
- snapshot
2092
- }) {
2260
+ };
2261
+ function transformScoreRow(row) {
2262
+ let input = void 0;
2263
+ if (row.input) {
2093
2264
  try {
2094
- const now = (/* @__PURE__ */ new Date()).toISOString();
2095
- await this.db.none(
2096
- `INSERT INTO ${this.getTableName(storage.TABLE_WORKFLOW_SNAPSHOT)} (
2097
- workflow_name,
2098
- run_id,
2099
- snapshot,
2100
- "createdAt",
2101
- "updatedAt"
2102
- ) VALUES ($1, $2, $3, $4, $5)
2103
- ON CONFLICT (workflow_name, run_id) DO UPDATE
2104
- SET snapshot = EXCLUDED.snapshot,
2105
- "updatedAt" = EXCLUDED."updatedAt"`,
2106
- [workflowName, runId, JSON.stringify(snapshot), now, now]
2107
- );
2265
+ input = JSON.parse(row.input);
2266
+ } catch {
2267
+ input = row.input;
2268
+ }
2269
+ }
2270
+ return {
2271
+ ...row,
2272
+ input,
2273
+ createdAt: row.createdAtZ || row.createdAt,
2274
+ updatedAt: row.updatedAtZ || row.updatedAt
2275
+ };
2276
+ }
2277
+ var ScoresPG = class extends storage.ScoresStorage {
2278
+ client;
2279
+ operations;
2280
+ constructor({ client, operations }) {
2281
+ super();
2282
+ this.client = client;
2283
+ this.operations = operations;
2284
+ }
2285
+ async getScoreById({ id }) {
2286
+ try {
2287
+ const result = await this.client.oneOrNone(`SELECT * FROM ${storage.TABLE_SCORERS} WHERE id = $1`, [id]);
2288
+ return transformScoreRow(result);
2108
2289
  } catch (error$1) {
2109
2290
  throw new error.MastraError(
2110
2291
  {
2111
- id: "MASTRA_STORAGE_PG_STORE_PERSIST_WORKFLOW_SNAPSHOT_FAILED",
2292
+ id: "MASTRA_STORAGE_PG_STORE_GET_SCORE_BY_ID_FAILED",
2112
2293
  domain: error.ErrorDomain.STORAGE,
2113
- category: error.ErrorCategory.THIRD_PARTY,
2114
- details: {
2115
- workflowName,
2116
- runId
2117
- }
2294
+ category: error.ErrorCategory.THIRD_PARTY
2118
2295
  },
2119
2296
  error$1
2120
2297
  );
2121
2298
  }
2122
2299
  }
2123
- async loadWorkflowSnapshot({
2124
- workflowName,
2125
- runId
2300
+ async getScoresByScorerId({
2301
+ scorerId,
2302
+ pagination
2126
2303
  }) {
2127
2304
  try {
2128
- const result = await this.load({
2129
- tableName: storage.TABLE_WORKFLOW_SNAPSHOT,
2130
- keys: {
2131
- workflow_name: workflowName,
2132
- run_id: runId
2133
- }
2134
- });
2135
- if (!result) {
2136
- return null;
2305
+ const total = await this.client.oneOrNone(
2306
+ `SELECT COUNT(*) FROM ${storage.TABLE_SCORERS} WHERE "scorerId" = $1`,
2307
+ [scorerId]
2308
+ );
2309
+ if (total?.count === "0" || !total?.count) {
2310
+ return {
2311
+ pagination: {
2312
+ total: 0,
2313
+ page: pagination.page,
2314
+ perPage: pagination.perPage,
2315
+ hasMore: false
2316
+ },
2317
+ scores: []
2318
+ };
2137
2319
  }
2138
- return result.snapshot;
2320
+ const result = await this.client.manyOrNone(
2321
+ `SELECT * FROM ${storage.TABLE_SCORERS} WHERE "scorerId" = $1 LIMIT $2 OFFSET $3`,
2322
+ [scorerId, pagination.perPage, pagination.page * pagination.perPage]
2323
+ );
2324
+ return {
2325
+ pagination: {
2326
+ total: Number(total?.count) || 0,
2327
+ page: pagination.page,
2328
+ perPage: pagination.perPage,
2329
+ hasMore: Number(total?.count) > (pagination.page + 1) * pagination.perPage
2330
+ },
2331
+ scores: result
2332
+ };
2139
2333
  } catch (error$1) {
2140
2334
  throw new error.MastraError(
2141
2335
  {
2142
- id: "MASTRA_STORAGE_PG_STORE_LOAD_WORKFLOW_SNAPSHOT_FAILED",
2336
+ id: "MASTRA_STORAGE_PG_STORE_GET_SCORES_BY_SCORER_ID_FAILED",
2143
2337
  domain: error.ErrorDomain.STORAGE,
2144
- category: error.ErrorCategory.THIRD_PARTY,
2145
- details: {
2146
- workflowName,
2147
- runId
2148
- }
2338
+ category: error.ErrorCategory.THIRD_PARTY
2149
2339
  },
2150
2340
  error$1
2151
2341
  );
2152
2342
  }
2153
2343
  }
2154
- async hasColumn(table, column) {
2155
- const schema = this.schema || "public";
2156
- const result = await this.db.oneOrNone(
2157
- `SELECT 1 FROM information_schema.columns WHERE table_schema = $1 AND table_name = $2 AND (column_name = $3 OR column_name = $4)`,
2158
- [schema, table, column, column.toLowerCase()]
2159
- );
2160
- return !!result;
2161
- }
2162
- parseWorkflowRun(row) {
2163
- let parsedSnapshot = row.snapshot;
2164
- if (typeof parsedSnapshot === "string") {
2165
- try {
2166
- parsedSnapshot = JSON.parse(row.snapshot);
2167
- } catch (e) {
2168
- console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
2169
- }
2170
- }
2171
- return {
2172
- workflowName: row.workflow_name,
2173
- runId: row.run_id,
2174
- snapshot: parsedSnapshot,
2175
- createdAt: row.createdAt,
2176
- updatedAt: row.updatedAt,
2177
- resourceId: row.resourceId
2178
- };
2179
- }
2180
- async getWorkflowRuns({
2181
- workflowName,
2182
- fromDate,
2183
- toDate,
2184
- limit,
2185
- offset,
2186
- resourceId
2187
- } = {}) {
2344
+ async saveScore(score) {
2188
2345
  try {
2189
- const conditions = [];
2190
- const values = [];
2191
- let paramIndex = 1;
2192
- if (workflowName) {
2193
- conditions.push(`workflow_name = $${paramIndex}`);
2194
- values.push(workflowName);
2195
- paramIndex++;
2196
- }
2197
- if (resourceId) {
2198
- const hasResourceId = await this.hasColumn(storage.TABLE_WORKFLOW_SNAPSHOT, "resourceId");
2199
- if (hasResourceId) {
2200
- conditions.push(`"resourceId" = $${paramIndex}`);
2201
- values.push(resourceId);
2202
- paramIndex++;
2203
- } else {
2204
- console.warn(`[${storage.TABLE_WORKFLOW_SNAPSHOT}] resourceId column not found. Skipping resourceId filter.`);
2346
+ const { input, ...rest } = score;
2347
+ await this.operations.insert({
2348
+ tableName: storage.TABLE_SCORERS,
2349
+ record: {
2350
+ ...rest,
2351
+ input: JSON.stringify(input),
2352
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2353
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2205
2354
  }
2206
- }
2207
- if (fromDate) {
2208
- conditions.push(`"createdAt" >= $${paramIndex}`);
2209
- values.push(fromDate);
2210
- paramIndex++;
2211
- }
2212
- if (toDate) {
2213
- conditions.push(`"createdAt" <= $${paramIndex}`);
2214
- values.push(toDate);
2215
- paramIndex++;
2216
- }
2217
- const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2218
- let total = 0;
2219
- if (limit !== void 0 && offset !== void 0) {
2220
- const countResult = await this.db.one(
2221
- `SELECT COUNT(*) as count FROM ${this.getTableName(storage.TABLE_WORKFLOW_SNAPSHOT)} ${whereClause}`,
2222
- values
2223
- );
2224
- total = Number(countResult.count);
2225
- }
2226
- const query = `
2227
- SELECT * FROM ${this.getTableName(storage.TABLE_WORKFLOW_SNAPSHOT)}
2228
- ${whereClause}
2229
- ORDER BY "createdAt" DESC
2230
- ${limit !== void 0 && offset !== void 0 ? ` LIMIT $${paramIndex} OFFSET $${paramIndex + 1}` : ""}
2231
- `;
2232
- const queryValues = limit !== void 0 && offset !== void 0 ? [...values, limit, offset] : values;
2233
- const result = await this.db.manyOrNone(query, queryValues);
2234
- const runs = (result || []).map((row) => {
2235
- return this.parseWorkflowRun(row);
2236
2355
  });
2237
- return { runs, total: total || runs.length };
2356
+ const scoreFromDb = await this.getScoreById({ id: score.id });
2357
+ return { score: scoreFromDb };
2238
2358
  } catch (error$1) {
2239
2359
  throw new error.MastraError(
2240
2360
  {
2241
- id: "MASTRA_STORAGE_PG_STORE_GET_WORKFLOW_RUNS_FAILED",
2361
+ id: "MASTRA_STORAGE_PG_STORE_SAVE_SCORE_FAILED",
2242
2362
  domain: error.ErrorDomain.STORAGE,
2243
- category: error.ErrorCategory.THIRD_PARTY,
2244
- details: {
2245
- workflowName: workflowName || "all"
2246
- }
2363
+ category: error.ErrorCategory.THIRD_PARTY
2247
2364
  },
2248
2365
  error$1
2249
2366
  );
2250
2367
  }
2251
2368
  }
2252
- async getWorkflowRunById({
2369
+ async getScoresByRunId({
2253
2370
  runId,
2254
- workflowName
2371
+ pagination
2255
2372
  }) {
2256
2373
  try {
2257
- const conditions = [];
2258
- const values = [];
2259
- let paramIndex = 1;
2260
- if (runId) {
2261
- conditions.push(`run_id = $${paramIndex}`);
2262
- values.push(runId);
2263
- paramIndex++;
2264
- }
2265
- if (workflowName) {
2266
- conditions.push(`workflow_name = $${paramIndex}`);
2267
- values.push(workflowName);
2268
- paramIndex++;
2269
- }
2270
- const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2271
- const query = `
2272
- SELECT * FROM ${this.getTableName(storage.TABLE_WORKFLOW_SNAPSHOT)}
2273
- ${whereClause}
2274
- `;
2275
- const queryValues = values;
2276
- const result = await this.db.oneOrNone(query, queryValues);
2277
- if (!result) {
2278
- return null;
2374
+ const total = await this.client.oneOrNone(
2375
+ `SELECT COUNT(*) FROM ${storage.TABLE_SCORERS} WHERE "runId" = $1`,
2376
+ [runId]
2377
+ );
2378
+ console.log(`total: ${total?.count}`);
2379
+ console.log(`typeof total: ${typeof total?.count}`);
2380
+ if (total?.count === "0" || !total?.count) {
2381
+ return {
2382
+ pagination: {
2383
+ total: 0,
2384
+ page: pagination.page,
2385
+ perPage: pagination.perPage,
2386
+ hasMore: false
2387
+ },
2388
+ scores: []
2389
+ };
2279
2390
  }
2280
- return this.parseWorkflowRun(result);
2391
+ const result = await this.client.manyOrNone(
2392
+ `SELECT * FROM ${storage.TABLE_SCORERS} WHERE "runId" = $1 LIMIT $2 OFFSET $3`,
2393
+ [runId, pagination.perPage, pagination.page * pagination.perPage]
2394
+ );
2395
+ return {
2396
+ pagination: {
2397
+ total: Number(total?.count) || 0,
2398
+ page: pagination.page,
2399
+ perPage: pagination.perPage,
2400
+ hasMore: Number(total?.count) > (pagination.page + 1) * pagination.perPage
2401
+ },
2402
+ scores: result
2403
+ };
2281
2404
  } catch (error$1) {
2282
2405
  throw new error.MastraError(
2283
2406
  {
2284
- id: "MASTRA_STORAGE_PG_STORE_GET_WORKFLOW_RUN_BY_ID_FAILED",
2407
+ id: "MASTRA_STORAGE_PG_STORE_GET_SCORES_BY_RUN_ID_FAILED",
2285
2408
  domain: error.ErrorDomain.STORAGE,
2286
- category: error.ErrorCategory.THIRD_PARTY,
2287
- details: {
2288
- runId,
2289
- workflowName: workflowName || ""
2290
- }
2409
+ category: error.ErrorCategory.THIRD_PARTY
2291
2410
  },
2292
2411
  error$1
2293
2412
  );
2294
2413
  }
2295
2414
  }
2296
- async close() {
2297
- this.pgp.end();
2298
- }
2299
- async getEvals(options = {}) {
2300
- const { agentName, type, page = 0, perPage = 100, dateRange } = options;
2301
- const fromDate = dateRange?.start;
2302
- const toDate = dateRange?.end;
2303
- const conditions = [];
2304
- const queryParams = [];
2305
- let paramIndex = 1;
2306
- if (agentName) {
2307
- conditions.push(`agent_name = $${paramIndex++}`);
2308
- queryParams.push(agentName);
2309
- }
2310
- if (type === "test") {
2311
- conditions.push(`(test_info IS NOT NULL AND test_info->>'testPath' IS NOT NULL)`);
2312
- } else if (type === "live") {
2313
- conditions.push(`(test_info IS NULL OR test_info->>'testPath' IS NULL)`);
2314
- }
2315
- if (fromDate) {
2316
- conditions.push(`created_at >= $${paramIndex++}`);
2317
- queryParams.push(fromDate);
2318
- }
2319
- if (toDate) {
2320
- conditions.push(`created_at <= $${paramIndex++}`);
2321
- queryParams.push(toDate);
2322
- }
2323
- const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2324
- const countQuery = `SELECT COUNT(*) FROM ${this.getTableName(storage.TABLE_EVALS)} ${whereClause}`;
2415
+ async getScoresByEntityId({
2416
+ entityId,
2417
+ entityType,
2418
+ pagination
2419
+ }) {
2325
2420
  try {
2326
- const countResult = await this.db.one(countQuery, queryParams);
2327
- const total = parseInt(countResult.count, 10);
2328
- const currentOffset = page * perPage;
2329
- if (total === 0) {
2421
+ const total = await this.client.oneOrNone(
2422
+ `SELECT COUNT(*) FROM ${storage.TABLE_SCORERS} WHERE "entityId" = $1 AND "entityType" = $2`,
2423
+ [entityId, entityType]
2424
+ );
2425
+ if (total?.count === "0" || !total?.count) {
2330
2426
  return {
2331
- evals: [],
2332
- total: 0,
2333
- page,
2334
- perPage,
2335
- hasMore: false
2427
+ pagination: {
2428
+ total: 0,
2429
+ page: pagination.page,
2430
+ perPage: pagination.perPage,
2431
+ hasMore: false
2432
+ },
2433
+ scores: []
2336
2434
  };
2337
2435
  }
2338
- const dataQuery = `SELECT * FROM ${this.getTableName(
2339
- storage.TABLE_EVALS
2340
- )} ${whereClause} ORDER BY created_at DESC LIMIT $${paramIndex++} OFFSET $${paramIndex++}`;
2341
- const rows = await this.db.manyOrNone(dataQuery, [...queryParams, perPage, currentOffset]);
2436
+ const result = await this.client.manyOrNone(
2437
+ `SELECT * FROM ${storage.TABLE_SCORERS} WHERE "entityId" = $1 AND "entityType" = $2 LIMIT $3 OFFSET $4`,
2438
+ [entityId, entityType, pagination.perPage, pagination.page * pagination.perPage]
2439
+ );
2342
2440
  return {
2343
- evals: rows?.map((row) => this.transformEvalRow(row)) ?? [],
2344
- total,
2345
- page,
2346
- perPage,
2347
- hasMore: currentOffset + (rows?.length ?? 0) < total
2441
+ pagination: {
2442
+ total: Number(total?.count) || 0,
2443
+ page: pagination.page,
2444
+ perPage: pagination.perPage,
2445
+ hasMore: Number(total?.count) > (pagination.page + 1) * pagination.perPage
2446
+ },
2447
+ scores: result
2348
2448
  };
2349
2449
  } catch (error$1) {
2350
- const mastraError = new error.MastraError(
2450
+ throw new error.MastraError(
2351
2451
  {
2352
- id: "MASTRA_STORAGE_PG_STORE_GET_EVALS_FAILED",
2452
+ id: "MASTRA_STORAGE_PG_STORE_GET_SCORES_BY_ENTITY_ID_FAILED",
2353
2453
  domain: error.ErrorDomain.STORAGE,
2354
- category: error.ErrorCategory.THIRD_PARTY,
2355
- details: {
2356
- agentName: agentName || "all",
2357
- type: type || "all",
2358
- page,
2359
- perPage
2360
- }
2454
+ category: error.ErrorCategory.THIRD_PARTY
2361
2455
  },
2362
2456
  error$1
2363
2457
  );
2364
- this.logger?.error?.(mastraError.toString());
2365
- this.logger?.trackException(mastraError);
2366
- throw mastraError;
2367
2458
  }
2368
2459
  }
2369
- async updateMessages({
2370
- messages
2460
+ };
2461
+ var TracesPG = class extends storage.TracesStorage {
2462
+ client;
2463
+ operations;
2464
+ schema;
2465
+ constructor({
2466
+ client,
2467
+ operations,
2468
+ schema
2371
2469
  }) {
2372
- if (messages.length === 0) {
2373
- return [];
2374
- }
2375
- const messageIds = messages.map((m) => m.id);
2376
- const selectQuery = `SELECT id, content, role, type, "createdAt", thread_id AS "threadId", "resourceId" FROM ${this.getTableName(
2377
- storage.TABLE_MESSAGES
2378
- )} WHERE id IN ($1:list)`;
2379
- const existingMessagesDb = await this.db.manyOrNone(selectQuery, [messageIds]);
2380
- if (existingMessagesDb.length === 0) {
2381
- return [];
2470
+ super();
2471
+ this.client = client;
2472
+ this.operations = operations;
2473
+ this.schema = schema;
2474
+ }
2475
+ async getTraces(args) {
2476
+ if (args.fromDate || args.toDate) {
2477
+ args.dateRange = {
2478
+ start: args.fromDate,
2479
+ end: args.toDate
2480
+ };
2382
2481
  }
2383
- const existingMessages = existingMessagesDb.map((msg) => {
2384
- if (typeof msg.content === "string") {
2385
- try {
2386
- msg.content = JSON.parse(msg.content);
2387
- } catch {
2388
- }
2482
+ try {
2483
+ const result = await this.getTracesPaginated(args);
2484
+ return result.traces;
2485
+ } catch (error$1) {
2486
+ throw new error.MastraError(
2487
+ {
2488
+ id: "MASTRA_STORAGE_PG_STORE_GET_TRACES_FAILED",
2489
+ domain: error.ErrorDomain.STORAGE,
2490
+ category: error.ErrorCategory.THIRD_PARTY
2491
+ },
2492
+ error$1
2493
+ );
2494
+ }
2495
+ }
2496
+ async getTracesPaginated(args) {
2497
+ const { name, scope, page = 0, perPage = 100, attributes, filters, dateRange } = args;
2498
+ const fromDate = dateRange?.start;
2499
+ const toDate = dateRange?.end;
2500
+ const currentOffset = page * perPage;
2501
+ const queryParams = [];
2502
+ const conditions = [];
2503
+ let paramIndex = 1;
2504
+ if (name) {
2505
+ conditions.push(`name LIKE $${paramIndex++}`);
2506
+ queryParams.push(`${name}%`);
2507
+ }
2508
+ if (scope) {
2509
+ conditions.push(`scope = $${paramIndex++}`);
2510
+ queryParams.push(scope);
2511
+ }
2512
+ if (attributes) {
2513
+ Object.entries(attributes).forEach(([key, value]) => {
2514
+ const parsedKey = utils.parseFieldKey(key);
2515
+ conditions.push(`attributes->>'${parsedKey}' = $${paramIndex++}`);
2516
+ queryParams.push(value);
2517
+ });
2518
+ }
2519
+ if (filters) {
2520
+ Object.entries(filters).forEach(([key, value]) => {
2521
+ const parsedKey = utils.parseFieldKey(key);
2522
+ conditions.push(`"${parsedKey}" = $${paramIndex++}`);
2523
+ queryParams.push(value);
2524
+ });
2525
+ }
2526
+ if (fromDate) {
2527
+ conditions.push(`"createdAt" >= $${paramIndex++}`);
2528
+ queryParams.push(fromDate);
2529
+ }
2530
+ if (toDate) {
2531
+ conditions.push(`"createdAt" <= $${paramIndex++}`);
2532
+ queryParams.push(toDate);
2533
+ }
2534
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2535
+ try {
2536
+ const countResult = await this.client.oneOrNone(
2537
+ `SELECT COUNT(*) FROM ${getTableName({ indexName: storage.TABLE_TRACES, schemaName: getSchemaName(this.schema) })} ${whereClause}`,
2538
+ queryParams
2539
+ );
2540
+ const total = Number(countResult?.count ?? 0);
2541
+ if (total === 0) {
2542
+ return {
2543
+ traces: [],
2544
+ total: 0,
2545
+ page,
2546
+ perPage,
2547
+ hasMore: false
2548
+ };
2389
2549
  }
2390
- return msg;
2391
- });
2392
- const threadIdsToUpdate = /* @__PURE__ */ new Set();
2393
- await this.db.tx(async (t) => {
2394
- const queries = [];
2395
- const columnMapping = {
2396
- threadId: "thread_id"
2550
+ const dataResult = await this.client.manyOrNone(
2551
+ `SELECT * FROM ${getTableName({ indexName: storage.TABLE_TRACES, schemaName: getSchemaName(this.schema) })} ${whereClause} ORDER BY "startTime" DESC LIMIT $${paramIndex++} OFFSET $${paramIndex++}`,
2552
+ [...queryParams, perPage, currentOffset]
2553
+ );
2554
+ const traces = dataResult.map((row) => ({
2555
+ id: row.id,
2556
+ parentSpanId: row.parentSpanId,
2557
+ traceId: row.traceId,
2558
+ name: row.name,
2559
+ scope: row.scope,
2560
+ kind: row.kind,
2561
+ status: storage.safelyParseJSON(row.status),
2562
+ events: storage.safelyParseJSON(row.events),
2563
+ links: storage.safelyParseJSON(row.links),
2564
+ attributes: storage.safelyParseJSON(row.attributes),
2565
+ startTime: row.startTime,
2566
+ endTime: row.endTime,
2567
+ other: storage.safelyParseJSON(row.other),
2568
+ createdAt: row.createdAtZ || row.createdAt
2569
+ }));
2570
+ return {
2571
+ traces,
2572
+ total,
2573
+ page,
2574
+ perPage,
2575
+ hasMore: currentOffset + traces.length < total
2397
2576
  };
2398
- for (const existingMessage of existingMessages) {
2399
- const updatePayload = messages.find((m) => m.id === existingMessage.id);
2400
- if (!updatePayload) continue;
2401
- const { id, ...fieldsToUpdate } = updatePayload;
2402
- if (Object.keys(fieldsToUpdate).length === 0) continue;
2403
- threadIdsToUpdate.add(existingMessage.threadId);
2404
- if (updatePayload.threadId && updatePayload.threadId !== existingMessage.threadId) {
2405
- threadIdsToUpdate.add(updatePayload.threadId);
2406
- }
2407
- const setClauses = [];
2408
- const values = [];
2409
- let paramIndex = 1;
2410
- const updatableFields = { ...fieldsToUpdate };
2411
- if (updatableFields.content) {
2412
- const newContent = {
2413
- ...existingMessage.content,
2414
- ...updatableFields.content,
2415
- // Deep merge metadata if it exists on both
2416
- ...existingMessage.content?.metadata && updatableFields.content.metadata ? {
2417
- metadata: {
2418
- ...existingMessage.content.metadata,
2419
- ...updatableFields.content.metadata
2420
- }
2421
- } : {}
2422
- };
2423
- setClauses.push(`content = $${paramIndex++}`);
2424
- values.push(newContent);
2425
- delete updatableFields.content;
2426
- }
2427
- for (const key in updatableFields) {
2428
- if (Object.prototype.hasOwnProperty.call(updatableFields, key)) {
2429
- const dbColumn = columnMapping[key] || key;
2430
- setClauses.push(`"${dbColumn}" = $${paramIndex++}`);
2431
- values.push(updatableFields[key]);
2577
+ } catch (error$1) {
2578
+ throw new error.MastraError(
2579
+ {
2580
+ id: "MASTRA_STORAGE_PG_STORE_GET_TRACES_PAGINATED_FAILED",
2581
+ domain: error.ErrorDomain.STORAGE,
2582
+ category: error.ErrorCategory.THIRD_PARTY
2583
+ },
2584
+ error$1
2585
+ );
2586
+ }
2587
+ }
2588
+ async batchTraceInsert({ records }) {
2589
+ this.logger.debug("Batch inserting traces", { count: records.length });
2590
+ await this.operations.batchInsert({
2591
+ tableName: storage.TABLE_TRACES,
2592
+ records
2593
+ });
2594
+ }
2595
+ };
2596
+ function parseWorkflowRun(row) {
2597
+ let parsedSnapshot = row.snapshot;
2598
+ if (typeof parsedSnapshot === "string") {
2599
+ try {
2600
+ parsedSnapshot = JSON.parse(row.snapshot);
2601
+ } catch (e) {
2602
+ console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
2603
+ }
2604
+ }
2605
+ return {
2606
+ workflowName: row.workflow_name,
2607
+ runId: row.run_id,
2608
+ snapshot: parsedSnapshot,
2609
+ resourceId: row.resourceId,
2610
+ createdAt: new Date(row.createdAtZ || row.createdAt),
2611
+ updatedAt: new Date(row.updatedAtZ || row.updatedAt)
2612
+ };
2613
+ }
2614
+ var WorkflowsPG = class extends storage.WorkflowsStorage {
2615
+ client;
2616
+ operations;
2617
+ schema;
2618
+ constructor({
2619
+ client,
2620
+ operations,
2621
+ schema
2622
+ }) {
2623
+ super();
2624
+ this.client = client;
2625
+ this.operations = operations;
2626
+ this.schema = schema;
2627
+ }
2628
+ async persistWorkflowSnapshot({
2629
+ workflowName,
2630
+ runId,
2631
+ snapshot
2632
+ }) {
2633
+ try {
2634
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2635
+ await this.client.none(
2636
+ `INSERT INTO ${storage.TABLE_WORKFLOW_SNAPSHOT} (workflow_name, run_id, snapshot, "createdAt", "updatedAt")
2637
+ VALUES ($1, $2, $3, $4, $5)
2638
+ ON CONFLICT (workflow_name, run_id) DO UPDATE
2639
+ SET snapshot = $3, "updatedAt" = $5`,
2640
+ [workflowName, runId, JSON.stringify(snapshot), now, now]
2641
+ );
2642
+ } catch (error$1) {
2643
+ throw new error.MastraError(
2644
+ {
2645
+ id: "MASTRA_STORAGE_PG_STORE_PERSIST_WORKFLOW_SNAPSHOT_FAILED",
2646
+ domain: error.ErrorDomain.STORAGE,
2647
+ category: error.ErrorCategory.THIRD_PARTY
2648
+ },
2649
+ error$1
2650
+ );
2651
+ }
2652
+ }
2653
+ async loadWorkflowSnapshot({
2654
+ workflowName,
2655
+ runId
2656
+ }) {
2657
+ try {
2658
+ const result = await this.operations.load({
2659
+ tableName: storage.TABLE_WORKFLOW_SNAPSHOT,
2660
+ keys: { workflow_name: workflowName, run_id: runId }
2661
+ });
2662
+ return result ? result.snapshot : null;
2663
+ } catch (error$1) {
2664
+ throw new error.MastraError(
2665
+ {
2666
+ id: "MASTRA_STORAGE_PG_STORE_LOAD_WORKFLOW_SNAPSHOT_FAILED",
2667
+ domain: error.ErrorDomain.STORAGE,
2668
+ category: error.ErrorCategory.THIRD_PARTY
2669
+ },
2670
+ error$1
2671
+ );
2672
+ }
2673
+ }
2674
+ async getWorkflowRunById({
2675
+ runId,
2676
+ workflowName
2677
+ }) {
2678
+ try {
2679
+ const conditions = [];
2680
+ const values = [];
2681
+ let paramIndex = 1;
2682
+ if (runId) {
2683
+ conditions.push(`run_id = $${paramIndex}`);
2684
+ values.push(runId);
2685
+ paramIndex++;
2686
+ }
2687
+ if (workflowName) {
2688
+ conditions.push(`workflow_name = $${paramIndex}`);
2689
+ values.push(workflowName);
2690
+ paramIndex++;
2691
+ }
2692
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2693
+ const query = `
2694
+ SELECT * FROM ${getTableName({ indexName: storage.TABLE_WORKFLOW_SNAPSHOT, schemaName: this.schema })}
2695
+ ${whereClause}
2696
+ `;
2697
+ const queryValues = values;
2698
+ const result = await this.client.oneOrNone(query, queryValues);
2699
+ if (!result) {
2700
+ return null;
2701
+ }
2702
+ return parseWorkflowRun(result);
2703
+ } catch (error$1) {
2704
+ throw new error.MastraError(
2705
+ {
2706
+ id: "MASTRA_STORAGE_PG_STORE_GET_WORKFLOW_RUN_BY_ID_FAILED",
2707
+ domain: error.ErrorDomain.STORAGE,
2708
+ category: error.ErrorCategory.THIRD_PARTY,
2709
+ details: {
2710
+ runId,
2711
+ workflowName: workflowName || ""
2432
2712
  }
2433
- }
2434
- if (setClauses.length > 0) {
2435
- values.push(id);
2436
- const sql = `UPDATE ${this.getTableName(
2437
- storage.TABLE_MESSAGES
2438
- )} SET ${setClauses.join(", ")} WHERE id = $${paramIndex}`;
2439
- queries.push(t.none(sql, values));
2713
+ },
2714
+ error$1
2715
+ );
2716
+ }
2717
+ }
2718
+ async getWorkflowRuns({
2719
+ workflowName,
2720
+ fromDate,
2721
+ toDate,
2722
+ limit,
2723
+ offset,
2724
+ resourceId
2725
+ } = {}) {
2726
+ try {
2727
+ const conditions = [];
2728
+ const values = [];
2729
+ let paramIndex = 1;
2730
+ if (workflowName) {
2731
+ conditions.push(`workflow_name = $${paramIndex}`);
2732
+ values.push(workflowName);
2733
+ paramIndex++;
2734
+ }
2735
+ if (resourceId) {
2736
+ const hasResourceId = await this.operations.hasColumn(storage.TABLE_WORKFLOW_SNAPSHOT, "resourceId");
2737
+ if (hasResourceId) {
2738
+ conditions.push(`"resourceId" = $${paramIndex}`);
2739
+ values.push(resourceId);
2740
+ paramIndex++;
2741
+ } else {
2742
+ console.warn(`[${storage.TABLE_WORKFLOW_SNAPSHOT}] resourceId column not found. Skipping resourceId filter.`);
2440
2743
  }
2441
2744
  }
2442
- if (threadIdsToUpdate.size > 0) {
2443
- queries.push(
2444
- t.none(`UPDATE ${this.getTableName(storage.TABLE_THREADS)} SET "updatedAt" = NOW() WHERE id IN ($1:list)`, [
2445
- Array.from(threadIdsToUpdate)
2446
- ])
2447
- );
2745
+ if (fromDate) {
2746
+ conditions.push(`"createdAt" >= $${paramIndex}`);
2747
+ values.push(fromDate);
2748
+ paramIndex++;
2448
2749
  }
2449
- if (queries.length > 0) {
2450
- await t.batch(queries);
2750
+ if (toDate) {
2751
+ conditions.push(`"createdAt" <= $${paramIndex}`);
2752
+ values.push(toDate);
2753
+ paramIndex++;
2451
2754
  }
2452
- });
2453
- const updatedMessages = await this.db.manyOrNone(selectQuery, [messageIds]);
2454
- return (updatedMessages || []).map((message) => {
2455
- if (typeof message.content === "string") {
2456
- try {
2457
- message.content = JSON.parse(message.content);
2458
- } catch {
2459
- }
2755
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2756
+ let total = 0;
2757
+ if (limit !== void 0 && offset !== void 0) {
2758
+ const countResult = await this.client.one(
2759
+ `SELECT COUNT(*) as count FROM ${getTableName({ indexName: storage.TABLE_WORKFLOW_SNAPSHOT, schemaName: this.schema })} ${whereClause}`,
2760
+ values
2761
+ );
2762
+ total = Number(countResult.count);
2460
2763
  }
2461
- return message;
2462
- });
2764
+ const query = `
2765
+ SELECT * FROM ${getTableName({ indexName: storage.TABLE_WORKFLOW_SNAPSHOT, schemaName: this.schema })}
2766
+ ${whereClause}
2767
+ ORDER BY "createdAt" DESC
2768
+ ${limit !== void 0 && offset !== void 0 ? ` LIMIT $${paramIndex} OFFSET $${paramIndex + 1}` : ""}
2769
+ `;
2770
+ const queryValues = limit !== void 0 && offset !== void 0 ? [...values, limit, offset] : values;
2771
+ const result = await this.client.manyOrNone(query, queryValues);
2772
+ const runs = (result || []).map((row) => {
2773
+ return parseWorkflowRun(row);
2774
+ });
2775
+ return { runs, total: total || runs.length };
2776
+ } catch (error$1) {
2777
+ throw new error.MastraError(
2778
+ {
2779
+ id: "MASTRA_STORAGE_PG_STORE_GET_WORKFLOW_RUNS_FAILED",
2780
+ domain: error.ErrorDomain.STORAGE,
2781
+ category: error.ErrorCategory.THIRD_PARTY,
2782
+ details: {
2783
+ workflowName: workflowName || "all"
2784
+ }
2785
+ },
2786
+ error$1
2787
+ );
2788
+ }
2463
2789
  }
2464
- async getResourceById({ resourceId }) {
2465
- const tableName = this.getTableName(storage.TABLE_RESOURCES);
2466
- const result = await this.db.oneOrNone(`SELECT * FROM ${tableName} WHERE id = $1`, [
2467
- resourceId
2468
- ]);
2469
- if (!result) {
2470
- return null;
2790
+ };
2791
+
2792
+ // src/storage/index.ts
2793
+ var PostgresStore = class extends storage.MastraStorage {
2794
+ db;
2795
+ pgp;
2796
+ client;
2797
+ schema;
2798
+ stores;
2799
+ constructor(config) {
2800
+ try {
2801
+ if ("connectionString" in config) {
2802
+ if (!config.connectionString || typeof config.connectionString !== "string" || config.connectionString.trim() === "") {
2803
+ throw new Error(
2804
+ "PostgresStore: connectionString must be provided and cannot be empty. Passing an empty string may cause fallback to local Postgres defaults."
2805
+ );
2806
+ }
2807
+ } else {
2808
+ const required = ["host", "database", "user", "password"];
2809
+ for (const key of required) {
2810
+ if (!(key in config) || typeof config[key] !== "string" || config[key].trim() === "") {
2811
+ throw new Error(
2812
+ `PostgresStore: ${key} must be provided and cannot be empty. Passing an empty string may cause fallback to local Postgres defaults.`
2813
+ );
2814
+ }
2815
+ }
2816
+ }
2817
+ super({ name: "PostgresStore" });
2818
+ this.pgp = pgPromise__default.default();
2819
+ this.schema = config.schemaName || "public";
2820
+ this.db = this.pgp(
2821
+ `connectionString` in config ? { connectionString: config.connectionString } : {
2822
+ host: config.host,
2823
+ port: config.port,
2824
+ database: config.database,
2825
+ user: config.user,
2826
+ password: config.password,
2827
+ ssl: config.ssl
2828
+ }
2829
+ );
2830
+ this.client = this.db;
2831
+ const operations = new StoreOperationsPG({ client: this.client, schemaName: this.schema });
2832
+ const scores = new ScoresPG({ client: this.client, operations });
2833
+ const traces = new TracesPG({ client: this.client, operations, schema: this.schema });
2834
+ const workflows = new WorkflowsPG({ client: this.client, operations, schema: this.schema });
2835
+ const legacyEvals = new LegacyEvalsPG({ client: this.client, schema: this.schema });
2836
+ const memory = new MemoryPG({ client: this.client, schema: this.schema, operations });
2837
+ this.stores = {
2838
+ operations,
2839
+ scores,
2840
+ traces,
2841
+ workflows,
2842
+ legacyEvals,
2843
+ memory
2844
+ };
2845
+ } catch (e) {
2846
+ throw new error.MastraError(
2847
+ {
2848
+ id: "MASTRA_STORAGE_PG_STORE_INITIALIZATION_FAILED",
2849
+ domain: error.ErrorDomain.STORAGE,
2850
+ category: error.ErrorCategory.USER
2851
+ },
2852
+ e
2853
+ );
2471
2854
  }
2855
+ }
2856
+ get supports() {
2472
2857
  return {
2473
- ...result,
2474
- // Ensure workingMemory is always returned as a string, regardless of automatic parsing
2475
- workingMemory: typeof result.workingMemory === "object" ? JSON.stringify(result.workingMemory) : result.workingMemory,
2476
- metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata
2858
+ selectByIncludeResourceScope: true,
2859
+ resourceWorkingMemory: true,
2860
+ hasColumn: true,
2861
+ createTable: true
2477
2862
  };
2478
2863
  }
2864
+ /** @deprecated use getEvals instead */
2865
+ async getEvalsByAgentName(agentName, type) {
2866
+ return this.stores.legacyEvals.getEvalsByAgentName(agentName, type);
2867
+ }
2868
+ async getEvals(options = {}) {
2869
+ return this.stores.legacyEvals.getEvals(options);
2870
+ }
2871
+ /**
2872
+ * @deprecated use getTracesPaginated instead
2873
+ */
2874
+ async getTraces(args) {
2875
+ return this.stores.traces.getTraces(args);
2876
+ }
2877
+ async getTracesPaginated(args) {
2878
+ return this.stores.traces.getTracesPaginated(args);
2879
+ }
2880
+ async batchTraceInsert({ records }) {
2881
+ return this.stores.traces.batchTraceInsert({ records });
2882
+ }
2883
+ async createTable({
2884
+ tableName,
2885
+ schema
2886
+ }) {
2887
+ return this.stores.operations.createTable({ tableName, schema });
2888
+ }
2889
+ async alterTable({
2890
+ tableName,
2891
+ schema,
2892
+ ifNotExists
2893
+ }) {
2894
+ return this.stores.operations.alterTable({ tableName, schema, ifNotExists });
2895
+ }
2896
+ async clearTable({ tableName }) {
2897
+ return this.stores.operations.clearTable({ tableName });
2898
+ }
2899
+ async dropTable({ tableName }) {
2900
+ return this.stores.operations.dropTable({ tableName });
2901
+ }
2902
+ async insert({ tableName, record }) {
2903
+ return this.stores.operations.insert({ tableName, record });
2904
+ }
2905
+ async batchInsert({ tableName, records }) {
2906
+ return this.stores.operations.batchInsert({ tableName, records });
2907
+ }
2908
+ async load({ tableName, keys }) {
2909
+ return this.stores.operations.load({ tableName, keys });
2910
+ }
2911
+ /**
2912
+ * Memory
2913
+ */
2914
+ async getThreadById({ threadId }) {
2915
+ return this.stores.memory.getThreadById({ threadId });
2916
+ }
2917
+ /**
2918
+ * @deprecated use getThreadsByResourceIdPaginated instead
2919
+ */
2920
+ async getThreadsByResourceId(args) {
2921
+ return this.stores.memory.getThreadsByResourceId(args);
2922
+ }
2923
+ async getThreadsByResourceIdPaginated(args) {
2924
+ return this.stores.memory.getThreadsByResourceIdPaginated(args);
2925
+ }
2926
+ async saveThread({ thread }) {
2927
+ return this.stores.memory.saveThread({ thread });
2928
+ }
2929
+ async updateThread({
2930
+ id,
2931
+ title,
2932
+ metadata
2933
+ }) {
2934
+ return this.stores.memory.updateThread({ id, title, metadata });
2935
+ }
2936
+ async deleteThread({ threadId }) {
2937
+ return this.stores.memory.deleteThread({ threadId });
2938
+ }
2939
+ async getMessages(args) {
2940
+ return this.stores.memory.getMessages(args);
2941
+ }
2942
+ async getMessagesPaginated(args) {
2943
+ return this.stores.memory.getMessagesPaginated(args);
2944
+ }
2945
+ async saveMessages(args) {
2946
+ return this.stores.memory.saveMessages(args);
2947
+ }
2948
+ async updateMessages({
2949
+ messages
2950
+ }) {
2951
+ return this.stores.memory.updateMessages({ messages });
2952
+ }
2953
+ async getResourceById({ resourceId }) {
2954
+ return this.stores.memory.getResourceById({ resourceId });
2955
+ }
2479
2956
  async saveResource({ resource }) {
2480
- const tableName = this.getTableName(storage.TABLE_RESOURCES);
2481
- await this.db.none(
2482
- `INSERT INTO ${tableName} (id, "workingMemory", metadata, "createdAt", "updatedAt")
2483
- VALUES ($1, $2, $3, $4, $5)`,
2484
- [
2485
- resource.id,
2486
- resource.workingMemory,
2487
- JSON.stringify(resource.metadata),
2488
- resource.createdAt.toISOString(),
2489
- resource.updatedAt.toISOString()
2490
- ]
2491
- );
2492
- return resource;
2957
+ return this.stores.memory.saveResource({ resource });
2493
2958
  }
2494
2959
  async updateResource({
2495
2960
  resourceId,
2496
2961
  workingMemory,
2497
2962
  metadata
2498
2963
  }) {
2499
- const existingResource = await this.getResourceById({ resourceId });
2500
- if (!existingResource) {
2501
- const newResource = {
2502
- id: resourceId,
2503
- workingMemory,
2504
- metadata: metadata || {},
2505
- createdAt: /* @__PURE__ */ new Date(),
2506
- updatedAt: /* @__PURE__ */ new Date()
2507
- };
2508
- return this.saveResource({ resource: newResource });
2509
- }
2510
- const updatedResource = {
2511
- ...existingResource,
2512
- workingMemory: workingMemory !== void 0 ? workingMemory : existingResource.workingMemory,
2513
- metadata: {
2514
- ...existingResource.metadata,
2515
- ...metadata
2516
- },
2517
- updatedAt: /* @__PURE__ */ new Date()
2518
- };
2519
- const tableName = this.getTableName(storage.TABLE_RESOURCES);
2520
- const updates = [];
2521
- const values = [];
2522
- let paramIndex = 1;
2523
- if (workingMemory !== void 0) {
2524
- updates.push(`"workingMemory" = $${paramIndex}`);
2525
- values.push(workingMemory);
2526
- paramIndex++;
2527
- }
2528
- if (metadata) {
2529
- updates.push(`metadata = $${paramIndex}`);
2530
- values.push(JSON.stringify(updatedResource.metadata));
2531
- paramIndex++;
2532
- }
2533
- updates.push(`"updatedAt" = $${paramIndex}`);
2534
- values.push(updatedResource.updatedAt.toISOString());
2535
- paramIndex++;
2536
- values.push(resourceId);
2537
- await this.db.none(`UPDATE ${tableName} SET ${updates.join(", ")} WHERE id = $${paramIndex}`, values);
2538
- return updatedResource;
2964
+ return this.stores.memory.updateResource({ resourceId, workingMemory, metadata });
2965
+ }
2966
+ /**
2967
+ * Workflows
2968
+ */
2969
+ async persistWorkflowSnapshot({
2970
+ workflowName,
2971
+ runId,
2972
+ snapshot
2973
+ }) {
2974
+ return this.stores.workflows.persistWorkflowSnapshot({ workflowName, runId, snapshot });
2975
+ }
2976
+ async loadWorkflowSnapshot({
2977
+ workflowName,
2978
+ runId
2979
+ }) {
2980
+ return this.stores.workflows.loadWorkflowSnapshot({ workflowName, runId });
2981
+ }
2982
+ async getWorkflowRuns({
2983
+ workflowName,
2984
+ fromDate,
2985
+ toDate,
2986
+ limit,
2987
+ offset,
2988
+ resourceId
2989
+ } = {}) {
2990
+ return this.stores.workflows.getWorkflowRuns({ workflowName, fromDate, toDate, limit, offset, resourceId });
2991
+ }
2992
+ async getWorkflowRunById({
2993
+ runId,
2994
+ workflowName
2995
+ }) {
2996
+ return this.stores.workflows.getWorkflowRunById({ runId, workflowName });
2997
+ }
2998
+ async close() {
2999
+ this.pgp.end();
3000
+ }
3001
+ /**
3002
+ * Scorers
3003
+ */
3004
+ async getScoreById({ id: _id }) {
3005
+ return this.stores.scores.getScoreById({ id: _id });
3006
+ }
3007
+ async getScoresByScorerId({
3008
+ scorerId: _scorerId,
3009
+ pagination: _pagination
3010
+ }) {
3011
+ return this.stores.scores.getScoresByScorerId({ scorerId: _scorerId, pagination: _pagination });
3012
+ }
3013
+ async saveScore(_score) {
3014
+ return this.stores.scores.saveScore(_score);
3015
+ }
3016
+ async getScoresByRunId({
3017
+ runId: _runId,
3018
+ pagination: _pagination
3019
+ }) {
3020
+ return this.stores.scores.getScoresByRunId({ runId: _runId, pagination: _pagination });
3021
+ }
3022
+ async getScoresByEntityId({
3023
+ entityId: _entityId,
3024
+ entityType: _entityType,
3025
+ pagination: _pagination
3026
+ }) {
3027
+ return this.stores.scores.getScoresByEntityId({
3028
+ entityId: _entityId,
3029
+ entityType: _entityType,
3030
+ pagination: _pagination
3031
+ });
2539
3032
  }
2540
3033
  };
2541
3034