@mastra/libsql 0.11.0 → 0.11.1-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.js CHANGED
@@ -3,8 +3,9 @@ import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
3
3
  import { parseSqlIdentifier, parseFieldKey } from '@mastra/core/utils';
4
4
  import { MastraVector } from '@mastra/core/vector';
5
5
  import { BaseFilterTranslator } from '@mastra/core/vector/filter';
6
+ import { MastraStorage, StoreOperations, TABLE_WORKFLOW_SNAPSHOT, ScoresStorage, TABLE_SCORERS, TracesStorage, TABLE_TRACES, safelyParseJSON, WorkflowsStorage, MemoryStorage, resolveMessageLimit, TABLE_MESSAGES, TABLE_THREADS, TABLE_RESOURCES, LegacyEvalsStorage, TABLE_EVALS } from '@mastra/core/storage';
7
+ import { parseSqlIdentifier as parseSqlIdentifier$1 } from '@mastra/core';
6
8
  import { MessageList } from '@mastra/core/agent';
7
- import { MastraStorage, TABLE_WORKFLOW_SNAPSHOT, TABLE_THREADS, TABLE_MESSAGES, TABLE_EVALS, TABLE_TRACES, TABLE_RESOURCES } from '@mastra/core/storage';
8
9
 
9
10
  // src/vector/index.ts
10
11
  var LibSQLFilterTranslator = class extends BaseFilterTranslator {
@@ -904,256 +905,542 @@ var LibSQLVector = class extends MastraVector {
904
905
  });
905
906
  }
906
907
  };
907
- function safelyParseJSON(jsonString) {
908
- try {
909
- return JSON.parse(jsonString);
910
- } catch {
911
- return {};
908
+ function transformEvalRow(row) {
909
+ const resultValue = JSON.parse(row.result);
910
+ const testInfoValue = row.test_info ? JSON.parse(row.test_info) : void 0;
911
+ if (!resultValue || typeof resultValue !== "object" || !("score" in resultValue)) {
912
+ throw new Error(`Invalid MetricResult format: ${JSON.stringify(resultValue)}`);
912
913
  }
914
+ return {
915
+ input: row.input,
916
+ output: row.output,
917
+ result: resultValue,
918
+ agentName: row.agent_name,
919
+ metricName: row.metric_name,
920
+ instructions: row.instructions,
921
+ testInfo: testInfoValue,
922
+ globalRunId: row.global_run_id,
923
+ runId: row.run_id,
924
+ createdAt: row.created_at
925
+ };
913
926
  }
914
- var LibSQLStore = class extends MastraStorage {
927
+ var LegacyEvalsLibSQL = class extends LegacyEvalsStorage {
915
928
  client;
916
- maxRetries;
917
- initialBackoffMs;
918
- constructor(config) {
919
- super({ name: `LibSQLStore` });
920
- this.maxRetries = config.maxRetries ?? 5;
921
- this.initialBackoffMs = config.initialBackoffMs ?? 100;
922
- if (config.url.endsWith(":memory:")) {
923
- this.shouldCacheInit = false;
924
- }
925
- this.client = createClient(config);
926
- if (config.url.startsWith("file:") || config.url.includes(":memory:")) {
927
- this.client.execute("PRAGMA journal_mode=WAL;").then(() => this.logger.debug("LibSQLStore: PRAGMA journal_mode=WAL set.")).catch((err) => this.logger.warn("LibSQLStore: Failed to set PRAGMA journal_mode=WAL.", err));
928
- this.client.execute("PRAGMA busy_timeout = 5000;").then(() => this.logger.debug("LibSQLStore: PRAGMA busy_timeout=5000 set.")).catch((err) => this.logger.warn("LibSQLStore: Failed to set PRAGMA busy_timeout.", err));
929
- }
930
- }
931
- get supports() {
932
- return {
933
- selectByIncludeResourceScope: true,
934
- resourceWorkingMemory: true
935
- };
929
+ constructor({ client }) {
930
+ super();
931
+ this.client = client;
936
932
  }
937
- getCreateTableSQL(tableName, schema) {
938
- const parsedTableName = parseSqlIdentifier(tableName, "table name");
939
- const columns = Object.entries(schema).map(([name, col]) => {
940
- const parsedColumnName = parseSqlIdentifier(name, "column name");
941
- let type = col.type.toUpperCase();
942
- if (type === "TEXT") type = "TEXT";
943
- if (type === "TIMESTAMP") type = "TEXT";
944
- const nullable = col.nullable ? "" : "NOT NULL";
945
- const primaryKey = col.primaryKey ? "PRIMARY KEY" : "";
946
- return `${parsedColumnName} ${type} ${nullable} ${primaryKey}`.trim();
947
- });
948
- if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
949
- const stmnt = `CREATE TABLE IF NOT EXISTS ${parsedTableName} (
950
- ${columns.join(",\n")},
951
- PRIMARY KEY (workflow_name, run_id)
952
- )`;
953
- return stmnt;
933
+ /** @deprecated use getEvals instead */
934
+ async getEvalsByAgentName(agentName, type) {
935
+ try {
936
+ const baseQuery = `SELECT * FROM ${TABLE_EVALS} WHERE agent_name = ?`;
937
+ 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)" : "";
938
+ const result = await this.client.execute({
939
+ sql: `${baseQuery}${typeCondition} ORDER BY created_at DESC`,
940
+ args: [agentName]
941
+ });
942
+ return result.rows?.map((row) => transformEvalRow(row)) ?? [];
943
+ } catch (error) {
944
+ if (error instanceof Error && error.message.includes("no such table")) {
945
+ return [];
946
+ }
947
+ throw new MastraError(
948
+ {
949
+ id: "LIBSQL_STORE_GET_EVALS_BY_AGENT_NAME_FAILED",
950
+ domain: ErrorDomain.STORAGE,
951
+ category: ErrorCategory.THIRD_PARTY,
952
+ details: { agentName }
953
+ },
954
+ error
955
+ );
954
956
  }
955
- return `CREATE TABLE IF NOT EXISTS ${parsedTableName} (${columns.join(", ")})`;
956
957
  }
957
- async createTable({
958
- tableName,
959
- schema
960
- }) {
958
+ async getEvals(options = {}) {
959
+ const { agentName, type, page = 0, perPage = 100, dateRange } = options;
960
+ const fromDate = dateRange?.start;
961
+ const toDate = dateRange?.end;
962
+ const conditions = [];
963
+ const queryParams = [];
964
+ if (agentName) {
965
+ conditions.push(`agent_name = ?`);
966
+ queryParams.push(agentName);
967
+ }
968
+ if (type === "test") {
969
+ conditions.push(`(test_info IS NOT NULL AND json_extract(test_info, '$.testPath') IS NOT NULL)`);
970
+ } else if (type === "live") {
971
+ conditions.push(`(test_info IS NULL OR json_extract(test_info, '$.testPath') IS NULL)`);
972
+ }
973
+ if (fromDate) {
974
+ conditions.push(`created_at >= ?`);
975
+ queryParams.push(fromDate.toISOString());
976
+ }
977
+ if (toDate) {
978
+ conditions.push(`created_at <= ?`);
979
+ queryParams.push(toDate.toISOString());
980
+ }
981
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
961
982
  try {
962
- this.logger.debug(`Creating database table`, { tableName, operation: "schema init" });
963
- const sql = this.getCreateTableSQL(tableName, schema);
964
- await this.client.execute(sql);
983
+ const countResult = await this.client.execute({
984
+ sql: `SELECT COUNT(*) as count FROM ${TABLE_EVALS} ${whereClause}`,
985
+ args: queryParams
986
+ });
987
+ const total = Number(countResult.rows?.[0]?.count ?? 0);
988
+ const currentOffset = page * perPage;
989
+ const hasMore = currentOffset + perPage < total;
990
+ if (total === 0) {
991
+ return {
992
+ evals: [],
993
+ total: 0,
994
+ page,
995
+ perPage,
996
+ hasMore: false
997
+ };
998
+ }
999
+ const dataResult = await this.client.execute({
1000
+ sql: `SELECT * FROM ${TABLE_EVALS} ${whereClause} ORDER BY created_at DESC LIMIT ? OFFSET ?`,
1001
+ args: [...queryParams, perPage, currentOffset]
1002
+ });
1003
+ return {
1004
+ evals: dataResult.rows?.map((row) => transformEvalRow(row)) ?? [],
1005
+ total,
1006
+ page,
1007
+ perPage,
1008
+ hasMore
1009
+ };
965
1010
  } catch (error) {
966
1011
  throw new MastraError(
967
1012
  {
968
- id: "LIBSQL_STORE_CREATE_TABLE_FAILED",
1013
+ id: "LIBSQL_STORE_GET_EVALS_FAILED",
969
1014
  domain: ErrorDomain.STORAGE,
970
- category: ErrorCategory.THIRD_PARTY,
971
- details: {
972
- tableName
973
- }
1015
+ category: ErrorCategory.THIRD_PARTY
974
1016
  },
975
1017
  error
976
1018
  );
977
1019
  }
978
1020
  }
979
- getSqlType(type) {
980
- switch (type) {
981
- case "bigint":
982
- return "INTEGER";
983
- // SQLite uses INTEGER for all integer sizes
984
- case "jsonb":
985
- return "TEXT";
986
- // Store JSON as TEXT in SQLite
987
- default:
988
- return super.getSqlType(type);
1021
+ };
1022
+ var MemoryLibSQL = class extends MemoryStorage {
1023
+ client;
1024
+ operations;
1025
+ constructor({ client, operations }) {
1026
+ super();
1027
+ this.client = client;
1028
+ this.operations = operations;
1029
+ }
1030
+ parseRow(row) {
1031
+ let content = row.content;
1032
+ try {
1033
+ content = JSON.parse(row.content);
1034
+ } catch {
989
1035
  }
1036
+ const result = {
1037
+ id: row.id,
1038
+ content,
1039
+ role: row.role,
1040
+ createdAt: new Date(row.createdAt),
1041
+ threadId: row.thread_id,
1042
+ resourceId: row.resourceId
1043
+ };
1044
+ if (row.type && row.type !== `v2`) result.type = row.type;
1045
+ return result;
990
1046
  }
991
- /**
992
- * Alters table schema to add columns if they don't exist
993
- * @param tableName Name of the table
994
- * @param schema Schema of the table
995
- * @param ifNotExists Array of column names to add if they don't exist
996
- */
997
- async alterTable({
998
- tableName,
999
- schema,
1000
- ifNotExists
1047
+ async _getIncludedMessages({
1048
+ threadId,
1049
+ selectBy
1050
+ }) {
1051
+ const include = selectBy?.include;
1052
+ if (!include) return null;
1053
+ const unionQueries = [];
1054
+ const params = [];
1055
+ for (const inc of include) {
1056
+ const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
1057
+ const searchId = inc.threadId || threadId;
1058
+ unionQueries.push(
1059
+ `
1060
+ SELECT * FROM (
1061
+ WITH numbered_messages AS (
1062
+ SELECT
1063
+ id, content, role, type, "createdAt", thread_id, "resourceId",
1064
+ ROW_NUMBER() OVER (ORDER BY "createdAt" ASC) as row_num
1065
+ FROM "${TABLE_MESSAGES}"
1066
+ WHERE thread_id = ?
1067
+ ),
1068
+ target_positions AS (
1069
+ SELECT row_num as target_pos
1070
+ FROM numbered_messages
1071
+ WHERE id = ?
1072
+ )
1073
+ SELECT DISTINCT m.*
1074
+ FROM numbered_messages m
1075
+ CROSS JOIN target_positions t
1076
+ WHERE m.row_num BETWEEN (t.target_pos - ?) AND (t.target_pos + ?)
1077
+ )
1078
+ `
1079
+ // Keep ASC for final sorting after fetching context
1080
+ );
1081
+ params.push(searchId, id, withPreviousMessages, withNextMessages);
1082
+ }
1083
+ const finalQuery = unionQueries.join(" UNION ALL ") + ' ORDER BY "createdAt" ASC';
1084
+ const includedResult = await this.client.execute({ sql: finalQuery, args: params });
1085
+ const includedRows = includedResult.rows?.map((row) => this.parseRow(row));
1086
+ const seen = /* @__PURE__ */ new Set();
1087
+ const dedupedRows = includedRows.filter((row) => {
1088
+ if (seen.has(row.id)) return false;
1089
+ seen.add(row.id);
1090
+ return true;
1091
+ });
1092
+ return dedupedRows;
1093
+ }
1094
+ async getMessages({
1095
+ threadId,
1096
+ selectBy,
1097
+ format
1001
1098
  }) {
1002
- const parsedTableName = parseSqlIdentifier(tableName, "table name");
1003
1099
  try {
1004
- const pragmaQuery = `PRAGMA table_info(${parsedTableName})`;
1005
- const result = await this.client.execute(pragmaQuery);
1006
- const existingColumnNames = new Set(result.rows.map((row) => row.name.toLowerCase()));
1007
- for (const columnName of ifNotExists) {
1008
- if (!existingColumnNames.has(columnName.toLowerCase()) && schema[columnName]) {
1009
- const columnDef = schema[columnName];
1010
- const sqlType = this.getSqlType(columnDef.type);
1011
- const nullable = columnDef.nullable === false ? "NOT NULL" : "";
1012
- const defaultValue = columnDef.nullable === false ? this.getDefaultValue(columnDef.type) : "";
1013
- const alterSql = `ALTER TABLE ${parsedTableName} ADD COLUMN "${columnName}" ${sqlType} ${nullable} ${defaultValue}`.trim();
1014
- await this.client.execute(alterSql);
1015
- this.logger?.debug?.(`Added column ${columnName} to table ${parsedTableName}`);
1100
+ const messages = [];
1101
+ const limit = resolveMessageLimit({ last: selectBy?.last, defaultLimit: 40 });
1102
+ if (selectBy?.include?.length) {
1103
+ const includeMessages = await this._getIncludedMessages({ threadId, selectBy });
1104
+ if (includeMessages) {
1105
+ messages.push(...includeMessages);
1016
1106
  }
1017
1107
  }
1108
+ const excludeIds = messages.map((m) => m.id);
1109
+ const remainingSql = `
1110
+ SELECT
1111
+ id,
1112
+ content,
1113
+ role,
1114
+ type,
1115
+ "createdAt",
1116
+ thread_id,
1117
+ "resourceId"
1118
+ FROM "${TABLE_MESSAGES}"
1119
+ WHERE thread_id = ?
1120
+ ${excludeIds.length ? `AND id NOT IN (${excludeIds.map(() => "?").join(", ")})` : ""}
1121
+ ORDER BY "createdAt" DESC
1122
+ LIMIT ?
1123
+ `;
1124
+ const remainingArgs = [threadId, ...excludeIds.length ? excludeIds : [], limit];
1125
+ const remainingResult = await this.client.execute({ sql: remainingSql, args: remainingArgs });
1126
+ if (remainingResult.rows) {
1127
+ messages.push(...remainingResult.rows.map((row) => this.parseRow(row)));
1128
+ }
1129
+ messages.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
1130
+ const list = new MessageList().add(messages, "memory");
1131
+ if (format === `v2`) return list.get.all.v2();
1132
+ return list.get.all.v1();
1018
1133
  } catch (error) {
1019
1134
  throw new MastraError(
1020
1135
  {
1021
- id: "LIBSQL_STORE_ALTER_TABLE_FAILED",
1136
+ id: "LIBSQL_STORE_GET_MESSAGES_FAILED",
1022
1137
  domain: ErrorDomain.STORAGE,
1023
1138
  category: ErrorCategory.THIRD_PARTY,
1024
- details: {
1025
- tableName
1026
- }
1139
+ details: { threadId }
1027
1140
  },
1028
1141
  error
1029
1142
  );
1030
1143
  }
1031
1144
  }
1032
- async clearTable({ tableName }) {
1033
- const parsedTableName = parseSqlIdentifier(tableName, "table name");
1145
+ async getMessagesPaginated(args) {
1146
+ const { threadId, format, selectBy } = args;
1147
+ const { page = 0, perPage: perPageInput, dateRange } = selectBy?.pagination || {};
1148
+ const perPage = perPageInput !== void 0 ? perPageInput : resolveMessageLimit({ last: selectBy?.last, defaultLimit: 40 });
1149
+ const fromDate = dateRange?.start;
1150
+ const toDate = dateRange?.end;
1151
+ const messages = [];
1152
+ if (selectBy?.include?.length) {
1153
+ try {
1154
+ const includeMessages = await this._getIncludedMessages({ threadId, selectBy });
1155
+ if (includeMessages) {
1156
+ messages.push(...includeMessages);
1157
+ }
1158
+ } catch (error) {
1159
+ throw new MastraError(
1160
+ {
1161
+ id: "LIBSQL_STORE_GET_MESSAGES_PAGINATED_GET_INCLUDE_MESSAGES_FAILED",
1162
+ domain: ErrorDomain.STORAGE,
1163
+ category: ErrorCategory.THIRD_PARTY,
1164
+ details: { threadId }
1165
+ },
1166
+ error
1167
+ );
1168
+ }
1169
+ }
1034
1170
  try {
1035
- await this.client.execute(`DELETE FROM ${parsedTableName}`);
1036
- } catch (e) {
1171
+ const currentOffset = page * perPage;
1172
+ const conditions = [`thread_id = ?`];
1173
+ const queryParams = [threadId];
1174
+ if (fromDate) {
1175
+ conditions.push(`"createdAt" >= ?`);
1176
+ queryParams.push(fromDate.toISOString());
1177
+ }
1178
+ if (toDate) {
1179
+ conditions.push(`"createdAt" <= ?`);
1180
+ queryParams.push(toDate.toISOString());
1181
+ }
1182
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1183
+ const countResult = await this.client.execute({
1184
+ sql: `SELECT COUNT(*) as count FROM ${TABLE_MESSAGES} ${whereClause}`,
1185
+ args: queryParams
1186
+ });
1187
+ const total = Number(countResult.rows?.[0]?.count ?? 0);
1188
+ if (total === 0 && messages.length === 0) {
1189
+ return {
1190
+ messages: [],
1191
+ total: 0,
1192
+ page,
1193
+ perPage,
1194
+ hasMore: false
1195
+ };
1196
+ }
1197
+ const excludeIds = messages.map((m) => m.id);
1198
+ const excludeIdsParam = excludeIds.map((_, idx) => `$${idx + queryParams.length + 1}`).join(", ");
1199
+ const dataResult = await this.client.execute({
1200
+ sql: `SELECT id, content, role, type, "createdAt", "resourceId", "thread_id" FROM ${TABLE_MESSAGES} ${whereClause} ${excludeIds.length ? `AND id NOT IN (${excludeIdsParam})` : ""} ORDER BY "createdAt" DESC LIMIT ? OFFSET ?`,
1201
+ args: [...queryParams, ...excludeIds, perPage, currentOffset]
1202
+ });
1203
+ messages.push(...(dataResult.rows || []).map((row) => this.parseRow(row)));
1204
+ const messagesToReturn = format === "v1" ? new MessageList().add(messages, "memory").get.all.v1() : new MessageList().add(messages, "memory").get.all.v2();
1205
+ return {
1206
+ messages: messagesToReturn,
1207
+ total,
1208
+ page,
1209
+ perPage,
1210
+ hasMore: currentOffset + messages.length < total
1211
+ };
1212
+ } catch (error) {
1037
1213
  const mastraError = new MastraError(
1038
1214
  {
1039
- id: "LIBSQL_STORE_CLEAR_TABLE_FAILED",
1215
+ id: "LIBSQL_STORE_GET_MESSAGES_PAGINATED_FAILED",
1040
1216
  domain: ErrorDomain.STORAGE,
1041
1217
  category: ErrorCategory.THIRD_PARTY,
1042
- details: {
1043
- tableName
1044
- }
1218
+ details: { threadId }
1045
1219
  },
1046
- e
1220
+ error
1047
1221
  );
1048
1222
  this.logger?.trackException?.(mastraError);
1049
1223
  this.logger?.error?.(mastraError.toString());
1224
+ return { messages: [], total: 0, page, perPage, hasMore: false };
1050
1225
  }
1051
1226
  }
1052
- prepareStatement({ tableName, record }) {
1053
- const parsedTableName = parseSqlIdentifier(tableName, "table name");
1054
- const columns = Object.keys(record).map((col) => parseSqlIdentifier(col, "column name"));
1055
- const values = Object.values(record).map((v) => {
1056
- if (typeof v === `undefined`) {
1057
- return null;
1058
- }
1059
- if (v instanceof Date) {
1060
- return v.toISOString();
1227
+ async saveMessages({
1228
+ messages,
1229
+ format
1230
+ }) {
1231
+ if (messages.length === 0) return messages;
1232
+ try {
1233
+ const threadId = messages[0]?.threadId;
1234
+ if (!threadId) {
1235
+ throw new Error("Thread ID is required");
1061
1236
  }
1062
- return typeof v === "object" ? JSON.stringify(v) : v;
1063
- });
1064
- const placeholders = values.map(() => "?").join(", ");
1065
- return {
1066
- sql: `INSERT OR REPLACE INTO ${parsedTableName} (${columns.join(", ")}) VALUES (${placeholders})`,
1067
- args: values
1068
- };
1069
- }
1070
- async executeWriteOperationWithRetry(operationFn, operationDescription) {
1071
- let retries = 0;
1072
- while (true) {
1073
- try {
1074
- return await operationFn();
1075
- } catch (error) {
1076
- if (error.message && (error.message.includes("SQLITE_BUSY") || error.message.includes("database is locked")) && retries < this.maxRetries) {
1077
- retries++;
1078
- const backoffTime = this.initialBackoffMs * Math.pow(2, retries - 1);
1079
- this.logger.warn(
1080
- `LibSQLStore: Encountered SQLITE_BUSY during ${operationDescription}. Retrying (${retries}/${this.maxRetries}) in ${backoffTime}ms...`
1237
+ const batchStatements = messages.map((message) => {
1238
+ const time = message.createdAt || /* @__PURE__ */ new Date();
1239
+ if (!message.threadId) {
1240
+ throw new Error(
1241
+ `Expected to find a threadId for message, but couldn't find one. An unexpected error has occurred.`
1081
1242
  );
1082
- await new Promise((resolve) => setTimeout(resolve, backoffTime));
1083
- } else {
1084
- this.logger.error(`LibSQLStore: Error during ${operationDescription} after ${retries} retries: ${error}`);
1085
- throw error;
1086
1243
  }
1087
- }
1244
+ if (!message.resourceId) {
1245
+ throw new Error(
1246
+ `Expected to find a resourceId for message, but couldn't find one. An unexpected error has occurred.`
1247
+ );
1248
+ }
1249
+ return {
1250
+ sql: `INSERT INTO ${TABLE_MESSAGES} (id, thread_id, content, role, type, createdAt, resourceId)
1251
+ VALUES (?, ?, ?, ?, ?, ?, ?)
1252
+ ON CONFLICT(id) DO UPDATE SET
1253
+ thread_id=excluded.thread_id,
1254
+ content=excluded.content,
1255
+ role=excluded.role,
1256
+ type=excluded.type,
1257
+ resourceId=excluded.resourceId
1258
+ `,
1259
+ args: [
1260
+ message.id,
1261
+ message.threadId,
1262
+ typeof message.content === "object" ? JSON.stringify(message.content) : message.content,
1263
+ message.role,
1264
+ message.type || "v2",
1265
+ time instanceof Date ? time.toISOString() : time,
1266
+ message.resourceId
1267
+ ]
1268
+ };
1269
+ });
1270
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1271
+ batchStatements.push({
1272
+ sql: `UPDATE ${TABLE_THREADS} SET updatedAt = ? WHERE id = ?`,
1273
+ args: [now, threadId]
1274
+ });
1275
+ await this.client.batch(batchStatements, "write");
1276
+ const list = new MessageList().add(messages, "memory");
1277
+ if (format === `v2`) return list.get.all.v2();
1278
+ return list.get.all.v1();
1279
+ } catch (error) {
1280
+ throw new MastraError(
1281
+ {
1282
+ id: "LIBSQL_STORE_SAVE_MESSAGES_FAILED",
1283
+ domain: ErrorDomain.STORAGE,
1284
+ category: ErrorCategory.THIRD_PARTY
1285
+ },
1286
+ error
1287
+ );
1088
1288
  }
1089
1289
  }
1090
- insert(args) {
1091
- return this.executeWriteOperationWithRetry(() => this.doInsert(args), `insert into table ${args.tableName}`);
1290
+ async updateMessages({
1291
+ messages
1292
+ }) {
1293
+ if (messages.length === 0) {
1294
+ return [];
1295
+ }
1296
+ const messageIds = messages.map((m) => m.id);
1297
+ const placeholders = messageIds.map(() => "?").join(",");
1298
+ const selectSql = `SELECT * FROM ${TABLE_MESSAGES} WHERE id IN (${placeholders})`;
1299
+ const existingResult = await this.client.execute({ sql: selectSql, args: messageIds });
1300
+ const existingMessages = existingResult.rows.map((row) => this.parseRow(row));
1301
+ if (existingMessages.length === 0) {
1302
+ return [];
1303
+ }
1304
+ const batchStatements = [];
1305
+ const threadIdsToUpdate = /* @__PURE__ */ new Set();
1306
+ const columnMapping = {
1307
+ threadId: "thread_id"
1308
+ };
1309
+ for (const existingMessage of existingMessages) {
1310
+ const updatePayload = messages.find((m) => m.id === existingMessage.id);
1311
+ if (!updatePayload) continue;
1312
+ const { id, ...fieldsToUpdate } = updatePayload;
1313
+ if (Object.keys(fieldsToUpdate).length === 0) continue;
1314
+ threadIdsToUpdate.add(existingMessage.threadId);
1315
+ if (updatePayload.threadId && updatePayload.threadId !== existingMessage.threadId) {
1316
+ threadIdsToUpdate.add(updatePayload.threadId);
1317
+ }
1318
+ const setClauses = [];
1319
+ const args = [];
1320
+ const updatableFields = { ...fieldsToUpdate };
1321
+ if (updatableFields.content) {
1322
+ const newContent = {
1323
+ ...existingMessage.content,
1324
+ ...updatableFields.content,
1325
+ // Deep merge metadata if it exists on both
1326
+ ...existingMessage.content?.metadata && updatableFields.content.metadata ? {
1327
+ metadata: {
1328
+ ...existingMessage.content.metadata,
1329
+ ...updatableFields.content.metadata
1330
+ }
1331
+ } : {}
1332
+ };
1333
+ setClauses.push(`${parseSqlIdentifier$1("content", "column name")} = ?`);
1334
+ args.push(JSON.stringify(newContent));
1335
+ delete updatableFields.content;
1336
+ }
1337
+ for (const key in updatableFields) {
1338
+ if (Object.prototype.hasOwnProperty.call(updatableFields, key)) {
1339
+ const dbKey = columnMapping[key] || key;
1340
+ setClauses.push(`${parseSqlIdentifier$1(dbKey, "column name")} = ?`);
1341
+ let value = updatableFields[key];
1342
+ if (typeof value === "object" && value !== null) {
1343
+ value = JSON.stringify(value);
1344
+ }
1345
+ args.push(value);
1346
+ }
1347
+ }
1348
+ if (setClauses.length === 0) continue;
1349
+ args.push(id);
1350
+ const sql = `UPDATE ${TABLE_MESSAGES} SET ${setClauses.join(", ")} WHERE id = ?`;
1351
+ batchStatements.push({ sql, args });
1352
+ }
1353
+ if (batchStatements.length === 0) {
1354
+ return existingMessages;
1355
+ }
1356
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1357
+ for (const threadId of threadIdsToUpdate) {
1358
+ if (threadId) {
1359
+ batchStatements.push({
1360
+ sql: `UPDATE ${TABLE_THREADS} SET updatedAt = ? WHERE id = ?`,
1361
+ args: [now, threadId]
1362
+ });
1363
+ }
1364
+ }
1365
+ await this.client.batch(batchStatements, "write");
1366
+ const updatedResult = await this.client.execute({ sql: selectSql, args: messageIds });
1367
+ return updatedResult.rows.map((row) => this.parseRow(row));
1092
1368
  }
1093
- async doInsert({
1094
- tableName,
1095
- record
1096
- }) {
1097
- await this.client.execute(
1098
- this.prepareStatement({
1099
- tableName,
1100
- record
1101
- })
1102
- );
1369
+ async getResourceById({ resourceId }) {
1370
+ const result = await this.operations.load({
1371
+ tableName: TABLE_RESOURCES,
1372
+ keys: { id: resourceId }
1373
+ });
1374
+ if (!result) {
1375
+ return null;
1376
+ }
1377
+ return {
1378
+ ...result,
1379
+ // Ensure workingMemory is always returned as a string, even if auto-parsed as JSON
1380
+ workingMemory: result.workingMemory && typeof result.workingMemory === "object" ? JSON.stringify(result.workingMemory) : result.workingMemory,
1381
+ metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata,
1382
+ createdAt: new Date(result.createdAt),
1383
+ updatedAt: new Date(result.updatedAt)
1384
+ };
1103
1385
  }
1104
- batchInsert(args) {
1105
- return this.executeWriteOperationWithRetry(
1106
- () => this.doBatchInsert(args),
1107
- `batch insert into table ${args.tableName}`
1108
- ).catch((error) => {
1109
- throw new MastraError(
1110
- {
1111
- id: "LIBSQL_STORE_BATCH_INSERT_FAILED",
1112
- domain: ErrorDomain.STORAGE,
1113
- category: ErrorCategory.THIRD_PARTY,
1114
- details: {
1115
- tableName: args.tableName
1116
- }
1117
- },
1118
- error
1119
- );
1386
+ async saveResource({ resource }) {
1387
+ console.log("resource", resource);
1388
+ await this.operations.insert({
1389
+ tableName: TABLE_RESOURCES,
1390
+ record: {
1391
+ ...resource,
1392
+ metadata: JSON.stringify(resource.metadata)
1393
+ }
1120
1394
  });
1395
+ return resource;
1121
1396
  }
1122
- async doBatchInsert({
1123
- tableName,
1124
- records
1397
+ async updateResource({
1398
+ resourceId,
1399
+ workingMemory,
1400
+ metadata
1125
1401
  }) {
1126
- if (records.length === 0) return;
1127
- const batchStatements = records.map((r) => this.prepareStatement({ tableName, record: r }));
1128
- await this.client.batch(batchStatements, "write");
1129
- }
1130
- async load({ tableName, keys }) {
1131
- const parsedTableName = parseSqlIdentifier(tableName, "table name");
1132
- const parsedKeys = Object.keys(keys).map((key) => parseSqlIdentifier(key, "column name"));
1133
- const conditions = parsedKeys.map((key) => `${key} = ?`).join(" AND ");
1134
- const values = Object.values(keys);
1135
- const result = await this.client.execute({
1136
- sql: `SELECT * FROM ${parsedTableName} WHERE ${conditions} ORDER BY createdAt DESC LIMIT 1`,
1402
+ const existingResource = await this.getResourceById({ resourceId });
1403
+ if (!existingResource) {
1404
+ const newResource = {
1405
+ id: resourceId,
1406
+ workingMemory,
1407
+ metadata: metadata || {},
1408
+ createdAt: /* @__PURE__ */ new Date(),
1409
+ updatedAt: /* @__PURE__ */ new Date()
1410
+ };
1411
+ return this.saveResource({ resource: newResource });
1412
+ }
1413
+ const updatedResource = {
1414
+ ...existingResource,
1415
+ workingMemory: workingMemory !== void 0 ? workingMemory : existingResource.workingMemory,
1416
+ metadata: {
1417
+ ...existingResource.metadata,
1418
+ ...metadata
1419
+ },
1420
+ updatedAt: /* @__PURE__ */ new Date()
1421
+ };
1422
+ const updates = [];
1423
+ const values = [];
1424
+ if (workingMemory !== void 0) {
1425
+ updates.push("workingMemory = ?");
1426
+ values.push(workingMemory);
1427
+ }
1428
+ if (metadata) {
1429
+ updates.push("metadata = ?");
1430
+ values.push(JSON.stringify(updatedResource.metadata));
1431
+ }
1432
+ updates.push("updatedAt = ?");
1433
+ values.push(updatedResource.updatedAt.toISOString());
1434
+ values.push(resourceId);
1435
+ await this.client.execute({
1436
+ sql: `UPDATE ${TABLE_RESOURCES} SET ${updates.join(", ")} WHERE id = ?`,
1137
1437
  args: values
1138
1438
  });
1139
- if (!result.rows || result.rows.length === 0) {
1140
- return null;
1141
- }
1142
- const row = result.rows[0];
1143
- const parsed = Object.fromEntries(
1144
- Object.entries(row || {}).map(([k, v]) => {
1145
- try {
1146
- return [k, typeof v === "string" ? v.startsWith("{") || v.startsWith("[") ? JSON.parse(v) : v : v];
1147
- } catch {
1148
- return [k, v];
1149
- }
1150
- })
1151
- );
1152
- return parsed;
1439
+ return updatedResource;
1153
1440
  }
1154
1441
  async getThreadById({ threadId }) {
1155
1442
  try {
1156
- const result = await this.load({
1443
+ const result = await this.operations.load({
1157
1444
  tableName: TABLE_THREADS,
1158
1445
  keys: { id: threadId }
1159
1446
  });
@@ -1162,7 +1449,9 @@ var LibSQLStore = class extends MastraStorage {
1162
1449
  }
1163
1450
  return {
1164
1451
  ...result,
1165
- metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata
1452
+ metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata,
1453
+ createdAt: new Date(result.createdAt),
1454
+ updatedAt: new Date(result.updatedAt)
1166
1455
  };
1167
1456
  } catch (error) {
1168
1457
  throw new MastraError(
@@ -1276,7 +1565,7 @@ var LibSQLStore = class extends MastraStorage {
1276
1565
  }
1277
1566
  async saveThread({ thread }) {
1278
1567
  try {
1279
- await this.insert({
1568
+ await this.operations.insert({
1280
1569
  tableName: TABLE_THREADS,
1281
1570
  record: {
1282
1571
  ...thread,
@@ -1311,7 +1600,10 @@ var LibSQLStore = class extends MastraStorage {
1311
1600
  domain: ErrorDomain.STORAGE,
1312
1601
  category: ErrorCategory.USER,
1313
1602
  text: `Thread ${id} not found`,
1314
- details: { threadId: id }
1603
+ details: {
1604
+ status: 404,
1605
+ threadId: id
1606
+ }
1315
1607
  });
1316
1608
  }
1317
1609
  const updatedThread = {
@@ -1354,268 +1646,329 @@ var LibSQLStore = class extends MastraStorage {
1354
1646
  } catch (error) {
1355
1647
  throw new MastraError(
1356
1648
  {
1357
- id: "LIBSQL_STORE_DELETE_THREAD_FAILED",
1649
+ id: "LIBSQL_STORE_DELETE_THREAD_FAILED",
1650
+ domain: ErrorDomain.STORAGE,
1651
+ category: ErrorCategory.THIRD_PARTY,
1652
+ details: { threadId }
1653
+ },
1654
+ error
1655
+ );
1656
+ }
1657
+ }
1658
+ };
1659
+ function createExecuteWriteOperationWithRetry({
1660
+ logger,
1661
+ maxRetries,
1662
+ initialBackoffMs
1663
+ }) {
1664
+ return async function executeWriteOperationWithRetry(operationFn, operationDescription) {
1665
+ let retries = 0;
1666
+ while (true) {
1667
+ try {
1668
+ return await operationFn();
1669
+ } catch (error) {
1670
+ if (error.message && (error.message.includes("SQLITE_BUSY") || error.message.includes("database is locked")) && retries < maxRetries) {
1671
+ retries++;
1672
+ const backoffTime = initialBackoffMs * Math.pow(2, retries - 1);
1673
+ logger.warn(
1674
+ `LibSQLStore: Encountered SQLITE_BUSY during ${operationDescription}. Retrying (${retries}/${maxRetries}) in ${backoffTime}ms...`
1675
+ );
1676
+ await new Promise((resolve) => setTimeout(resolve, backoffTime));
1677
+ } else {
1678
+ logger.error(`LibSQLStore: Error during ${operationDescription} after ${retries} retries: ${error}`);
1679
+ throw error;
1680
+ }
1681
+ }
1682
+ }
1683
+ };
1684
+ }
1685
+ function prepareStatement({ tableName, record }) {
1686
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
1687
+ const columns = Object.keys(record).map((col) => parseSqlIdentifier(col, "column name"));
1688
+ const values = Object.values(record).map((v) => {
1689
+ if (typeof v === `undefined`) {
1690
+ return null;
1691
+ }
1692
+ if (v instanceof Date) {
1693
+ return v.toISOString();
1694
+ }
1695
+ return typeof v === "object" ? JSON.stringify(v) : v;
1696
+ });
1697
+ const placeholders = values.map(() => "?").join(", ");
1698
+ return {
1699
+ sql: `INSERT OR REPLACE INTO ${parsedTableName} (${columns.join(", ")}) VALUES (${placeholders})`,
1700
+ args: values
1701
+ };
1702
+ }
1703
+
1704
+ // src/storage/domains/operations/index.ts
1705
+ var StoreOperationsLibSQL = class extends StoreOperations {
1706
+ client;
1707
+ /**
1708
+ * Maximum number of retries for write operations if an SQLITE_BUSY error occurs.
1709
+ * @default 5
1710
+ */
1711
+ maxRetries;
1712
+ /**
1713
+ * Initial backoff time in milliseconds for retrying write operations on SQLITE_BUSY.
1714
+ * The backoff time will double with each retry (exponential backoff).
1715
+ * @default 100
1716
+ */
1717
+ initialBackoffMs;
1718
+ constructor({
1719
+ client,
1720
+ maxRetries,
1721
+ initialBackoffMs
1722
+ }) {
1723
+ super();
1724
+ this.client = client;
1725
+ this.maxRetries = maxRetries ?? 5;
1726
+ this.initialBackoffMs = initialBackoffMs ?? 100;
1727
+ }
1728
+ async hasColumn(table, column) {
1729
+ const result = await this.client.execute({
1730
+ sql: `PRAGMA table_info(${table})`
1731
+ });
1732
+ return (await result.rows)?.some((row) => row.name === column);
1733
+ }
1734
+ getCreateTableSQL(tableName, schema) {
1735
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
1736
+ const columns = Object.entries(schema).map(([name, col]) => {
1737
+ const parsedColumnName = parseSqlIdentifier(name, "column name");
1738
+ let type = col.type.toUpperCase();
1739
+ if (type === "TEXT") type = "TEXT";
1740
+ if (type === "TIMESTAMP") type = "TEXT";
1741
+ const nullable = col.nullable ? "" : "NOT NULL";
1742
+ const primaryKey = col.primaryKey ? "PRIMARY KEY" : "";
1743
+ return `${parsedColumnName} ${type} ${nullable} ${primaryKey}`.trim();
1744
+ });
1745
+ if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
1746
+ const stmnt = `CREATE TABLE IF NOT EXISTS ${parsedTableName} (
1747
+ ${columns.join(",\n")},
1748
+ PRIMARY KEY (workflow_name, run_id)
1749
+ )`;
1750
+ return stmnt;
1751
+ }
1752
+ return `CREATE TABLE IF NOT EXISTS ${parsedTableName} (${columns.join(", ")})`;
1753
+ }
1754
+ async createTable({
1755
+ tableName,
1756
+ schema
1757
+ }) {
1758
+ try {
1759
+ this.logger.debug(`Creating database table`, { tableName, operation: "schema init" });
1760
+ const sql = this.getCreateTableSQL(tableName, schema);
1761
+ await this.client.execute(sql);
1762
+ } catch (error) {
1763
+ throw new MastraError(
1764
+ {
1765
+ id: "LIBSQL_STORE_CREATE_TABLE_FAILED",
1766
+ domain: ErrorDomain.STORAGE,
1767
+ category: ErrorCategory.THIRD_PARTY,
1768
+ details: {
1769
+ tableName
1770
+ }
1771
+ },
1772
+ error
1773
+ );
1774
+ }
1775
+ }
1776
+ getSqlType(type) {
1777
+ switch (type) {
1778
+ case "bigint":
1779
+ return "INTEGER";
1780
+ // SQLite uses INTEGER for all integer sizes
1781
+ case "jsonb":
1782
+ return "TEXT";
1783
+ // Store JSON as TEXT in SQLite
1784
+ default:
1785
+ return super.getSqlType(type);
1786
+ }
1787
+ }
1788
+ async doInsert({
1789
+ tableName,
1790
+ record
1791
+ }) {
1792
+ await this.client.execute(
1793
+ prepareStatement({
1794
+ tableName,
1795
+ record
1796
+ })
1797
+ );
1798
+ }
1799
+ insert(args) {
1800
+ const executeWriteOperationWithRetry = createExecuteWriteOperationWithRetry({
1801
+ logger: this.logger,
1802
+ maxRetries: this.maxRetries,
1803
+ initialBackoffMs: this.initialBackoffMs
1804
+ });
1805
+ return executeWriteOperationWithRetry(() => this.doInsert(args), `insert into table ${args.tableName}`);
1806
+ }
1807
+ async load({ tableName, keys }) {
1808
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
1809
+ const parsedKeys = Object.keys(keys).map((key) => parseSqlIdentifier(key, "column name"));
1810
+ const conditions = parsedKeys.map((key) => `${key} = ?`).join(" AND ");
1811
+ const values = Object.values(keys);
1812
+ const result = await this.client.execute({
1813
+ sql: `SELECT * FROM ${parsedTableName} WHERE ${conditions} ORDER BY createdAt DESC LIMIT 1`,
1814
+ args: values
1815
+ });
1816
+ if (!result.rows || result.rows.length === 0) {
1817
+ return null;
1818
+ }
1819
+ const row = result.rows[0];
1820
+ const parsed = Object.fromEntries(
1821
+ Object.entries(row || {}).map(([k, v]) => {
1822
+ try {
1823
+ return [k, typeof v === "string" ? v.startsWith("{") || v.startsWith("[") ? JSON.parse(v) : v : v];
1824
+ } catch {
1825
+ return [k, v];
1826
+ }
1827
+ })
1828
+ );
1829
+ return parsed;
1830
+ }
1831
+ async doBatchInsert({
1832
+ tableName,
1833
+ records
1834
+ }) {
1835
+ if (records.length === 0) return;
1836
+ const batchStatements = records.map((r) => prepareStatement({ tableName, record: r }));
1837
+ await this.client.batch(batchStatements, "write");
1838
+ }
1839
+ batchInsert(args) {
1840
+ const executeWriteOperationWithRetry = createExecuteWriteOperationWithRetry({
1841
+ logger: this.logger,
1842
+ maxRetries: this.maxRetries,
1843
+ initialBackoffMs: this.initialBackoffMs
1844
+ });
1845
+ return executeWriteOperationWithRetry(
1846
+ () => this.doBatchInsert(args),
1847
+ `batch insert into table ${args.tableName}`
1848
+ ).catch((error) => {
1849
+ throw new MastraError(
1850
+ {
1851
+ id: "LIBSQL_STORE_BATCH_INSERT_FAILED",
1358
1852
  domain: ErrorDomain.STORAGE,
1359
1853
  category: ErrorCategory.THIRD_PARTY,
1360
- details: { threadId }
1854
+ details: {
1855
+ tableName: args.tableName
1856
+ }
1361
1857
  },
1362
1858
  error
1363
1859
  );
1364
- }
1365
- }
1366
- parseRow(row) {
1367
- let content = row.content;
1368
- try {
1369
- content = JSON.parse(row.content);
1370
- } catch {
1371
- }
1372
- const result = {
1373
- id: row.id,
1374
- content,
1375
- role: row.role,
1376
- createdAt: new Date(row.createdAt),
1377
- threadId: row.thread_id,
1378
- resourceId: row.resourceId
1379
- };
1380
- if (row.type && row.type !== `v2`) result.type = row.type;
1381
- return result;
1382
- }
1383
- async _getIncludedMessages({
1384
- threadId,
1385
- selectBy
1386
- }) {
1387
- const include = selectBy?.include;
1388
- if (!include) return null;
1389
- const unionQueries = [];
1390
- const params = [];
1391
- for (const inc of include) {
1392
- const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
1393
- const searchId = inc.threadId || threadId;
1394
- unionQueries.push(
1395
- `
1396
- SELECT * FROM (
1397
- WITH numbered_messages AS (
1398
- SELECT
1399
- id, content, role, type, "createdAt", thread_id, "resourceId",
1400
- ROW_NUMBER() OVER (ORDER BY "createdAt" ASC) as row_num
1401
- FROM "${TABLE_MESSAGES}"
1402
- WHERE thread_id = ?
1403
- ),
1404
- target_positions AS (
1405
- SELECT row_num as target_pos
1406
- FROM numbered_messages
1407
- WHERE id = ?
1408
- )
1409
- SELECT DISTINCT m.*
1410
- FROM numbered_messages m
1411
- CROSS JOIN target_positions t
1412
- WHERE m.row_num BETWEEN (t.target_pos - ?) AND (t.target_pos + ?)
1413
- )
1414
- `
1415
- // Keep ASC for final sorting after fetching context
1416
- );
1417
- params.push(searchId, id, withPreviousMessages, withNextMessages);
1418
- }
1419
- const finalQuery = unionQueries.join(" UNION ALL ") + ' ORDER BY "createdAt" ASC';
1420
- const includedResult = await this.client.execute({ sql: finalQuery, args: params });
1421
- const includedRows = includedResult.rows?.map((row) => this.parseRow(row));
1422
- const seen = /* @__PURE__ */ new Set();
1423
- const dedupedRows = includedRows.filter((row) => {
1424
- if (seen.has(row.id)) return false;
1425
- seen.add(row.id);
1426
- return true;
1427
1860
  });
1428
- return dedupedRows;
1429
1861
  }
1430
- async getMessages({
1431
- threadId,
1432
- selectBy,
1433
- format
1862
+ /**
1863
+ * Alters table schema to add columns if they don't exist
1864
+ * @param tableName Name of the table
1865
+ * @param schema Schema of the table
1866
+ * @param ifNotExists Array of column names to add if they don't exist
1867
+ */
1868
+ async alterTable({
1869
+ tableName,
1870
+ schema,
1871
+ ifNotExists
1434
1872
  }) {
1873
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
1435
1874
  try {
1436
- const messages = [];
1437
- const limit = this.resolveMessageLimit({ last: selectBy?.last, defaultLimit: 40 });
1438
- if (selectBy?.include?.length) {
1439
- const includeMessages = await this._getIncludedMessages({ threadId, selectBy });
1440
- if (includeMessages) {
1441
- messages.push(...includeMessages);
1875
+ const pragmaQuery = `PRAGMA table_info(${parsedTableName})`;
1876
+ const result = await this.client.execute(pragmaQuery);
1877
+ const existingColumnNames = new Set(result.rows.map((row) => row.name.toLowerCase()));
1878
+ for (const columnName of ifNotExists) {
1879
+ if (!existingColumnNames.has(columnName.toLowerCase()) && schema[columnName]) {
1880
+ const columnDef = schema[columnName];
1881
+ const sqlType = this.getSqlType(columnDef.type);
1882
+ const nullable = columnDef.nullable === false ? "NOT NULL" : "";
1883
+ const defaultValue = columnDef.nullable === false ? this.getDefaultValue(columnDef.type) : "";
1884
+ const alterSql = `ALTER TABLE ${parsedTableName} ADD COLUMN "${columnName}" ${sqlType} ${nullable} ${defaultValue}`.trim();
1885
+ await this.client.execute(alterSql);
1886
+ this.logger?.debug?.(`Added column ${columnName} to table ${parsedTableName}`);
1442
1887
  }
1443
1888
  }
1444
- const excludeIds = messages.map((m) => m.id);
1445
- const remainingSql = `
1446
- SELECT
1447
- id,
1448
- content,
1449
- role,
1450
- type,
1451
- "createdAt",
1452
- thread_id,
1453
- "resourceId"
1454
- FROM "${TABLE_MESSAGES}"
1455
- WHERE thread_id = ?
1456
- ${excludeIds.length ? `AND id NOT IN (${excludeIds.map(() => "?").join(", ")})` : ""}
1457
- ORDER BY "createdAt" DESC
1458
- LIMIT ?
1459
- `;
1460
- const remainingArgs = [threadId, ...excludeIds.length ? excludeIds : [], limit];
1461
- const remainingResult = await this.client.execute({ sql: remainingSql, args: remainingArgs });
1462
- if (remainingResult.rows) {
1463
- messages.push(...remainingResult.rows.map((row) => this.parseRow(row)));
1464
- }
1465
- messages.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
1466
- const list = new MessageList().add(messages, "memory");
1467
- if (format === `v2`) return list.get.all.v2();
1468
- return list.get.all.v1();
1469
1889
  } catch (error) {
1470
1890
  throw new MastraError(
1471
1891
  {
1472
- id: "LIBSQL_STORE_GET_MESSAGES_FAILED",
1892
+ id: "LIBSQL_STORE_ALTER_TABLE_FAILED",
1473
1893
  domain: ErrorDomain.STORAGE,
1474
1894
  category: ErrorCategory.THIRD_PARTY,
1475
- details: { threadId }
1895
+ details: {
1896
+ tableName
1897
+ }
1476
1898
  },
1477
1899
  error
1478
1900
  );
1479
1901
  }
1480
1902
  }
1481
- async getMessagesPaginated(args) {
1482
- const { threadId, format, selectBy } = args;
1483
- const { page = 0, perPage: perPageInput, dateRange } = selectBy?.pagination || {};
1484
- const perPage = perPageInput !== void 0 ? perPageInput : this.resolveMessageLimit({ last: selectBy?.last, defaultLimit: 40 });
1485
- const fromDate = dateRange?.start;
1486
- const toDate = dateRange?.end;
1487
- const messages = [];
1488
- if (selectBy?.include?.length) {
1489
- try {
1490
- const includeMessages = await this._getIncludedMessages({ threadId, selectBy });
1491
- if (includeMessages) {
1492
- messages.push(...includeMessages);
1493
- }
1494
- } catch (error) {
1495
- throw new MastraError(
1496
- {
1497
- id: "LIBSQL_STORE_GET_MESSAGES_PAGINATED_GET_INCLUDE_MESSAGES_FAILED",
1498
- domain: ErrorDomain.STORAGE,
1499
- category: ErrorCategory.THIRD_PARTY,
1500
- details: { threadId }
1501
- },
1502
- error
1503
- );
1504
- }
1505
- }
1903
+ async clearTable({ tableName }) {
1904
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
1506
1905
  try {
1507
- const currentOffset = page * perPage;
1508
- const conditions = [`thread_id = ?`];
1509
- const queryParams = [threadId];
1510
- if (fromDate) {
1511
- conditions.push(`"createdAt" >= ?`);
1512
- queryParams.push(fromDate.toISOString());
1513
- }
1514
- if (toDate) {
1515
- conditions.push(`"createdAt" <= ?`);
1516
- queryParams.push(toDate.toISOString());
1517
- }
1518
- const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1519
- const countResult = await this.client.execute({
1520
- sql: `SELECT COUNT(*) as count FROM ${TABLE_MESSAGES} ${whereClause}`,
1521
- args: queryParams
1522
- });
1523
- const total = Number(countResult.rows?.[0]?.count ?? 0);
1524
- if (total === 0 && messages.length === 0) {
1525
- return {
1526
- messages: [],
1527
- total: 0,
1528
- page,
1529
- perPage,
1530
- hasMore: false
1531
- };
1532
- }
1533
- const excludeIds = messages.map((m) => m.id);
1534
- const excludeIdsParam = excludeIds.map((_, idx) => `$${idx + queryParams.length + 1}`).join(", ");
1535
- const dataResult = await this.client.execute({
1536
- sql: `SELECT id, content, role, type, "createdAt", "resourceId", "thread_id" FROM ${TABLE_MESSAGES} ${whereClause} ${excludeIds.length ? `AND id NOT IN (${excludeIdsParam})` : ""} ORDER BY "createdAt" DESC LIMIT ? OFFSET ?`,
1537
- args: [...queryParams, ...excludeIds, perPage, currentOffset]
1538
- });
1539
- messages.push(...(dataResult.rows || []).map((row) => this.parseRow(row)));
1540
- const messagesToReturn = format === "v1" ? new MessageList().add(messages, "memory").get.all.v1() : new MessageList().add(messages, "memory").get.all.v2();
1541
- return {
1542
- messages: messagesToReturn,
1543
- total,
1544
- page,
1545
- perPage,
1546
- hasMore: currentOffset + messages.length < total
1547
- };
1548
- } catch (error) {
1906
+ await this.client.execute(`DELETE FROM ${parsedTableName}`);
1907
+ } catch (e) {
1549
1908
  const mastraError = new MastraError(
1550
1909
  {
1551
- id: "LIBSQL_STORE_GET_MESSAGES_PAGINATED_FAILED",
1910
+ id: "LIBSQL_STORE_CLEAR_TABLE_FAILED",
1552
1911
  domain: ErrorDomain.STORAGE,
1553
1912
  category: ErrorCategory.THIRD_PARTY,
1554
- details: { threadId }
1913
+ details: {
1914
+ tableName
1915
+ }
1555
1916
  },
1556
- error
1917
+ e
1557
1918
  );
1558
1919
  this.logger?.trackException?.(mastraError);
1559
1920
  this.logger?.error?.(mastraError.toString());
1560
- return { messages: [], total: 0, page, perPage, hasMore: false };
1561
1921
  }
1562
1922
  }
1563
- async saveMessages({
1564
- messages,
1565
- format
1566
- }) {
1567
- if (messages.length === 0) return messages;
1923
+ async dropTable({ tableName }) {
1924
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
1568
1925
  try {
1569
- const threadId = messages[0]?.threadId;
1570
- if (!threadId) {
1571
- throw new Error("Thread ID is required");
1572
- }
1573
- const batchStatements = messages.map((message) => {
1574
- const time = message.createdAt || /* @__PURE__ */ new Date();
1575
- if (!message.threadId) {
1576
- throw new Error(
1577
- `Expected to find a threadId for message, but couldn't find one. An unexpected error has occurred.`
1578
- );
1579
- }
1580
- if (!message.resourceId) {
1581
- throw new Error(
1582
- `Expected to find a resourceId for message, but couldn't find one. An unexpected error has occurred.`
1583
- );
1584
- }
1585
- return {
1586
- sql: `INSERT INTO ${TABLE_MESSAGES} (id, thread_id, content, role, type, createdAt, resourceId)
1587
- VALUES (?, ?, ?, ?, ?, ?, ?)
1588
- ON CONFLICT(id) DO UPDATE SET
1589
- thread_id=excluded.thread_id,
1590
- content=excluded.content,
1591
- role=excluded.role,
1592
- type=excluded.type,
1593
- resourceId=excluded.resourceId
1594
- `,
1595
- args: [
1596
- message.id,
1597
- message.threadId,
1598
- typeof message.content === "object" ? JSON.stringify(message.content) : message.content,
1599
- message.role,
1600
- message.type || "v2",
1601
- time instanceof Date ? time.toISOString() : time,
1602
- message.resourceId
1603
- ]
1604
- };
1605
- });
1606
- const now = (/* @__PURE__ */ new Date()).toISOString();
1607
- batchStatements.push({
1608
- sql: `UPDATE ${TABLE_THREADS} SET updatedAt = ? WHERE id = ?`,
1609
- args: [now, threadId]
1926
+ await this.client.execute(`DROP TABLE IF EXISTS ${parsedTableName}`);
1927
+ } catch (e) {
1928
+ throw new MastraError(
1929
+ {
1930
+ id: "LIBSQL_STORE_DROP_TABLE_FAILED",
1931
+ domain: ErrorDomain.STORAGE,
1932
+ category: ErrorCategory.THIRD_PARTY,
1933
+ details: {
1934
+ tableName
1935
+ }
1936
+ },
1937
+ e
1938
+ );
1939
+ }
1940
+ }
1941
+ };
1942
+ var ScoresLibSQL = class extends ScoresStorage {
1943
+ operations;
1944
+ client;
1945
+ constructor({ client, operations }) {
1946
+ super();
1947
+ this.operations = operations;
1948
+ this.client = client;
1949
+ }
1950
+ async getScoresByRunId({
1951
+ runId,
1952
+ pagination
1953
+ }) {
1954
+ try {
1955
+ const result = await this.client.execute({
1956
+ sql: `SELECT * FROM ${TABLE_SCORERS} WHERE runId = ? ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
1957
+ args: [runId, pagination.perPage + 1, pagination.page * pagination.perPage]
1610
1958
  });
1611
- await this.client.batch(batchStatements, "write");
1612
- const list = new MessageList().add(messages, "memory");
1613
- if (format === `v2`) return list.get.all.v2();
1614
- return list.get.all.v1();
1959
+ return {
1960
+ scores: result.rows?.slice(0, pagination.perPage).map((row) => this.transformScoreRow(row)) ?? [],
1961
+ pagination: {
1962
+ total: result.rows?.length ?? 0,
1963
+ page: pagination.page,
1964
+ perPage: pagination.perPage,
1965
+ hasMore: result.rows?.length > pagination.perPage
1966
+ }
1967
+ };
1615
1968
  } catch (error) {
1616
1969
  throw new MastraError(
1617
1970
  {
1618
- id: "LIBSQL_STORE_SAVE_MESSAGES_FAILED",
1971
+ id: "LIBSQL_STORE_GET_SCORES_BY_RUN_ID_FAILED",
1619
1972
  domain: ErrorDomain.STORAGE,
1620
1973
  category: ErrorCategory.THIRD_PARTY
1621
1974
  },
@@ -1623,185 +1976,144 @@ var LibSQLStore = class extends MastraStorage {
1623
1976
  );
1624
1977
  }
1625
1978
  }
1626
- async updateMessages({
1627
- messages
1979
+ async getScoresByScorerId({
1980
+ scorerId,
1981
+ entityId,
1982
+ entityType,
1983
+ pagination
1628
1984
  }) {
1629
- if (messages.length === 0) {
1630
- return [];
1631
- }
1632
- const messageIds = messages.map((m) => m.id);
1633
- const placeholders = messageIds.map(() => "?").join(",");
1634
- const selectSql = `SELECT * FROM ${TABLE_MESSAGES} WHERE id IN (${placeholders})`;
1635
- const existingResult = await this.client.execute({ sql: selectSql, args: messageIds });
1636
- const existingMessages = existingResult.rows.map((row) => this.parseRow(row));
1637
- if (existingMessages.length === 0) {
1638
- return [];
1639
- }
1640
- const batchStatements = [];
1641
- const threadIdsToUpdate = /* @__PURE__ */ new Set();
1642
- const columnMapping = {
1643
- threadId: "thread_id"
1644
- };
1645
- for (const existingMessage of existingMessages) {
1646
- const updatePayload = messages.find((m) => m.id === existingMessage.id);
1647
- if (!updatePayload) continue;
1648
- const { id, ...fieldsToUpdate } = updatePayload;
1649
- if (Object.keys(fieldsToUpdate).length === 0) continue;
1650
- threadIdsToUpdate.add(existingMessage.threadId);
1651
- if (updatePayload.threadId && updatePayload.threadId !== existingMessage.threadId) {
1652
- threadIdsToUpdate.add(updatePayload.threadId);
1653
- }
1654
- const setClauses = [];
1655
- const args = [];
1656
- const updatableFields = { ...fieldsToUpdate };
1657
- if (updatableFields.content) {
1658
- const newContent = {
1659
- ...existingMessage.content,
1660
- ...updatableFields.content,
1661
- // Deep merge metadata if it exists on both
1662
- ...existingMessage.content?.metadata && updatableFields.content.metadata ? {
1663
- metadata: {
1664
- ...existingMessage.content.metadata,
1665
- ...updatableFields.content.metadata
1666
- }
1667
- } : {}
1668
- };
1669
- setClauses.push(`${parseSqlIdentifier("content", "column name")} = ?`);
1670
- args.push(JSON.stringify(newContent));
1671
- delete updatableFields.content;
1985
+ try {
1986
+ const conditions = [];
1987
+ const queryParams = [];
1988
+ if (scorerId) {
1989
+ conditions.push(`scorerId = ?`);
1990
+ queryParams.push(scorerId);
1672
1991
  }
1673
- for (const key in updatableFields) {
1674
- if (Object.prototype.hasOwnProperty.call(updatableFields, key)) {
1675
- const dbKey = columnMapping[key] || key;
1676
- setClauses.push(`${parseSqlIdentifier(dbKey, "column name")} = ?`);
1677
- let value = updatableFields[key];
1678
- if (typeof value === "object" && value !== null) {
1679
- value = JSON.stringify(value);
1680
- }
1681
- args.push(value);
1682
- }
1992
+ if (entityId) {
1993
+ conditions.push(`entityId = ?`);
1994
+ queryParams.push(entityId);
1683
1995
  }
1684
- if (setClauses.length === 0) continue;
1685
- args.push(id);
1686
- const sql = `UPDATE ${TABLE_MESSAGES} SET ${setClauses.join(", ")} WHERE id = ?`;
1687
- batchStatements.push({ sql, args });
1688
- }
1689
- if (batchStatements.length === 0) {
1690
- return existingMessages;
1691
- }
1692
- const now = (/* @__PURE__ */ new Date()).toISOString();
1693
- for (const threadId of threadIdsToUpdate) {
1694
- if (threadId) {
1695
- batchStatements.push({
1696
- sql: `UPDATE ${TABLE_THREADS} SET updatedAt = ? WHERE id = ?`,
1697
- args: [now, threadId]
1698
- });
1996
+ if (entityType) {
1997
+ conditions.push(`entityType = ?`);
1998
+ queryParams.push(entityType);
1699
1999
  }
2000
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2001
+ const result = await this.client.execute({
2002
+ sql: `SELECT * FROM ${TABLE_SCORERS} ${whereClause} ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
2003
+ args: [...queryParams, pagination.perPage + 1, pagination.page * pagination.perPage]
2004
+ });
2005
+ return {
2006
+ scores: result.rows?.slice(0, pagination.perPage).map((row) => this.transformScoreRow(row)) ?? [],
2007
+ pagination: {
2008
+ total: result.rows?.length ?? 0,
2009
+ page: pagination.page,
2010
+ perPage: pagination.perPage,
2011
+ hasMore: result.rows?.length > pagination.perPage
2012
+ }
2013
+ };
2014
+ } catch (error) {
2015
+ throw new MastraError(
2016
+ {
2017
+ id: "LIBSQL_STORE_GET_SCORES_BY_SCORER_ID_FAILED",
2018
+ domain: ErrorDomain.STORAGE,
2019
+ category: ErrorCategory.THIRD_PARTY
2020
+ },
2021
+ error
2022
+ );
1700
2023
  }
1701
- await this.client.batch(batchStatements, "write");
1702
- const updatedResult = await this.client.execute({ sql: selectSql, args: messageIds });
1703
- return updatedResult.rows.map((row) => this.parseRow(row));
1704
2024
  }
1705
- transformEvalRow(row) {
1706
- const resultValue = JSON.parse(row.result);
1707
- const testInfoValue = row.test_info ? JSON.parse(row.test_info) : void 0;
1708
- if (!resultValue || typeof resultValue !== "object" || !("score" in resultValue)) {
1709
- throw new Error(`Invalid MetricResult format: ${JSON.stringify(resultValue)}`);
1710
- }
2025
+ transformScoreRow(row) {
2026
+ const scorerValue = JSON.parse(row.scorer ?? "{}");
2027
+ const inputValue = JSON.parse(row.input ?? "{}");
2028
+ const outputValue = JSON.parse(row.output ?? "{}");
2029
+ const additionalLLMContextValue = row.additionalLLMContext ? JSON.parse(row.additionalLLMContext) : null;
2030
+ const runtimeContextValue = row.runtimeContext ? JSON.parse(row.runtimeContext) : null;
2031
+ const metadataValue = row.metadata ? JSON.parse(row.metadata) : null;
2032
+ const entityValue = row.entity ? JSON.parse(row.entity) : null;
2033
+ const extractStepResultValue = row.extractStepResult ? JSON.parse(row.extractStepResult) : null;
2034
+ const analyzeStepResultValue = row.analyzeStepResult ? JSON.parse(row.analyzeStepResult) : null;
1711
2035
  return {
1712
- input: row.input,
1713
- output: row.output,
1714
- result: resultValue,
1715
- agentName: row.agent_name,
1716
- metricName: row.metric_name,
1717
- instructions: row.instructions,
1718
- testInfo: testInfoValue,
1719
- globalRunId: row.global_run_id,
1720
- runId: row.run_id,
1721
- createdAt: row.created_at
2036
+ id: row.id,
2037
+ traceId: row.traceId,
2038
+ runId: row.runId,
2039
+ scorer: scorerValue,
2040
+ score: row.score,
2041
+ reason: row.reason,
2042
+ extractStepResult: extractStepResultValue,
2043
+ analyzeStepResult: analyzeStepResultValue,
2044
+ analyzePrompt: row.analyzePrompt,
2045
+ extractPrompt: row.extractPrompt,
2046
+ metadata: metadataValue,
2047
+ input: inputValue,
2048
+ output: outputValue,
2049
+ additionalContext: additionalLLMContextValue,
2050
+ runtimeContext: runtimeContextValue,
2051
+ entityType: row.entityType,
2052
+ entity: entityValue,
2053
+ entityId: row.entityId,
2054
+ scorerId: row.scorerId,
2055
+ source: row.source,
2056
+ resourceId: row.resourceId,
2057
+ threadId: row.threadId,
2058
+ createdAt: row.createdAt,
2059
+ updatedAt: row.updatedAt
1722
2060
  };
1723
2061
  }
1724
- /** @deprecated use getEvals instead */
1725
- async getEvalsByAgentName(agentName, type) {
2062
+ async getScoreById({ id }) {
2063
+ const result = await this.client.execute({
2064
+ sql: `SELECT * FROM ${TABLE_SCORERS} WHERE id = ?`,
2065
+ args: [id]
2066
+ });
2067
+ return result.rows?.[0] ? this.transformScoreRow(result.rows[0]) : null;
2068
+ }
2069
+ async saveScore(score) {
1726
2070
  try {
1727
- const baseQuery = `SELECT * FROM ${TABLE_EVALS} WHERE agent_name = ?`;
1728
- 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)" : "";
1729
- const result = await this.client.execute({
1730
- sql: `${baseQuery}${typeCondition} ORDER BY created_at DESC`,
1731
- args: [agentName]
2071
+ const id = crypto.randomUUID();
2072
+ await this.operations.insert({
2073
+ tableName: TABLE_SCORERS,
2074
+ record: {
2075
+ id,
2076
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2077
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2078
+ ...score
2079
+ }
1732
2080
  });
1733
- return result.rows?.map((row) => this.transformEvalRow(row)) ?? [];
2081
+ const scoreFromDb = await this.getScoreById({ id });
2082
+ return { score: scoreFromDb };
1734
2083
  } catch (error) {
1735
- if (error instanceof Error && error.message.includes("no such table")) {
1736
- return [];
1737
- }
1738
2084
  throw new MastraError(
1739
2085
  {
1740
- id: "LIBSQL_STORE_GET_EVALS_BY_AGENT_NAME_FAILED",
2086
+ id: "LIBSQL_STORE_SAVE_SCORE_FAILED",
1741
2087
  domain: ErrorDomain.STORAGE,
1742
- category: ErrorCategory.THIRD_PARTY,
1743
- details: { agentName }
2088
+ category: ErrorCategory.THIRD_PARTY
1744
2089
  },
1745
2090
  error
1746
2091
  );
1747
2092
  }
1748
2093
  }
1749
- async getEvals(options = {}) {
1750
- const { agentName, type, page = 0, perPage = 100, dateRange } = options;
1751
- const fromDate = dateRange?.start;
1752
- const toDate = dateRange?.end;
1753
- const conditions = [];
1754
- const queryParams = [];
1755
- if (agentName) {
1756
- conditions.push(`agent_name = ?`);
1757
- queryParams.push(agentName);
1758
- }
1759
- if (type === "test") {
1760
- conditions.push(`(test_info IS NOT NULL AND json_extract(test_info, '$.testPath') IS NOT NULL)`);
1761
- } else if (type === "live") {
1762
- conditions.push(`(test_info IS NULL OR json_extract(test_info, '$.testPath') IS NULL)`);
1763
- }
1764
- if (fromDate) {
1765
- conditions.push(`created_at >= ?`);
1766
- queryParams.push(fromDate.toISOString());
1767
- }
1768
- if (toDate) {
1769
- conditions.push(`created_at <= ?`);
1770
- queryParams.push(toDate.toISOString());
1771
- }
1772
- const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2094
+ async getScoresByEntityId({
2095
+ entityId,
2096
+ entityType,
2097
+ pagination
2098
+ }) {
1773
2099
  try {
1774
- const countResult = await this.client.execute({
1775
- sql: `SELECT COUNT(*) as count FROM ${TABLE_EVALS} ${whereClause}`,
1776
- args: queryParams
1777
- });
1778
- const total = Number(countResult.rows?.[0]?.count ?? 0);
1779
- const currentOffset = page * perPage;
1780
- const hasMore = currentOffset + perPage < total;
1781
- if (total === 0) {
1782
- return {
1783
- evals: [],
1784
- total: 0,
1785
- page,
1786
- perPage,
1787
- hasMore: false
1788
- };
1789
- }
1790
- const dataResult = await this.client.execute({
1791
- sql: `SELECT * FROM ${TABLE_EVALS} ${whereClause} ORDER BY created_at DESC LIMIT ? OFFSET ?`,
1792
- args: [...queryParams, perPage, currentOffset]
2100
+ const result = await this.client.execute({
2101
+ sql: `SELECT * FROM ${TABLE_SCORERS} WHERE entityId = ? AND entityType = ? ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
2102
+ args: [entityId, entityType, pagination.perPage + 1, pagination.page * pagination.perPage]
1793
2103
  });
1794
2104
  return {
1795
- evals: dataResult.rows?.map((row) => this.transformEvalRow(row)) ?? [],
1796
- total,
1797
- page,
1798
- perPage,
1799
- hasMore
2105
+ scores: result.rows?.slice(0, pagination.perPage).map((row) => this.transformScoreRow(row)) ?? [],
2106
+ pagination: {
2107
+ total: result.rows?.length ?? 0,
2108
+ page: pagination.page,
2109
+ perPage: pagination.perPage,
2110
+ hasMore: result.rows?.length > pagination.perPage
2111
+ }
1800
2112
  };
1801
2113
  } catch (error) {
1802
2114
  throw new MastraError(
1803
2115
  {
1804
- id: "LIBSQL_STORE_GET_EVALS_FAILED",
2116
+ id: "LIBSQL_STORE_GET_SCORES_BY_ENTITY_ID_FAILED",
1805
2117
  domain: ErrorDomain.STORAGE,
1806
2118
  category: ErrorCategory.THIRD_PARTY
1807
2119
  },
@@ -1809,9 +2121,15 @@ var LibSQLStore = class extends MastraStorage {
1809
2121
  );
1810
2122
  }
1811
2123
  }
1812
- /**
1813
- * @deprecated use getTracesPaginated instead.
1814
- */
2124
+ };
2125
+ var TracesLibSQL = class extends TracesStorage {
2126
+ client;
2127
+ operations;
2128
+ constructor({ client, operations }) {
2129
+ super();
2130
+ this.client = client;
2131
+ this.operations = operations;
2132
+ }
1815
2133
  async getTraces(args) {
1816
2134
  if (args.fromDate || args.toDate) {
1817
2135
  args.dateRange = {
@@ -1888,35 +2206,133 @@ var LibSQLStore = class extends MastraStorage {
1888
2206
  sql: `SELECT * FROM ${TABLE_TRACES} ${whereClause} ORDER BY "startTime" DESC LIMIT ? OFFSET ?`,
1889
2207
  args: [...queryArgs, perPage, currentOffset]
1890
2208
  });
1891
- const traces = dataResult.rows?.map(
1892
- (row) => ({
1893
- id: row.id,
1894
- parentSpanId: row.parentSpanId,
1895
- traceId: row.traceId,
1896
- name: row.name,
1897
- scope: row.scope,
1898
- kind: row.kind,
1899
- status: safelyParseJSON(row.status),
1900
- events: safelyParseJSON(row.events),
1901
- links: safelyParseJSON(row.links),
1902
- attributes: safelyParseJSON(row.attributes),
1903
- startTime: row.startTime,
1904
- endTime: row.endTime,
1905
- other: safelyParseJSON(row.other),
1906
- createdAt: row.createdAt
1907
- })
1908
- ) ?? [];
1909
- return {
1910
- traces,
1911
- total,
1912
- page,
1913
- perPage,
1914
- hasMore: currentOffset + traces.length < total
1915
- };
2209
+ const traces = dataResult.rows?.map(
2210
+ (row) => ({
2211
+ id: row.id,
2212
+ parentSpanId: row.parentSpanId,
2213
+ traceId: row.traceId,
2214
+ name: row.name,
2215
+ scope: row.scope,
2216
+ kind: row.kind,
2217
+ status: safelyParseJSON(row.status),
2218
+ events: safelyParseJSON(row.events),
2219
+ links: safelyParseJSON(row.links),
2220
+ attributes: safelyParseJSON(row.attributes),
2221
+ startTime: row.startTime,
2222
+ endTime: row.endTime,
2223
+ other: safelyParseJSON(row.other),
2224
+ createdAt: row.createdAt
2225
+ })
2226
+ ) ?? [];
2227
+ return {
2228
+ traces,
2229
+ total,
2230
+ page,
2231
+ perPage,
2232
+ hasMore: currentOffset + traces.length < total
2233
+ };
2234
+ } catch (error) {
2235
+ throw new MastraError(
2236
+ {
2237
+ id: "LIBSQL_STORE_GET_TRACES_PAGINATED_FAILED",
2238
+ domain: ErrorDomain.STORAGE,
2239
+ category: ErrorCategory.THIRD_PARTY
2240
+ },
2241
+ error
2242
+ );
2243
+ }
2244
+ }
2245
+ async batchTraceInsert({ records }) {
2246
+ this.logger.debug("Batch inserting traces", { count: records.length });
2247
+ await this.operations.batchInsert({
2248
+ tableName: TABLE_TRACES,
2249
+ records
2250
+ });
2251
+ }
2252
+ };
2253
+ function parseWorkflowRun(row) {
2254
+ let parsedSnapshot = row.snapshot;
2255
+ if (typeof parsedSnapshot === "string") {
2256
+ try {
2257
+ parsedSnapshot = JSON.parse(row.snapshot);
2258
+ } catch (e) {
2259
+ console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
2260
+ }
2261
+ }
2262
+ return {
2263
+ workflowName: row.workflow_name,
2264
+ runId: row.run_id,
2265
+ snapshot: parsedSnapshot,
2266
+ resourceId: row.resourceId,
2267
+ createdAt: new Date(row.createdAt),
2268
+ updatedAt: new Date(row.updatedAt)
2269
+ };
2270
+ }
2271
+ var WorkflowsLibSQL = class extends WorkflowsStorage {
2272
+ operations;
2273
+ client;
2274
+ constructor({ operations, client }) {
2275
+ super();
2276
+ this.operations = operations;
2277
+ this.client = client;
2278
+ }
2279
+ async persistWorkflowSnapshot({
2280
+ workflowName,
2281
+ runId,
2282
+ snapshot
2283
+ }) {
2284
+ const data = {
2285
+ workflow_name: workflowName,
2286
+ run_id: runId,
2287
+ snapshot,
2288
+ createdAt: /* @__PURE__ */ new Date(),
2289
+ updatedAt: /* @__PURE__ */ new Date()
2290
+ };
2291
+ this.logger.debug("Persisting workflow snapshot", { workflowName, runId, data });
2292
+ await this.operations.insert({
2293
+ tableName: TABLE_WORKFLOW_SNAPSHOT,
2294
+ record: data
2295
+ });
2296
+ }
2297
+ async loadWorkflowSnapshot({
2298
+ workflowName,
2299
+ runId
2300
+ }) {
2301
+ this.logger.debug("Loading workflow snapshot", { workflowName, runId });
2302
+ const d = await this.operations.load({
2303
+ tableName: TABLE_WORKFLOW_SNAPSHOT,
2304
+ keys: { workflow_name: workflowName, run_id: runId }
2305
+ });
2306
+ return d ? d.snapshot : null;
2307
+ }
2308
+ async getWorkflowRunById({
2309
+ runId,
2310
+ workflowName
2311
+ }) {
2312
+ const conditions = [];
2313
+ const args = [];
2314
+ if (runId) {
2315
+ conditions.push("run_id = ?");
2316
+ args.push(runId);
2317
+ }
2318
+ if (workflowName) {
2319
+ conditions.push("workflow_name = ?");
2320
+ args.push(workflowName);
2321
+ }
2322
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2323
+ try {
2324
+ const result = await this.client.execute({
2325
+ sql: `SELECT * FROM ${TABLE_WORKFLOW_SNAPSHOT} ${whereClause}`,
2326
+ args
2327
+ });
2328
+ if (!result.rows?.[0]) {
2329
+ return null;
2330
+ }
2331
+ return parseWorkflowRun(result.rows[0]);
1916
2332
  } catch (error) {
1917
2333
  throw new MastraError(
1918
2334
  {
1919
- id: "LIBSQL_STORE_GET_TRACES_PAGINATED_FAILED",
2335
+ id: "LIBSQL_STORE_GET_WORKFLOW_RUN_BY_ID_FAILED",
1920
2336
  domain: ErrorDomain.STORAGE,
1921
2337
  category: ErrorCategory.THIRD_PARTY
1922
2338
  },
@@ -1948,7 +2364,7 @@ var LibSQLStore = class extends MastraStorage {
1948
2364
  args.push(toDate.toISOString());
1949
2365
  }
1950
2366
  if (resourceId) {
1951
- const hasResourceId = await this.hasColumn(TABLE_WORKFLOW_SNAPSHOT, "resourceId");
2367
+ const hasResourceId = await this.operations.hasColumn(TABLE_WORKFLOW_SNAPSHOT, "resourceId");
1952
2368
  if (hasResourceId) {
1953
2369
  conditions.push("resourceId = ?");
1954
2370
  args.push(resourceId);
@@ -1969,7 +2385,7 @@ var LibSQLStore = class extends MastraStorage {
1969
2385
  sql: `SELECT * FROM ${TABLE_WORKFLOW_SNAPSHOT} ${whereClause} ORDER BY createdAt DESC${limit !== void 0 && offset !== void 0 ? ` LIMIT ? OFFSET ?` : ""}`,
1970
2386
  args: limit !== void 0 && offset !== void 0 ? [...args, limit, offset] : args
1971
2387
  });
1972
- const runs = (result.rows || []).map((row) => this.parseWorkflowRun(row));
2388
+ const runs = (result.rows || []).map((row) => parseWorkflowRun(row));
1973
2389
  return { runs, total: total || runs.length };
1974
2390
  } catch (error) {
1975
2391
  throw new MastraError(
@@ -1982,133 +2398,227 @@ var LibSQLStore = class extends MastraStorage {
1982
2398
  );
1983
2399
  }
1984
2400
  }
2401
+ };
2402
+
2403
+ // src/storage/index.ts
2404
+ var LibSQLStore = class extends MastraStorage {
2405
+ client;
2406
+ maxRetries;
2407
+ initialBackoffMs;
2408
+ stores;
2409
+ constructor(config) {
2410
+ super({ name: `LibSQLStore` });
2411
+ this.maxRetries = config.maxRetries ?? 5;
2412
+ this.initialBackoffMs = config.initialBackoffMs ?? 100;
2413
+ if ("url" in config) {
2414
+ if (config.url.endsWith(":memory:")) {
2415
+ this.shouldCacheInit = false;
2416
+ }
2417
+ this.client = createClient({ url: config.url });
2418
+ if (config.url.startsWith("file:") || config.url.includes(":memory:")) {
2419
+ this.client.execute("PRAGMA journal_mode=WAL;").then(() => this.logger.debug("LibSQLStore: PRAGMA journal_mode=WAL set.")).catch((err) => this.logger.warn("LibSQLStore: Failed to set PRAGMA journal_mode=WAL.", err));
2420
+ this.client.execute("PRAGMA busy_timeout = 5000;").then(() => this.logger.debug("LibSQLStore: PRAGMA busy_timeout=5000 set.")).catch((err) => this.logger.warn("LibSQLStore: Failed to set PRAGMA busy_timeout.", err));
2421
+ }
2422
+ } else {
2423
+ this.client = config.client;
2424
+ }
2425
+ const operations = new StoreOperationsLibSQL({
2426
+ client: this.client,
2427
+ maxRetries: this.maxRetries,
2428
+ initialBackoffMs: this.initialBackoffMs
2429
+ });
2430
+ const scores = new ScoresLibSQL({ client: this.client, operations });
2431
+ const traces = new TracesLibSQL({ client: this.client, operations });
2432
+ const workflows = new WorkflowsLibSQL({ client: this.client, operations });
2433
+ const memory = new MemoryLibSQL({ client: this.client, operations });
2434
+ const legacyEvals = new LegacyEvalsLibSQL({ client: this.client });
2435
+ this.stores = {
2436
+ operations,
2437
+ scores,
2438
+ traces,
2439
+ workflows,
2440
+ memory,
2441
+ legacyEvals
2442
+ };
2443
+ }
2444
+ get supports() {
2445
+ return {
2446
+ selectByIncludeResourceScope: true,
2447
+ resourceWorkingMemory: true,
2448
+ hasColumn: true,
2449
+ createTable: true
2450
+ };
2451
+ }
2452
+ async createTable({
2453
+ tableName,
2454
+ schema
2455
+ }) {
2456
+ await this.stores.operations.createTable({ tableName, schema });
2457
+ }
2458
+ /**
2459
+ * Alters table schema to add columns if they don't exist
2460
+ * @param tableName Name of the table
2461
+ * @param schema Schema of the table
2462
+ * @param ifNotExists Array of column names to add if they don't exist
2463
+ */
2464
+ async alterTable({
2465
+ tableName,
2466
+ schema,
2467
+ ifNotExists
2468
+ }) {
2469
+ await this.stores.operations.alterTable({ tableName, schema, ifNotExists });
2470
+ }
2471
+ async clearTable({ tableName }) {
2472
+ await this.stores.operations.clearTable({ tableName });
2473
+ }
2474
+ async dropTable({ tableName }) {
2475
+ await this.stores.operations.dropTable({ tableName });
2476
+ }
2477
+ insert(args) {
2478
+ return this.stores.operations.insert(args);
2479
+ }
2480
+ batchInsert(args) {
2481
+ return this.stores.operations.batchInsert(args);
2482
+ }
2483
+ async load({ tableName, keys }) {
2484
+ return this.stores.operations.load({ tableName, keys });
2485
+ }
2486
+ async getThreadById({ threadId }) {
2487
+ return this.stores.memory.getThreadById({ threadId });
2488
+ }
2489
+ /**
2490
+ * @deprecated use getThreadsByResourceIdPaginated instead for paginated results.
2491
+ */
2492
+ async getThreadsByResourceId(args) {
2493
+ return this.stores.memory.getThreadsByResourceId(args);
2494
+ }
2495
+ async getThreadsByResourceIdPaginated(args) {
2496
+ return this.stores.memory.getThreadsByResourceIdPaginated(args);
2497
+ }
2498
+ async saveThread({ thread }) {
2499
+ return this.stores.memory.saveThread({ thread });
2500
+ }
2501
+ async updateThread({
2502
+ id,
2503
+ title,
2504
+ metadata
2505
+ }) {
2506
+ return this.stores.memory.updateThread({ id, title, metadata });
2507
+ }
2508
+ async deleteThread({ threadId }) {
2509
+ return this.stores.memory.deleteThread({ threadId });
2510
+ }
2511
+ async getMessages({
2512
+ threadId,
2513
+ selectBy,
2514
+ format
2515
+ }) {
2516
+ return this.stores.memory.getMessages({ threadId, selectBy, format });
2517
+ }
2518
+ async getMessagesPaginated(args) {
2519
+ return this.stores.memory.getMessagesPaginated(args);
2520
+ }
2521
+ async saveMessages(args) {
2522
+ return this.stores.memory.saveMessages(args);
2523
+ }
2524
+ async updateMessages({
2525
+ messages
2526
+ }) {
2527
+ return this.stores.memory.updateMessages({ messages });
2528
+ }
2529
+ /** @deprecated use getEvals instead */
2530
+ async getEvalsByAgentName(agentName, type) {
2531
+ return this.stores.legacyEvals.getEvalsByAgentName(agentName, type);
2532
+ }
2533
+ async getEvals(options = {}) {
2534
+ return this.stores.legacyEvals.getEvals(options);
2535
+ }
2536
+ async getScoreById({ id }) {
2537
+ return this.stores.scores.getScoreById({ id });
2538
+ }
2539
+ async saveScore(score) {
2540
+ return this.stores.scores.saveScore(score);
2541
+ }
2542
+ async getScoresByScorerId({
2543
+ scorerId,
2544
+ entityId,
2545
+ entityType,
2546
+ pagination
2547
+ }) {
2548
+ return this.stores.scores.getScoresByScorerId({ scorerId, entityId, entityType, pagination });
2549
+ }
2550
+ async getScoresByRunId({
2551
+ runId,
2552
+ pagination
2553
+ }) {
2554
+ return this.stores.scores.getScoresByRunId({ runId, pagination });
2555
+ }
2556
+ async getScoresByEntityId({
2557
+ entityId,
2558
+ entityType,
2559
+ pagination
2560
+ }) {
2561
+ return this.stores.scores.getScoresByEntityId({ entityId, entityType, pagination });
2562
+ }
2563
+ /**
2564
+ * TRACES
2565
+ */
2566
+ /**
2567
+ * @deprecated use getTracesPaginated instead.
2568
+ */
2569
+ async getTraces(args) {
2570
+ return this.stores.traces.getTraces(args);
2571
+ }
2572
+ async getTracesPaginated(args) {
2573
+ return this.stores.traces.getTracesPaginated(args);
2574
+ }
2575
+ async batchTraceInsert(args) {
2576
+ return this.stores.traces.batchTraceInsert(args);
2577
+ }
2578
+ /**
2579
+ * WORKFLOWS
2580
+ */
2581
+ async persistWorkflowSnapshot({
2582
+ workflowName,
2583
+ runId,
2584
+ snapshot
2585
+ }) {
2586
+ return this.stores.workflows.persistWorkflowSnapshot({ workflowName, runId, snapshot });
2587
+ }
2588
+ async loadWorkflowSnapshot({
2589
+ workflowName,
2590
+ runId
2591
+ }) {
2592
+ return this.stores.workflows.loadWorkflowSnapshot({ workflowName, runId });
2593
+ }
2594
+ async getWorkflowRuns({
2595
+ workflowName,
2596
+ fromDate,
2597
+ toDate,
2598
+ limit,
2599
+ offset,
2600
+ resourceId
2601
+ } = {}) {
2602
+ return this.stores.workflows.getWorkflowRuns({ workflowName, fromDate, toDate, limit, offset, resourceId });
2603
+ }
1985
2604
  async getWorkflowRunById({
1986
2605
  runId,
1987
2606
  workflowName
1988
2607
  }) {
1989
- const conditions = [];
1990
- const args = [];
1991
- if (runId) {
1992
- conditions.push("run_id = ?");
1993
- args.push(runId);
1994
- }
1995
- if (workflowName) {
1996
- conditions.push("workflow_name = ?");
1997
- args.push(workflowName);
1998
- }
1999
- const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2000
- try {
2001
- const result = await this.client.execute({
2002
- sql: `SELECT * FROM ${TABLE_WORKFLOW_SNAPSHOT} ${whereClause}`,
2003
- args
2004
- });
2005
- if (!result.rows?.[0]) {
2006
- return null;
2007
- }
2008
- return this.parseWorkflowRun(result.rows[0]);
2009
- } catch (error) {
2010
- throw new MastraError(
2011
- {
2012
- id: "LIBSQL_STORE_GET_WORKFLOW_RUN_BY_ID_FAILED",
2013
- domain: ErrorDomain.STORAGE,
2014
- category: ErrorCategory.THIRD_PARTY
2015
- },
2016
- error
2017
- );
2018
- }
2608
+ return this.stores.workflows.getWorkflowRunById({ runId, workflowName });
2019
2609
  }
2020
2610
  async getResourceById({ resourceId }) {
2021
- const result = await this.load({
2022
- tableName: TABLE_RESOURCES,
2023
- keys: { id: resourceId }
2024
- });
2025
- if (!result) {
2026
- return null;
2027
- }
2028
- return {
2029
- ...result,
2030
- // Ensure workingMemory is always returned as a string, even if auto-parsed as JSON
2031
- workingMemory: typeof result.workingMemory === "object" ? JSON.stringify(result.workingMemory) : result.workingMemory,
2032
- metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata
2033
- };
2611
+ return this.stores.memory.getResourceById({ resourceId });
2034
2612
  }
2035
2613
  async saveResource({ resource }) {
2036
- await this.insert({
2037
- tableName: TABLE_RESOURCES,
2038
- record: {
2039
- ...resource,
2040
- metadata: JSON.stringify(resource.metadata)
2041
- }
2042
- });
2043
- return resource;
2614
+ return this.stores.memory.saveResource({ resource });
2044
2615
  }
2045
2616
  async updateResource({
2046
2617
  resourceId,
2047
2618
  workingMemory,
2048
2619
  metadata
2049
2620
  }) {
2050
- const existingResource = await this.getResourceById({ resourceId });
2051
- if (!existingResource) {
2052
- const newResource = {
2053
- id: resourceId,
2054
- workingMemory,
2055
- metadata: metadata || {},
2056
- createdAt: /* @__PURE__ */ new Date(),
2057
- updatedAt: /* @__PURE__ */ new Date()
2058
- };
2059
- return this.saveResource({ resource: newResource });
2060
- }
2061
- const updatedResource = {
2062
- ...existingResource,
2063
- workingMemory: workingMemory !== void 0 ? workingMemory : existingResource.workingMemory,
2064
- metadata: {
2065
- ...existingResource.metadata,
2066
- ...metadata
2067
- },
2068
- updatedAt: /* @__PURE__ */ new Date()
2069
- };
2070
- const updates = [];
2071
- const values = [];
2072
- if (workingMemory !== void 0) {
2073
- updates.push("workingMemory = ?");
2074
- values.push(workingMemory);
2075
- }
2076
- if (metadata) {
2077
- updates.push("metadata = ?");
2078
- values.push(JSON.stringify(updatedResource.metadata));
2079
- }
2080
- updates.push("updatedAt = ?");
2081
- values.push(updatedResource.updatedAt.toISOString());
2082
- values.push(resourceId);
2083
- await this.client.execute({
2084
- sql: `UPDATE ${TABLE_RESOURCES} SET ${updates.join(", ")} WHERE id = ?`,
2085
- args: values
2086
- });
2087
- return updatedResource;
2088
- }
2089
- async hasColumn(table, column) {
2090
- const result = await this.client.execute({
2091
- sql: `PRAGMA table_info(${table})`
2092
- });
2093
- return (await result.rows)?.some((row) => row.name === column);
2094
- }
2095
- parseWorkflowRun(row) {
2096
- let parsedSnapshot = row.snapshot;
2097
- if (typeof parsedSnapshot === "string") {
2098
- try {
2099
- parsedSnapshot = JSON.parse(row.snapshot);
2100
- } catch (e) {
2101
- console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
2102
- }
2103
- }
2104
- return {
2105
- workflowName: row.workflow_name,
2106
- runId: row.run_id,
2107
- snapshot: parsedSnapshot,
2108
- resourceId: row.resourceId,
2109
- createdAt: new Date(row.createdAt),
2110
- updatedAt: new Date(row.updatedAt)
2111
- };
2621
+ return this.stores.memory.updateResource({ resourceId, workingMemory, metadata });
2112
2622
  }
2113
2623
  };
2114
2624