@mastra/libsql 1.0.0-beta.8 → 1.0.0-beta.9

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
@@ -1,9 +1,10 @@
1
1
  import { createClient } from '@libsql/client';
2
2
  import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
3
- import { createVectorErrorId, MastraStorage, StoreOperations, TABLE_WORKFLOW_SNAPSHOT, TABLE_SPANS, createStorageErrorId, ScoresStorage, TABLE_SCORERS, normalizePerPage, calculatePagination, transformScoreRow, WorkflowsStorage, MemoryStorage, TABLE_MESSAGES, TABLE_THREADS, TABLE_RESOURCES, ObservabilityStorage, AgentsStorage, TABLE_AGENTS, TABLE_SCHEMAS, safelyParseJSON, SPAN_SCHEMA } from '@mastra/core/storage';
3
+ import { createVectorErrorId, MastraStorage, ScoresStorage, SCORERS_SCHEMA, TABLE_SCORERS, normalizePerPage, calculatePagination, createStorageErrorId, transformScoreRow, WorkflowsStorage, TABLE_SCHEMAS, TABLE_WORKFLOW_SNAPSHOT, MemoryStorage, TABLE_THREADS, TABLE_MESSAGES, TABLE_RESOURCES, ObservabilityStorage, SPAN_SCHEMA, TABLE_SPANS, listTracesArgsSchema, AgentsStorage, AGENTS_SCHEMA, TABLE_AGENTS, getSqlType, TraceStatus, safelyParseJSON } from '@mastra/core/storage';
4
4
  import { parseSqlIdentifier, parseFieldKey } from '@mastra/core/utils';
5
5
  import { MastraVector } from '@mastra/core/vector';
6
6
  import { BaseFilterTranslator } from '@mastra/core/vector/filter';
7
+ import { MastraBase } from '@mastra/core/base';
7
8
  import { MessageList } from '@mastra/core/agent';
8
9
  import { saveScorePayloadSchema } from '@mastra/core/evals';
9
10
 
@@ -1092,1807 +1093,2134 @@ var LibSQLVector = class extends MastraVector {
1092
1093
  });
1093
1094
  }
1094
1095
  };
1095
- var AgentsLibSQL = class extends AgentsStorage {
1096
- client;
1097
- constructor({ client, operations: _ }) {
1098
- super();
1099
- this.client = client;
1100
- }
1101
- parseJson(value, fieldName) {
1102
- if (!value) return void 0;
1103
- if (typeof value !== "string") return value;
1104
- try {
1105
- return JSON.parse(value);
1106
- } catch (error) {
1107
- const details = {
1108
- value: value.length > 100 ? value.substring(0, 100) + "..." : value
1109
- };
1110
- if (fieldName) {
1111
- details.field = fieldName;
1096
+ function isLockError(error) {
1097
+ return error.code === "SQLITE_BUSY" || error.code === "SQLITE_LOCKED" || error.message?.toLowerCase().includes("database is locked") || error.message?.toLowerCase().includes("database table is locked") || error.message?.toLowerCase().includes("table is locked") || error.constructor.name === "SqliteError" && error.message?.toLowerCase().includes("locked");
1098
+ }
1099
+ function createExecuteWriteOperationWithRetry({
1100
+ logger,
1101
+ maxRetries,
1102
+ initialBackoffMs
1103
+ }) {
1104
+ return async function executeWriteOperationWithRetry(operationFn, operationDescription) {
1105
+ let attempts = 0;
1106
+ let backoff = initialBackoffMs;
1107
+ while (attempts < maxRetries) {
1108
+ try {
1109
+ return await operationFn();
1110
+ } catch (error) {
1111
+ logger.debug(`LibSQLStore: Error caught in retry loop for ${operationDescription}`, {
1112
+ errorType: error.constructor.name,
1113
+ errorCode: error.code,
1114
+ errorMessage: error.message,
1115
+ attempts,
1116
+ maxRetries
1117
+ });
1118
+ if (isLockError(error)) {
1119
+ attempts++;
1120
+ if (attempts >= maxRetries) {
1121
+ logger.error(
1122
+ `LibSQLStore: Operation failed after ${maxRetries} attempts due to database lock: ${error.message}`,
1123
+ { error, attempts, maxRetries }
1124
+ );
1125
+ throw error;
1126
+ }
1127
+ logger.warn(
1128
+ `LibSQLStore: Attempt ${attempts} failed due to database lock during ${operationDescription}. Retrying in ${backoff}ms...`,
1129
+ { errorMessage: error.message, attempts, backoff, maxRetries }
1130
+ );
1131
+ await new Promise((resolve) => setTimeout(resolve, backoff));
1132
+ backoff *= 2;
1133
+ } else {
1134
+ logger.error(`LibSQLStore: Non-lock error during ${operationDescription}, not retrying`, { error });
1135
+ throw error;
1136
+ }
1112
1137
  }
1113
- throw new MastraError(
1114
- {
1115
- id: createStorageErrorId("LIBSQL", "PARSE_JSON", "INVALID_JSON"),
1116
- domain: ErrorDomain.STORAGE,
1117
- category: ErrorCategory.SYSTEM,
1118
- text: `Failed to parse JSON${fieldName ? ` for field "${fieldName}"` : ""}: ${error instanceof Error ? error.message : "Unknown error"}`,
1119
- details
1120
- },
1121
- error
1122
- );
1123
1138
  }
1139
+ throw new Error(`LibSQLStore: Unexpected exit from retry loop for ${operationDescription}`);
1140
+ };
1141
+ }
1142
+ function prepareStatement({ tableName, record }) {
1143
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
1144
+ const columns = Object.keys(record).map((col) => parseSqlIdentifier(col, "column name"));
1145
+ const values = Object.values(record).map((v) => {
1146
+ if (typeof v === `undefined` || v === null) {
1147
+ return null;
1148
+ }
1149
+ if (v instanceof Date) {
1150
+ return v.toISOString();
1151
+ }
1152
+ return typeof v === "object" ? JSON.stringify(v) : v;
1153
+ });
1154
+ const placeholders = values.map(() => "?").join(", ");
1155
+ return {
1156
+ sql: `INSERT OR REPLACE INTO ${parsedTableName} (${columns.join(", ")}) VALUES (${placeholders})`,
1157
+ args: values
1158
+ };
1159
+ }
1160
+ function prepareUpdateStatement({
1161
+ tableName,
1162
+ updates,
1163
+ keys
1164
+ }) {
1165
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
1166
+ const schema = TABLE_SCHEMAS[tableName];
1167
+ const updateColumns = Object.keys(updates).map((col) => parseSqlIdentifier(col, "column name"));
1168
+ const updateValues = Object.values(updates).map(transformToSqlValue);
1169
+ const setClause = updateColumns.map((col) => `${col} = ?`).join(", ");
1170
+ const whereClause = prepareWhereClause(keys, schema);
1171
+ return {
1172
+ sql: `UPDATE ${parsedTableName} SET ${setClause}${whereClause.sql}`,
1173
+ args: [...updateValues, ...whereClause.args]
1174
+ };
1175
+ }
1176
+ function transformToSqlValue(value) {
1177
+ if (typeof value === "undefined" || value === null) {
1178
+ return null;
1124
1179
  }
1125
- parseRow(row) {
1126
- return {
1127
- id: row.id,
1128
- name: row.name,
1129
- description: row.description,
1130
- instructions: row.instructions,
1131
- model: this.parseJson(row.model, "model"),
1132
- tools: this.parseJson(row.tools, "tools"),
1133
- defaultOptions: this.parseJson(row.defaultOptions, "defaultOptions"),
1134
- workflows: this.parseJson(row.workflows, "workflows"),
1135
- agents: this.parseJson(row.agents, "agents"),
1136
- inputProcessors: this.parseJson(row.inputProcessors, "inputProcessors"),
1137
- outputProcessors: this.parseJson(row.outputProcessors, "outputProcessors"),
1138
- memory: this.parseJson(row.memory, "memory"),
1139
- scorers: this.parseJson(row.scorers, "scorers"),
1140
- metadata: this.parseJson(row.metadata, "metadata"),
1141
- createdAt: new Date(row.createdAt),
1142
- updatedAt: new Date(row.updatedAt)
1143
- };
1180
+ if (value instanceof Date) {
1181
+ return value.toISOString();
1144
1182
  }
1145
- async getAgentById({ id }) {
1146
- try {
1147
- const result = await this.client.execute({
1148
- sql: `SELECT * FROM "${TABLE_AGENTS}" WHERE id = ?`,
1149
- args: [id]
1150
- });
1151
- if (!result.rows || result.rows.length === 0) {
1152
- return null;
1153
- }
1154
- return this.parseRow(result.rows[0]);
1155
- } catch (error) {
1156
- throw new MastraError(
1157
- {
1158
- id: createStorageErrorId("LIBSQL", "GET_AGENT_BY_ID", "FAILED"),
1159
- domain: ErrorDomain.STORAGE,
1160
- category: ErrorCategory.THIRD_PARTY,
1161
- details: { agentId: id }
1162
- },
1163
- error
1164
- );
1183
+ return typeof value === "object" ? JSON.stringify(value) : value;
1184
+ }
1185
+ function prepareDeleteStatement({ tableName, keys }) {
1186
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
1187
+ const whereClause = prepareWhereClause(keys, TABLE_SCHEMAS[tableName]);
1188
+ return {
1189
+ sql: `DELETE FROM ${parsedTableName}${whereClause.sql}`,
1190
+ args: whereClause.args
1191
+ };
1192
+ }
1193
+ function prepareWhereClause(filters, schema) {
1194
+ const conditions = [];
1195
+ const args = [];
1196
+ for (const [columnName, filterValue] of Object.entries(filters)) {
1197
+ const column = schema[columnName];
1198
+ if (!column) {
1199
+ throw new Error(`Unknown column: ${columnName}`);
1165
1200
  }
1201
+ const parsedColumn = parseSqlIdentifier(columnName, "column name");
1202
+ const result = buildCondition2(parsedColumn, filterValue);
1203
+ conditions.push(result.condition);
1204
+ args.push(...result.args);
1166
1205
  }
1167
- async createAgent({ agent }) {
1168
- try {
1169
- const now = /* @__PURE__ */ new Date();
1170
- const nowIso = now.toISOString();
1171
- await this.client.execute({
1172
- sql: `INSERT INTO "${TABLE_AGENTS}" (id, name, description, instructions, model, tools, "defaultOptions", workflows, agents, "inputProcessors", "outputProcessors", memory, scorers, metadata, "createdAt", "updatedAt")
1173
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1174
- args: [
1175
- agent.id,
1176
- agent.name,
1177
- agent.description ?? null,
1178
- agent.instructions,
1179
- JSON.stringify(agent.model),
1180
- agent.tools ? JSON.stringify(agent.tools) : null,
1181
- agent.defaultOptions ? JSON.stringify(agent.defaultOptions) : null,
1182
- agent.workflows ? JSON.stringify(agent.workflows) : null,
1183
- agent.agents ? JSON.stringify(agent.agents) : null,
1184
- agent.inputProcessors ? JSON.stringify(agent.inputProcessors) : null,
1185
- agent.outputProcessors ? JSON.stringify(agent.outputProcessors) : null,
1186
- agent.memory ? JSON.stringify(agent.memory) : null,
1187
- agent.scorers ? JSON.stringify(agent.scorers) : null,
1188
- agent.metadata ? JSON.stringify(agent.metadata) : null,
1189
- nowIso,
1190
- nowIso
1191
- ]
1192
- });
1193
- return {
1194
- ...agent,
1195
- createdAt: now,
1196
- updatedAt: now
1197
- };
1198
- } catch (error) {
1199
- throw new MastraError(
1200
- {
1201
- id: createStorageErrorId("LIBSQL", "CREATE_AGENT", "FAILED"),
1202
- domain: ErrorDomain.STORAGE,
1203
- category: ErrorCategory.THIRD_PARTY,
1204
- details: { agentId: agent.id }
1205
- },
1206
- error
1207
- );
1208
- }
1206
+ return {
1207
+ sql: conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "",
1208
+ args
1209
+ };
1210
+ }
1211
+ function buildCondition2(columnName, filterValue) {
1212
+ if (filterValue === null) {
1213
+ return { condition: `${columnName} IS NULL`, args: [] };
1209
1214
  }
1210
- async updateAgent({ id, ...updates }) {
1211
- try {
1212
- const existingAgent = await this.getAgentById({ id });
1213
- if (!existingAgent) {
1214
- throw new MastraError({
1215
- id: createStorageErrorId("LIBSQL", "UPDATE_AGENT", "NOT_FOUND"),
1216
- domain: ErrorDomain.STORAGE,
1217
- category: ErrorCategory.USER,
1218
- text: `Agent ${id} not found`,
1219
- details: { agentId: id }
1220
- });
1221
- }
1222
- const setClauses = [];
1223
- const args = [];
1224
- if (updates.name !== void 0) {
1225
- setClauses.push("name = ?");
1226
- args.push(updates.name);
1227
- }
1228
- if (updates.description !== void 0) {
1229
- setClauses.push("description = ?");
1230
- args.push(updates.description);
1231
- }
1232
- if (updates.instructions !== void 0) {
1233
- setClauses.push("instructions = ?");
1234
- args.push(updates.instructions);
1235
- }
1236
- if (updates.model !== void 0) {
1237
- setClauses.push("model = ?");
1238
- args.push(JSON.stringify(updates.model));
1239
- }
1240
- if (updates.tools !== void 0) {
1241
- setClauses.push("tools = ?");
1242
- args.push(JSON.stringify(updates.tools));
1243
- }
1244
- if (updates.defaultOptions !== void 0) {
1245
- setClauses.push('"defaultOptions" = ?');
1246
- args.push(JSON.stringify(updates.defaultOptions));
1247
- }
1248
- if (updates.workflows !== void 0) {
1249
- setClauses.push("workflows = ?");
1250
- args.push(JSON.stringify(updates.workflows));
1251
- }
1252
- if (updates.agents !== void 0) {
1253
- setClauses.push("agents = ?");
1254
- args.push(JSON.stringify(updates.agents));
1255
- }
1256
- if (updates.inputProcessors !== void 0) {
1257
- setClauses.push('"inputProcessors" = ?');
1258
- args.push(JSON.stringify(updates.inputProcessors));
1259
- }
1260
- if (updates.outputProcessors !== void 0) {
1261
- setClauses.push('"outputProcessors" = ?');
1262
- args.push(JSON.stringify(updates.outputProcessors));
1263
- }
1264
- if (updates.memory !== void 0) {
1265
- setClauses.push("memory = ?");
1266
- args.push(JSON.stringify(updates.memory));
1267
- }
1268
- if (updates.scorers !== void 0) {
1269
- setClauses.push("scorers = ?");
1270
- args.push(JSON.stringify(updates.scorers));
1271
- }
1272
- if (updates.metadata !== void 0) {
1273
- const mergedMetadata = { ...existingAgent.metadata, ...updates.metadata };
1274
- setClauses.push("metadata = ?");
1275
- args.push(JSON.stringify(mergedMetadata));
1276
- }
1277
- const now = /* @__PURE__ */ new Date();
1278
- setClauses.push('"updatedAt" = ?');
1279
- args.push(now.toISOString());
1280
- args.push(id);
1281
- if (setClauses.length > 1) {
1282
- await this.client.execute({
1283
- sql: `UPDATE "${TABLE_AGENTS}" SET ${setClauses.join(", ")} WHERE id = ?`,
1284
- args
1285
- });
1286
- }
1287
- const updatedAgent = await this.getAgentById({ id });
1288
- if (!updatedAgent) {
1289
- throw new MastraError({
1290
- id: createStorageErrorId("LIBSQL", "UPDATE_AGENT", "NOT_FOUND_AFTER_UPDATE"),
1291
- domain: ErrorDomain.STORAGE,
1292
- category: ErrorCategory.SYSTEM,
1293
- text: `Agent ${id} not found after update`,
1294
- details: { agentId: id }
1295
- });
1296
- }
1297
- return updatedAgent;
1298
- } catch (error) {
1299
- if (error instanceof MastraError) {
1300
- throw error;
1301
- }
1302
- throw new MastraError(
1303
- {
1304
- id: createStorageErrorId("LIBSQL", "UPDATE_AGENT", "FAILED"),
1305
- domain: ErrorDomain.STORAGE,
1306
- category: ErrorCategory.THIRD_PARTY,
1307
- details: { agentId: id }
1308
- },
1309
- error
1310
- );
1311
- }
1215
+ if (typeof filterValue === "object" && filterValue !== null && ("startAt" in filterValue || "endAt" in filterValue)) {
1216
+ return buildDateRangeCondition(columnName, filterValue);
1312
1217
  }
1313
- async deleteAgent({ id }) {
1314
- try {
1315
- await this.client.execute({
1316
- sql: `DELETE FROM "${TABLE_AGENTS}" WHERE id = ?`,
1317
- args: [id]
1318
- });
1319
- } catch (error) {
1320
- throw new MastraError(
1321
- {
1322
- id: createStorageErrorId("LIBSQL", "DELETE_AGENT", "FAILED"),
1323
- domain: ErrorDomain.STORAGE,
1324
- category: ErrorCategory.THIRD_PARTY,
1325
- details: { agentId: id }
1326
- },
1327
- error
1328
- );
1329
- }
1218
+ return {
1219
+ condition: `${columnName} = ?`,
1220
+ args: [transformToSqlValue(filterValue)]
1221
+ };
1222
+ }
1223
+ function buildDateRangeCondition(columnName, range) {
1224
+ const conditions = [];
1225
+ const args = [];
1226
+ if (range.startAt !== void 0) {
1227
+ conditions.push(`${columnName} >= ?`);
1228
+ args.push(transformToSqlValue(range.startAt));
1330
1229
  }
1331
- async listAgents(args) {
1332
- const { page = 0, perPage: perPageInput, orderBy } = args || {};
1333
- const { field, direction } = this.parseOrderBy(orderBy);
1334
- if (page < 0) {
1335
- throw new MastraError(
1336
- {
1337
- id: createStorageErrorId("LIBSQL", "LIST_AGENTS", "INVALID_PAGE"),
1338
- domain: ErrorDomain.STORAGE,
1339
- category: ErrorCategory.USER,
1340
- details: { page }
1341
- },
1342
- new Error("page must be >= 0")
1343
- );
1230
+ if (range.endAt !== void 0) {
1231
+ conditions.push(`${columnName} <= ?`);
1232
+ args.push(transformToSqlValue(range.endAt));
1233
+ }
1234
+ if (conditions.length === 0) {
1235
+ throw new Error("Date range must specify at least startAt or endAt");
1236
+ }
1237
+ return {
1238
+ condition: conditions.join(" AND "),
1239
+ args
1240
+ };
1241
+ }
1242
+ function transformFromSqlRow({
1243
+ tableName,
1244
+ sqlRow
1245
+ }) {
1246
+ const result = {};
1247
+ const jsonColumns = new Set(
1248
+ Object.keys(TABLE_SCHEMAS[tableName]).filter((key) => TABLE_SCHEMAS[tableName][key].type === "jsonb").map((key) => key)
1249
+ );
1250
+ const dateColumns = new Set(
1251
+ Object.keys(TABLE_SCHEMAS[tableName]).filter((key) => TABLE_SCHEMAS[tableName][key].type === "timestamp").map((key) => key)
1252
+ );
1253
+ for (const [key, value] of Object.entries(sqlRow)) {
1254
+ if (value === null || value === void 0) {
1255
+ result[key] = value;
1256
+ continue;
1344
1257
  }
1345
- const perPage = normalizePerPage(perPageInput, 100);
1346
- const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
1347
- try {
1348
- const countResult = await this.client.execute({
1349
- sql: `SELECT COUNT(*) as count FROM "${TABLE_AGENTS}"`,
1350
- args: []
1351
- });
1352
- const total = Number(countResult.rows?.[0]?.count ?? 0);
1353
- if (total === 0) {
1354
- return {
1355
- agents: [],
1356
- total: 0,
1357
- page,
1358
- perPage: perPageForResponse,
1359
- hasMore: false
1360
- };
1361
- }
1362
- const limitValue = perPageInput === false ? total : perPage;
1363
- const dataResult = await this.client.execute({
1364
- sql: `SELECT * FROM "${TABLE_AGENTS}" ORDER BY "${field}" ${direction} LIMIT ? OFFSET ?`,
1365
- args: [limitValue, offset]
1366
- });
1367
- const agents = (dataResult.rows || []).map((row) => this.parseRow(row));
1368
- return {
1369
- agents,
1370
- total,
1371
- page,
1372
- perPage: perPageForResponse,
1373
- hasMore: perPageInput === false ? false : offset + perPage < total
1374
- };
1375
- } catch (error) {
1376
- throw new MastraError(
1377
- {
1378
- id: createStorageErrorId("LIBSQL", "LIST_AGENTS", "FAILED"),
1379
- domain: ErrorDomain.STORAGE,
1380
- category: ErrorCategory.THIRD_PARTY
1381
- },
1382
- error
1383
- );
1258
+ if (dateColumns.has(key) && typeof value === "string") {
1259
+ result[key] = new Date(value);
1260
+ continue;
1261
+ }
1262
+ if (jsonColumns.has(key) && typeof value === "string") {
1263
+ result[key] = safelyParseJSON(value);
1264
+ continue;
1384
1265
  }
1266
+ result[key] = value;
1385
1267
  }
1386
- };
1387
- var MemoryLibSQL = class extends MemoryStorage {
1268
+ return result;
1269
+ }
1270
+
1271
+ // src/storage/db/index.ts
1272
+ function resolveClient(config) {
1273
+ if ("client" in config) {
1274
+ return config.client;
1275
+ }
1276
+ return createClient({
1277
+ url: config.url,
1278
+ ...config.authToken ? { authToken: config.authToken } : {}
1279
+ });
1280
+ }
1281
+ var LibSQLDB = class extends MastraBase {
1388
1282
  client;
1389
- operations;
1390
- constructor({ client, operations }) {
1391
- super();
1283
+ maxRetries;
1284
+ initialBackoffMs;
1285
+ executeWriteOperationWithRetry;
1286
+ constructor({
1287
+ client,
1288
+ maxRetries,
1289
+ initialBackoffMs
1290
+ }) {
1291
+ super({
1292
+ component: "STORAGE",
1293
+ name: "LIBSQL_DB_LAYER"
1294
+ });
1392
1295
  this.client = client;
1393
- this.operations = operations;
1394
- }
1395
- parseRow(row) {
1396
- let content = row.content;
1397
- try {
1398
- content = JSON.parse(row.content);
1399
- } catch {
1400
- }
1401
- const result = {
1402
- id: row.id,
1403
- content,
1404
- role: row.role,
1405
- createdAt: new Date(row.createdAt),
1406
- threadId: row.thread_id,
1407
- resourceId: row.resourceId
1408
- };
1409
- if (row.type && row.type !== `v2`) result.type = row.type;
1410
- return result;
1296
+ this.maxRetries = maxRetries ?? 5;
1297
+ this.initialBackoffMs = initialBackoffMs ?? 100;
1298
+ this.executeWriteOperationWithRetry = createExecuteWriteOperationWithRetry({
1299
+ logger: this.logger,
1300
+ maxRetries: this.maxRetries,
1301
+ initialBackoffMs: this.initialBackoffMs
1302
+ });
1411
1303
  }
1412
- async _getIncludedMessages({ include }) {
1413
- if (!include || include.length === 0) return null;
1414
- const unionQueries = [];
1415
- const params = [];
1416
- for (const inc of include) {
1417
- const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
1418
- unionQueries.push(
1419
- `
1420
- SELECT * FROM (
1421
- WITH target_thread AS (
1422
- SELECT thread_id FROM "${TABLE_MESSAGES}" WHERE id = ?
1423
- ),
1424
- numbered_messages AS (
1425
- SELECT
1426
- id, content, role, type, "createdAt", thread_id, "resourceId",
1427
- ROW_NUMBER() OVER (ORDER BY "createdAt" ASC) as row_num
1428
- FROM "${TABLE_MESSAGES}"
1429
- WHERE thread_id = (SELECT thread_id FROM target_thread)
1430
- ),
1431
- target_positions AS (
1432
- SELECT row_num as target_pos
1433
- FROM numbered_messages
1434
- WHERE id = ?
1435
- )
1436
- SELECT DISTINCT m.*
1437
- FROM numbered_messages m
1438
- CROSS JOIN target_positions t
1439
- WHERE m.row_num BETWEEN (t.target_pos - ?) AND (t.target_pos + ?)
1440
- )
1441
- `
1442
- // Keep ASC for final sorting after fetching context
1443
- );
1444
- params.push(id, id, withPreviousMessages, withNextMessages);
1445
- }
1446
- const finalQuery = unionQueries.join(" UNION ALL ") + ' ORDER BY "createdAt" ASC';
1447
- const includedResult = await this.client.execute({ sql: finalQuery, args: params });
1448
- const includedRows = includedResult.rows?.map((row) => this.parseRow(row));
1449
- const seen = /* @__PURE__ */ new Set();
1450
- const dedupedRows = includedRows.filter((row) => {
1451
- if (seen.has(row.id)) return false;
1452
- seen.add(row.id);
1453
- return true;
1304
+ /**
1305
+ * Checks if a column exists in the specified table.
1306
+ *
1307
+ * @param table - The name of the table to check
1308
+ * @param column - The name of the column to look for
1309
+ * @returns `true` if the column exists in the table, `false` otherwise
1310
+ */
1311
+ async hasColumn(table, column) {
1312
+ const sanitizedTable = parseSqlIdentifier(table, "table name");
1313
+ const result = await this.client.execute({
1314
+ sql: `PRAGMA table_info("${sanitizedTable}")`
1454
1315
  });
1455
- return dedupedRows;
1316
+ return result.rows?.some((row) => row.name === column);
1317
+ }
1318
+ /**
1319
+ * Internal insert implementation without retry logic.
1320
+ */
1321
+ async doInsert({
1322
+ tableName,
1323
+ record
1324
+ }) {
1325
+ await this.client.execute(
1326
+ prepareStatement({
1327
+ tableName,
1328
+ record
1329
+ })
1330
+ );
1331
+ }
1332
+ /**
1333
+ * Inserts or replaces a record in the specified table with automatic retry on lock errors.
1334
+ *
1335
+ * @param args - The insert arguments
1336
+ * @param args.tableName - The name of the table to insert into
1337
+ * @param args.record - The record to insert (key-value pairs)
1338
+ */
1339
+ insert(args) {
1340
+ return this.executeWriteOperationWithRetry(() => this.doInsert(args), `insert into table ${args.tableName}`);
1456
1341
  }
1457
- async listMessagesById({ messageIds }) {
1458
- if (messageIds.length === 0) return { messages: [] };
1459
- try {
1460
- const sql = `
1461
- SELECT
1462
- id,
1463
- content,
1464
- role,
1465
- type,
1466
- "createdAt",
1467
- thread_id,
1468
- "resourceId"
1469
- FROM "${TABLE_MESSAGES}"
1470
- WHERE id IN (${messageIds.map(() => "?").join(", ")})
1471
- ORDER BY "createdAt" DESC
1472
- `;
1473
- const result = await this.client.execute({ sql, args: messageIds });
1474
- if (!result.rows) return { messages: [] };
1475
- const list = new MessageList().add(result.rows.map(this.parseRow), "memory");
1476
- return { messages: list.get.all.db() };
1477
- } catch (error) {
1342
+ /**
1343
+ * Internal update implementation without retry logic.
1344
+ */
1345
+ async doUpdate({
1346
+ tableName,
1347
+ keys,
1348
+ data
1349
+ }) {
1350
+ await this.client.execute(prepareUpdateStatement({ tableName, updates: data, keys }));
1351
+ }
1352
+ /**
1353
+ * Updates a record in the specified table with automatic retry on lock errors.
1354
+ *
1355
+ * @param args - The update arguments
1356
+ * @param args.tableName - The name of the table to update
1357
+ * @param args.keys - The key(s) identifying the record to update
1358
+ * @param args.data - The fields to update (key-value pairs)
1359
+ */
1360
+ update(args) {
1361
+ return this.executeWriteOperationWithRetry(() => this.doUpdate(args), `update table ${args.tableName}`);
1362
+ }
1363
+ /**
1364
+ * Internal batch insert implementation without retry logic.
1365
+ */
1366
+ async doBatchInsert({
1367
+ tableName,
1368
+ records
1369
+ }) {
1370
+ if (records.length === 0) return;
1371
+ const batchStatements = records.map((r) => prepareStatement({ tableName, record: r }));
1372
+ await this.client.batch(batchStatements, "write");
1373
+ }
1374
+ /**
1375
+ * Inserts multiple records in a single batch transaction with automatic retry on lock errors.
1376
+ *
1377
+ * @param args - The batch insert arguments
1378
+ * @param args.tableName - The name of the table to insert into
1379
+ * @param args.records - Array of records to insert
1380
+ * @throws {MastraError} When the batch insert fails after retries
1381
+ */
1382
+ async batchInsert(args) {
1383
+ return this.executeWriteOperationWithRetry(
1384
+ () => this.doBatchInsert(args),
1385
+ `batch insert into table ${args.tableName}`
1386
+ ).catch((error) => {
1478
1387
  throw new MastraError(
1479
1388
  {
1480
- id: createStorageErrorId("LIBSQL", "LIST_MESSAGES_BY_ID", "FAILED"),
1389
+ id: createStorageErrorId("LIBSQL", "BATCH_INSERT", "FAILED"),
1481
1390
  domain: ErrorDomain.STORAGE,
1482
1391
  category: ErrorCategory.THIRD_PARTY,
1483
- details: { messageIds: JSON.stringify(messageIds) }
1392
+ details: {
1393
+ tableName: args.tableName
1394
+ }
1484
1395
  },
1485
1396
  error
1486
1397
  );
1487
- }
1398
+ });
1488
1399
  }
1489
- async listMessages(args) {
1490
- const { threadId, resourceId, include, filter, perPage: perPageInput, page = 0, orderBy } = args;
1491
- const threadIds = Array.isArray(threadId) ? threadId : [threadId];
1492
- if (threadIds.length === 0 || threadIds.some((id) => !id.trim())) {
1400
+ /**
1401
+ * Internal batch update implementation without retry logic.
1402
+ * Each record can be updated based on single or composite keys.
1403
+ */
1404
+ async doBatchUpdate({
1405
+ tableName,
1406
+ updates
1407
+ }) {
1408
+ if (updates.length === 0) return;
1409
+ const batchStatements = updates.map(
1410
+ ({ keys, data }) => prepareUpdateStatement({
1411
+ tableName,
1412
+ updates: data,
1413
+ keys
1414
+ })
1415
+ );
1416
+ await this.client.batch(batchStatements, "write");
1417
+ }
1418
+ /**
1419
+ * Updates multiple records in a single batch transaction with automatic retry on lock errors.
1420
+ * Each record can be updated based on single or composite keys.
1421
+ *
1422
+ * @param args - The batch update arguments
1423
+ * @param args.tableName - The name of the table to update
1424
+ * @param args.updates - Array of update operations, each containing keys and data
1425
+ * @throws {MastraError} When the batch update fails after retries
1426
+ */
1427
+ async batchUpdate(args) {
1428
+ return this.executeWriteOperationWithRetry(
1429
+ () => this.doBatchUpdate(args),
1430
+ `batch update in table ${args.tableName}`
1431
+ ).catch((error) => {
1493
1432
  throw new MastraError(
1494
1433
  {
1495
- id: createStorageErrorId("LIBSQL", "LIST_MESSAGES", "INVALID_THREAD_ID"),
1434
+ id: createStorageErrorId("LIBSQL", "BATCH_UPDATE", "FAILED"),
1496
1435
  domain: ErrorDomain.STORAGE,
1497
1436
  category: ErrorCategory.THIRD_PARTY,
1498
- details: { threadId: Array.isArray(threadId) ? threadId.join(",") : threadId }
1437
+ details: {
1438
+ tableName: args.tableName
1439
+ }
1499
1440
  },
1500
- new Error("threadId must be a non-empty string or array of non-empty strings")
1441
+ error
1501
1442
  );
1502
- }
1503
- if (page < 0) {
1443
+ });
1444
+ }
1445
+ /**
1446
+ * Internal batch delete implementation without retry logic.
1447
+ * Each record can be deleted based on single or composite keys.
1448
+ */
1449
+ async doBatchDelete({
1450
+ tableName,
1451
+ keys
1452
+ }) {
1453
+ if (keys.length === 0) return;
1454
+ const batchStatements = keys.map(
1455
+ (keyObj) => prepareDeleteStatement({
1456
+ tableName,
1457
+ keys: keyObj
1458
+ })
1459
+ );
1460
+ await this.client.batch(batchStatements, "write");
1461
+ }
1462
+ /**
1463
+ * Deletes multiple records in a single batch transaction with automatic retry on lock errors.
1464
+ * Each record can be deleted based on single or composite keys.
1465
+ *
1466
+ * @param args - The batch delete arguments
1467
+ * @param args.tableName - The name of the table to delete from
1468
+ * @param args.keys - Array of key objects identifying records to delete
1469
+ * @throws {MastraError} When the batch delete fails after retries
1470
+ */
1471
+ async batchDelete({
1472
+ tableName,
1473
+ keys
1474
+ }) {
1475
+ return this.executeWriteOperationWithRetry(
1476
+ () => this.doBatchDelete({ tableName, keys }),
1477
+ `batch delete from table ${tableName}`
1478
+ ).catch((error) => {
1504
1479
  throw new MastraError(
1505
1480
  {
1506
- id: createStorageErrorId("LIBSQL", "LIST_MESSAGES", "INVALID_PAGE"),
1507
- domain: ErrorDomain.STORAGE,
1508
- category: ErrorCategory.USER,
1509
- details: { page }
1510
- },
1511
- new Error("page must be >= 0")
1512
- );
1513
- }
1514
- const perPage = normalizePerPage(perPageInput, 40);
1515
- const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
1516
- try {
1517
- const { field, direction } = this.parseOrderBy(orderBy, "ASC");
1518
- const orderByStatement = `ORDER BY "${field}" ${direction}`;
1519
- const threadPlaceholders = threadIds.map(() => "?").join(", ");
1520
- const conditions = [`thread_id IN (${threadPlaceholders})`];
1521
- const queryParams = [...threadIds];
1522
- if (resourceId) {
1523
- conditions.push(`"resourceId" = ?`);
1524
- queryParams.push(resourceId);
1525
- }
1526
- if (filter?.dateRange?.start) {
1527
- conditions.push(`"createdAt" >= ?`);
1528
- queryParams.push(
1529
- filter.dateRange.start instanceof Date ? filter.dateRange.start.toISOString() : filter.dateRange.start
1530
- );
1531
- }
1532
- if (filter?.dateRange?.end) {
1533
- conditions.push(`"createdAt" <= ?`);
1534
- queryParams.push(
1535
- filter.dateRange.end instanceof Date ? filter.dateRange.end.toISOString() : filter.dateRange.end
1536
- );
1537
- }
1538
- const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1539
- const countResult = await this.client.execute({
1540
- sql: `SELECT COUNT(*) as count FROM ${TABLE_MESSAGES} ${whereClause}`,
1541
- args: queryParams
1542
- });
1543
- const total = Number(countResult.rows?.[0]?.count ?? 0);
1544
- const limitValue = perPageInput === false ? total : perPage;
1545
- const dataResult = await this.client.execute({
1546
- sql: `SELECT id, content, role, type, "createdAt", "resourceId", "thread_id" FROM ${TABLE_MESSAGES} ${whereClause} ${orderByStatement} LIMIT ? OFFSET ?`,
1547
- args: [...queryParams, limitValue, offset]
1548
- });
1549
- const messages = (dataResult.rows || []).map((row) => this.parseRow(row));
1550
- if (total === 0 && messages.length === 0 && (!include || include.length === 0)) {
1551
- return {
1552
- messages: [],
1553
- total: 0,
1554
- page,
1555
- perPage: perPageForResponse,
1556
- hasMore: false
1557
- };
1558
- }
1559
- const messageIds = new Set(messages.map((m) => m.id));
1560
- if (include && include.length > 0) {
1561
- const includeMessages = await this._getIncludedMessages({ include });
1562
- if (includeMessages) {
1563
- for (const includeMsg of includeMessages) {
1564
- if (!messageIds.has(includeMsg.id)) {
1565
- messages.push(includeMsg);
1566
- messageIds.add(includeMsg.id);
1567
- }
1568
- }
1569
- }
1570
- }
1571
- const list = new MessageList().add(messages, "memory");
1572
- let finalMessages = list.get.all.db();
1573
- finalMessages = finalMessages.sort((a, b) => {
1574
- const isDateField = field === "createdAt" || field === "updatedAt";
1575
- const aValue = isDateField ? new Date(a[field]).getTime() : a[field];
1576
- const bValue = isDateField ? new Date(b[field]).getTime() : b[field];
1577
- if (typeof aValue === "number" && typeof bValue === "number") {
1578
- return direction === "ASC" ? aValue - bValue : bValue - aValue;
1579
- }
1580
- return direction === "ASC" ? String(aValue).localeCompare(String(bValue)) : String(bValue).localeCompare(String(aValue));
1581
- });
1582
- const threadIdSet = new Set(threadIds);
1583
- const returnedThreadMessageIds = new Set(
1584
- finalMessages.filter((m) => m.threadId && threadIdSet.has(m.threadId)).map((m) => m.id)
1585
- );
1586
- const allThreadMessagesReturned = returnedThreadMessageIds.size >= total;
1587
- const hasMore = perPageInput !== false && !allThreadMessagesReturned && offset + perPage < total;
1588
- return {
1589
- messages: finalMessages,
1590
- total,
1591
- page,
1592
- perPage: perPageForResponse,
1593
- hasMore
1594
- };
1595
- } catch (error) {
1596
- const mastraError = new MastraError(
1597
- {
1598
- id: createStorageErrorId("LIBSQL", "LIST_MESSAGES", "FAILED"),
1481
+ id: createStorageErrorId("LIBSQL", "BATCH_DELETE", "FAILED"),
1599
1482
  domain: ErrorDomain.STORAGE,
1600
1483
  category: ErrorCategory.THIRD_PARTY,
1601
1484
  details: {
1602
- threadId: Array.isArray(threadId) ? threadId.join(",") : threadId,
1603
- resourceId: resourceId ?? ""
1485
+ tableName
1604
1486
  }
1605
1487
  },
1606
1488
  error
1607
1489
  );
1608
- this.logger?.error?.(mastraError.toString());
1609
- this.logger?.trackException?.(mastraError);
1610
- return {
1611
- messages: [],
1612
- total: 0,
1613
- page,
1614
- perPage: perPageForResponse,
1615
- hasMore: false
1616
- };
1490
+ });
1491
+ }
1492
+ /**
1493
+ * Internal single-record delete implementation without retry logic.
1494
+ */
1495
+ async doDelete({ tableName, keys }) {
1496
+ await this.client.execute(prepareDeleteStatement({ tableName, keys }));
1497
+ }
1498
+ /**
1499
+ * Deletes a single record from the specified table with automatic retry on lock errors.
1500
+ *
1501
+ * @param args - The delete arguments
1502
+ * @param args.tableName - The name of the table to delete from
1503
+ * @param args.keys - The key(s) identifying the record to delete
1504
+ * @throws {MastraError} When the delete fails after retries
1505
+ */
1506
+ async delete(args) {
1507
+ return this.executeWriteOperationWithRetry(() => this.doDelete(args), `delete from table ${args.tableName}`).catch(
1508
+ (error) => {
1509
+ throw new MastraError(
1510
+ {
1511
+ id: createStorageErrorId("LIBSQL", "DELETE", "FAILED"),
1512
+ domain: ErrorDomain.STORAGE,
1513
+ category: ErrorCategory.THIRD_PARTY,
1514
+ details: {
1515
+ tableName: args.tableName
1516
+ }
1517
+ },
1518
+ error
1519
+ );
1520
+ }
1521
+ );
1522
+ }
1523
+ /**
1524
+ * Selects a single record from the specified table by key(s).
1525
+ * Returns the most recently created record if multiple matches exist.
1526
+ * Automatically parses JSON string values back to objects/arrays.
1527
+ *
1528
+ * @typeParam R - The expected return type of the record
1529
+ * @param args - The select arguments
1530
+ * @param args.tableName - The name of the table to select from
1531
+ * @param args.keys - The key(s) identifying the record to select
1532
+ * @returns The matching record or `null` if not found
1533
+ */
1534
+ async select({ tableName, keys }) {
1535
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
1536
+ const parsedKeys = Object.keys(keys).map((key) => parseSqlIdentifier(key, "column name"));
1537
+ const conditions = parsedKeys.map((key) => `${key} = ?`).join(" AND ");
1538
+ const values = Object.values(keys);
1539
+ const result = await this.client.execute({
1540
+ sql: `SELECT * FROM ${parsedTableName} WHERE ${conditions} ORDER BY createdAt DESC LIMIT 1`,
1541
+ args: values
1542
+ });
1543
+ if (!result.rows || result.rows.length === 0) {
1544
+ return null;
1545
+ }
1546
+ const row = result.rows[0];
1547
+ const parsed = Object.fromEntries(
1548
+ Object.entries(row || {}).map(([k, v]) => {
1549
+ try {
1550
+ return [k, typeof v === "string" ? v.startsWith("{") || v.startsWith("[") ? JSON.parse(v) : v : v];
1551
+ } catch {
1552
+ return [k, v];
1553
+ }
1554
+ })
1555
+ );
1556
+ return parsed;
1557
+ }
1558
+ /**
1559
+ * Selects multiple records from the specified table with optional filtering, ordering, and pagination.
1560
+ *
1561
+ * @typeParam R - The expected return type of each record
1562
+ * @param args - The select arguments
1563
+ * @param args.tableName - The name of the table to select from
1564
+ * @param args.whereClause - Optional WHERE clause with SQL string and arguments
1565
+ * @param args.orderBy - Optional ORDER BY clause (e.g., "createdAt DESC")
1566
+ * @param args.offset - Optional offset for pagination
1567
+ * @param args.limit - Optional limit for pagination
1568
+ * @param args.args - Optional additional query arguments
1569
+ * @returns Array of matching records
1570
+ */
1571
+ async selectMany({
1572
+ tableName,
1573
+ whereClause,
1574
+ orderBy,
1575
+ offset,
1576
+ limit,
1577
+ args
1578
+ }) {
1579
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
1580
+ let statement = `SELECT * FROM ${parsedTableName}`;
1581
+ if (whereClause?.sql) {
1582
+ statement += ` ${whereClause.sql}`;
1583
+ }
1584
+ if (orderBy) {
1585
+ statement += ` ORDER BY ${orderBy}`;
1586
+ }
1587
+ if (limit) {
1588
+ statement += ` LIMIT ${limit}`;
1589
+ }
1590
+ if (offset) {
1591
+ statement += ` OFFSET ${offset}`;
1592
+ }
1593
+ const result = await this.client.execute({
1594
+ sql: statement,
1595
+ args: [...whereClause?.args ?? [], ...args ?? []]
1596
+ });
1597
+ return result.rows;
1598
+ }
1599
+ /**
1600
+ * Returns the total count of records matching the optional WHERE clause.
1601
+ *
1602
+ * @param args - The count arguments
1603
+ * @param args.tableName - The name of the table to count from
1604
+ * @param args.whereClause - Optional WHERE clause with SQL string and arguments
1605
+ * @returns The total count of matching records
1606
+ */
1607
+ async selectTotalCount({
1608
+ tableName,
1609
+ whereClause
1610
+ }) {
1611
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
1612
+ const statement = `SELECT COUNT(*) as count FROM ${parsedTableName} ${whereClause ? `${whereClause.sql}` : ""}`;
1613
+ const result = await this.client.execute({
1614
+ sql: statement,
1615
+ args: whereClause?.args ?? []
1616
+ });
1617
+ if (!result.rows || result.rows.length === 0) {
1618
+ return 0;
1617
1619
  }
1620
+ return result.rows[0]?.count ?? 0;
1618
1621
  }
1619
- async saveMessages({ messages }) {
1620
- if (messages.length === 0) return { messages };
1622
+ /**
1623
+ * Maps a storage column type to its SQLite equivalent.
1624
+ */
1625
+ getSqlType(type) {
1626
+ switch (type) {
1627
+ case "bigint":
1628
+ return "INTEGER";
1629
+ // SQLite uses INTEGER for all integer sizes
1630
+ case "timestamp":
1631
+ return "TEXT";
1632
+ // Store timestamps as ISO strings in SQLite
1633
+ case "float":
1634
+ return "REAL";
1635
+ // SQLite's floating point type
1636
+ case "boolean":
1637
+ return "INTEGER";
1638
+ // SQLite uses 0/1 for booleans
1639
+ case "jsonb":
1640
+ return "TEXT";
1641
+ // Store JSON as TEXT in SQLite
1642
+ default:
1643
+ return getSqlType(type);
1644
+ }
1645
+ }
1646
+ /**
1647
+ * Creates a table if it doesn't exist based on the provided schema.
1648
+ *
1649
+ * @param args - The create table arguments
1650
+ * @param args.tableName - The name of the table to create
1651
+ * @param args.schema - The schema definition for the table columns
1652
+ */
1653
+ async createTable({
1654
+ tableName,
1655
+ schema
1656
+ }) {
1621
1657
  try {
1622
- const threadId = messages[0]?.threadId;
1623
- if (!threadId) {
1624
- throw new Error("Thread ID is required");
1625
- }
1626
- const batchStatements = messages.map((message) => {
1627
- const time = message.createdAt || /* @__PURE__ */ new Date();
1628
- if (!message.threadId) {
1629
- throw new Error(
1630
- `Expected to find a threadId for message, but couldn't find one. An unexpected error has occurred.`
1631
- );
1632
- }
1633
- if (!message.resourceId) {
1634
- throw new Error(
1635
- `Expected to find a resourceId for message, but couldn't find one. An unexpected error has occurred.`
1636
- );
1637
- }
1638
- return {
1639
- sql: `INSERT INTO "${TABLE_MESSAGES}" (id, thread_id, content, role, type, "createdAt", "resourceId")
1640
- VALUES (?, ?, ?, ?, ?, ?, ?)
1641
- ON CONFLICT(id) DO UPDATE SET
1642
- thread_id=excluded.thread_id,
1643
- content=excluded.content,
1644
- role=excluded.role,
1645
- type=excluded.type,
1646
- "resourceId"=excluded."resourceId"
1647
- `,
1648
- args: [
1649
- message.id,
1650
- message.threadId,
1651
- typeof message.content === "object" ? JSON.stringify(message.content) : message.content,
1652
- message.role,
1653
- message.type || "v2",
1654
- time instanceof Date ? time.toISOString() : time,
1655
- message.resourceId
1656
- ]
1657
- };
1658
- });
1659
- const now = (/* @__PURE__ */ new Date()).toISOString();
1660
- batchStatements.push({
1661
- sql: `UPDATE "${TABLE_THREADS}" SET "updatedAt" = ? WHERE id = ?`,
1662
- args: [now, threadId]
1658
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
1659
+ const columnDefinitions = Object.entries(schema).map(([colName, colDef]) => {
1660
+ const type = this.getSqlType(colDef.type);
1661
+ const nullable = colDef.nullable === false ? "NOT NULL" : "";
1662
+ const primaryKey = colDef.primaryKey ? "PRIMARY KEY" : "";
1663
+ return `"${colName}" ${type} ${nullable} ${primaryKey}`.trim();
1663
1664
  });
1664
- const BATCH_SIZE = 50;
1665
- const messageStatements = batchStatements.slice(0, -1);
1666
- const threadUpdateStatement = batchStatements[batchStatements.length - 1];
1667
- for (let i = 0; i < messageStatements.length; i += BATCH_SIZE) {
1668
- const batch = messageStatements.slice(i, i + BATCH_SIZE);
1669
- if (batch.length > 0) {
1670
- await this.client.batch(batch, "write");
1671
- }
1672
- }
1673
- if (threadUpdateStatement) {
1674
- await this.client.execute(threadUpdateStatement);
1665
+ const tableConstraints = [];
1666
+ if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
1667
+ tableConstraints.push("UNIQUE (workflow_name, run_id)");
1668
+ }
1669
+ const allDefinitions = [...columnDefinitions, ...tableConstraints].join(",\n ");
1670
+ const sql = `CREATE TABLE IF NOT EXISTS ${parsedTableName} (
1671
+ ${allDefinitions}
1672
+ )`;
1673
+ await this.client.execute(sql);
1674
+ this.logger.debug(`LibSQLDB: Created table ${tableName}`);
1675
+ if (tableName === TABLE_SPANS) {
1676
+ await this.migrateSpansTable();
1675
1677
  }
1676
- const list = new MessageList().add(messages, "memory");
1677
- return { messages: list.get.all.db() };
1678
1678
  } catch (error) {
1679
1679
  throw new MastraError(
1680
1680
  {
1681
- id: createStorageErrorId("LIBSQL", "SAVE_MESSAGES", "FAILED"),
1681
+ id: createStorageErrorId("LIBSQL", "CREATE_TABLE", "FAILED"),
1682
1682
  domain: ErrorDomain.STORAGE,
1683
- category: ErrorCategory.THIRD_PARTY
1683
+ category: ErrorCategory.THIRD_PARTY,
1684
+ details: { tableName }
1684
1685
  },
1685
1686
  error
1686
1687
  );
1687
1688
  }
1688
1689
  }
1689
- async updateMessages({
1690
- messages
1691
- }) {
1692
- if (messages.length === 0) {
1693
- return [];
1694
- }
1695
- const messageIds = messages.map((m) => m.id);
1696
- const placeholders = messageIds.map(() => "?").join(",");
1697
- const selectSql = `SELECT * FROM ${TABLE_MESSAGES} WHERE id IN (${placeholders})`;
1698
- const existingResult = await this.client.execute({ sql: selectSql, args: messageIds });
1699
- const existingMessages = existingResult.rows.map((row) => this.parseRow(row));
1700
- if (existingMessages.length === 0) {
1701
- return [];
1702
- }
1703
- const batchStatements = [];
1704
- const threadIdsToUpdate = /* @__PURE__ */ new Set();
1705
- const columnMapping = {
1706
- threadId: "thread_id"
1707
- };
1708
- for (const existingMessage of existingMessages) {
1709
- const updatePayload = messages.find((m) => m.id === existingMessage.id);
1710
- if (!updatePayload) continue;
1711
- const { id, ...fieldsToUpdate } = updatePayload;
1712
- if (Object.keys(fieldsToUpdate).length === 0) continue;
1713
- threadIdsToUpdate.add(existingMessage.threadId);
1714
- if (updatePayload.threadId && updatePayload.threadId !== existingMessage.threadId) {
1715
- threadIdsToUpdate.add(updatePayload.threadId);
1716
- }
1717
- const setClauses = [];
1718
- const args = [];
1719
- const updatableFields = { ...fieldsToUpdate };
1720
- if (updatableFields.content) {
1721
- const newContent = {
1722
- ...existingMessage.content,
1723
- ...updatableFields.content,
1724
- // Deep merge metadata if it exists on both
1725
- ...existingMessage.content?.metadata && updatableFields.content.metadata ? {
1726
- metadata: {
1727
- ...existingMessage.content.metadata,
1728
- ...updatableFields.content.metadata
1729
- }
1730
- } : {}
1731
- };
1732
- setClauses.push(`${parseSqlIdentifier("content", "column name")} = ?`);
1733
- args.push(JSON.stringify(newContent));
1734
- delete updatableFields.content;
1735
- }
1736
- for (const key in updatableFields) {
1737
- if (Object.prototype.hasOwnProperty.call(updatableFields, key)) {
1738
- const dbKey = columnMapping[key] || key;
1739
- setClauses.push(`${parseSqlIdentifier(dbKey, "column name")} = ?`);
1740
- let value = updatableFields[key];
1741
- if (typeof value === "object" && value !== null) {
1742
- value = JSON.stringify(value);
1743
- }
1744
- args.push(value);
1690
+ /**
1691
+ * Migrates the spans table schema from OLD_SPAN_SCHEMA to current SPAN_SCHEMA.
1692
+ * This adds new columns that don't exist in old schema.
1693
+ */
1694
+ async migrateSpansTable() {
1695
+ const schema = TABLE_SCHEMAS[TABLE_SPANS];
1696
+ try {
1697
+ for (const [columnName, columnDef] of Object.entries(schema)) {
1698
+ const columnExists = await this.hasColumn(TABLE_SPANS, columnName);
1699
+ if (!columnExists) {
1700
+ const sqlType = this.getSqlType(columnDef.type);
1701
+ const alterSql = `ALTER TABLE "${TABLE_SPANS}" ADD COLUMN "${columnName}" ${sqlType}`;
1702
+ await this.client.execute(alterSql);
1703
+ this.logger.debug(`LibSQLDB: Added column '${columnName}' to ${TABLE_SPANS}`);
1745
1704
  }
1746
1705
  }
1747
- if (setClauses.length === 0) continue;
1748
- args.push(id);
1749
- const sql = `UPDATE ${TABLE_MESSAGES} SET ${setClauses.join(", ")} WHERE id = ?`;
1750
- batchStatements.push({ sql, args });
1751
- }
1752
- if (batchStatements.length === 0) {
1753
- return existingMessages;
1754
- }
1755
- const now = (/* @__PURE__ */ new Date()).toISOString();
1756
- for (const threadId of threadIdsToUpdate) {
1757
- if (threadId) {
1758
- batchStatements.push({
1759
- sql: `UPDATE ${TABLE_THREADS} SET updatedAt = ? WHERE id = ?`,
1760
- args: [now, threadId]
1761
- });
1762
- }
1706
+ this.logger.info(`LibSQLDB: Migration completed for ${TABLE_SPANS}`);
1707
+ } catch (error) {
1708
+ this.logger.warn(`LibSQLDB: Failed to migrate spans table ${TABLE_SPANS}:`, error);
1763
1709
  }
1764
- await this.client.batch(batchStatements, "write");
1765
- const updatedResult = await this.client.execute({ sql: selectSql, args: messageIds });
1766
- return updatedResult.rows.map((row) => this.parseRow(row));
1767
1710
  }
1768
- async deleteMessages(messageIds) {
1769
- if (!messageIds || messageIds.length === 0) {
1770
- return;
1711
+ /**
1712
+ * Gets a default value for a column type (used when adding NOT NULL columns).
1713
+ */
1714
+ getDefaultValue(type) {
1715
+ switch (type) {
1716
+ case "text":
1717
+ case "uuid":
1718
+ return "DEFAULT ''";
1719
+ case "integer":
1720
+ case "bigint":
1721
+ case "float":
1722
+ return "DEFAULT 0";
1723
+ case "boolean":
1724
+ return "DEFAULT 0";
1725
+ case "jsonb":
1726
+ return "DEFAULT '{}'";
1727
+ case "timestamp":
1728
+ return "DEFAULT CURRENT_TIMESTAMP";
1729
+ default:
1730
+ return "DEFAULT ''";
1771
1731
  }
1772
- try {
1773
- const BATCH_SIZE = 100;
1774
- const threadIds = /* @__PURE__ */ new Set();
1775
- const tx = await this.client.transaction("write");
1776
- try {
1777
- for (let i = 0; i < messageIds.length; i += BATCH_SIZE) {
1778
- const batch = messageIds.slice(i, i + BATCH_SIZE);
1779
- const placeholders = batch.map(() => "?").join(",");
1780
- const result = await tx.execute({
1781
- sql: `SELECT DISTINCT thread_id FROM "${TABLE_MESSAGES}" WHERE id IN (${placeholders})`,
1782
- args: batch
1783
- });
1784
- result.rows?.forEach((row) => {
1785
- if (row.thread_id) threadIds.add(row.thread_id);
1786
- });
1787
- await tx.execute({
1788
- sql: `DELETE FROM "${TABLE_MESSAGES}" WHERE id IN (${placeholders})`,
1789
- args: batch
1790
- });
1791
- }
1792
- if (threadIds.size > 0) {
1793
- const now = (/* @__PURE__ */ new Date()).toISOString();
1794
- for (const threadId of threadIds) {
1795
- await tx.execute({
1796
- sql: `UPDATE "${TABLE_THREADS}" SET "updatedAt" = ? WHERE id = ?`,
1797
- args: [now, threadId]
1798
- });
1799
- }
1732
+ }
1733
+ /**
1734
+ * Alters an existing table to add missing columns.
1735
+ * Used for schema migrations when new columns are added.
1736
+ *
1737
+ * @param args - The alter table arguments
1738
+ * @param args.tableName - The name of the table to alter
1739
+ * @param args.schema - The full schema definition for the table
1740
+ * @param args.ifNotExists - Array of column names to add if they don't exist
1741
+ */
1742
+ async alterTable({
1743
+ tableName,
1744
+ schema,
1745
+ ifNotExists
1746
+ }) {
1747
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
1748
+ try {
1749
+ const tableInfo = await this.client.execute({
1750
+ sql: `PRAGMA table_info("${parsedTableName}")`
1751
+ });
1752
+ const existingColumns = new Set((tableInfo.rows || []).map((row) => row.name?.toLowerCase()));
1753
+ for (const columnName of ifNotExists) {
1754
+ if (!existingColumns.has(columnName.toLowerCase()) && schema[columnName]) {
1755
+ const columnDef = schema[columnName];
1756
+ const sqlType = this.getSqlType(columnDef.type);
1757
+ const defaultValue = this.getDefaultValue(columnDef.type);
1758
+ const alterSql = `ALTER TABLE ${parsedTableName} ADD COLUMN "${columnName}" ${sqlType} ${defaultValue}`;
1759
+ await this.client.execute(alterSql);
1760
+ this.logger.debug(`LibSQLDB: Added column ${columnName} to table ${tableName}`);
1800
1761
  }
1801
- await tx.commit();
1802
- } catch (error) {
1803
- await tx.rollback();
1804
- throw error;
1805
1762
  }
1806
1763
  } catch (error) {
1807
1764
  throw new MastraError(
1808
1765
  {
1809
- id: createStorageErrorId("LIBSQL", "DELETE_MESSAGES", "FAILED"),
1766
+ id: createStorageErrorId("LIBSQL", "ALTER_TABLE", "FAILED"),
1810
1767
  domain: ErrorDomain.STORAGE,
1811
1768
  category: ErrorCategory.THIRD_PARTY,
1812
- details: { messageIds: messageIds.join(", ") }
1769
+ details: { tableName }
1813
1770
  },
1814
1771
  error
1815
1772
  );
1816
1773
  }
1817
1774
  }
1818
- async getResourceById({ resourceId }) {
1819
- const result = await this.operations.load({
1820
- tableName: TABLE_RESOURCES,
1821
- keys: { id: resourceId }
1822
- });
1823
- if (!result) {
1824
- return null;
1775
+ /**
1776
+ * Deletes all records from the specified table.
1777
+ * Errors are logged but not thrown.
1778
+ *
1779
+ * @param args - The delete arguments
1780
+ * @param args.tableName - The name of the table to clear
1781
+ */
1782
+ async deleteData({ tableName }) {
1783
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
1784
+ try {
1785
+ await this.client.execute(`DELETE FROM ${parsedTableName}`);
1786
+ } catch (e) {
1787
+ const mastraError = new MastraError(
1788
+ {
1789
+ id: createStorageErrorId("LIBSQL", "CLEAR_TABLE", "FAILED"),
1790
+ domain: ErrorDomain.STORAGE,
1791
+ category: ErrorCategory.THIRD_PARTY,
1792
+ details: {
1793
+ tableName
1794
+ }
1795
+ },
1796
+ e
1797
+ );
1798
+ this.logger?.trackException?.(mastraError);
1799
+ this.logger?.error?.(mastraError.toString());
1825
1800
  }
1826
- return {
1827
- ...result,
1828
- // Ensure workingMemory is always returned as a string, even if auto-parsed as JSON
1829
- workingMemory: result.workingMemory && typeof result.workingMemory === "object" ? JSON.stringify(result.workingMemory) : result.workingMemory,
1830
- metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata,
1831
- createdAt: new Date(result.createdAt),
1832
- updatedAt: new Date(result.updatedAt)
1833
- };
1834
1801
  }
1835
- async saveResource({ resource }) {
1836
- await this.operations.insert({
1837
- tableName: TABLE_RESOURCES,
1838
- record: {
1839
- ...resource,
1840
- metadata: JSON.stringify(resource.metadata)
1841
- }
1842
- });
1843
- return resource;
1802
+ };
1803
+
1804
+ // src/storage/domains/agents/index.ts
1805
+ var AgentsLibSQL = class extends AgentsStorage {
1806
+ #db;
1807
+ constructor(config) {
1808
+ super();
1809
+ const client = resolveClient(config);
1810
+ this.#db = new LibSQLDB({ client, maxRetries: config.maxRetries, initialBackoffMs: config.initialBackoffMs });
1844
1811
  }
1845
- async updateResource({
1846
- resourceId,
1847
- workingMemory,
1848
- metadata
1849
- }) {
1850
- const existingResource = await this.getResourceById({ resourceId });
1851
- if (!existingResource) {
1852
- const newResource = {
1853
- id: resourceId,
1854
- workingMemory,
1855
- metadata: metadata || {},
1856
- createdAt: /* @__PURE__ */ new Date(),
1857
- updatedAt: /* @__PURE__ */ new Date()
1812
+ async init() {
1813
+ await this.#db.createTable({ tableName: TABLE_AGENTS, schema: AGENTS_SCHEMA });
1814
+ }
1815
+ async dangerouslyClearAll() {
1816
+ await this.#db.deleteData({ tableName: TABLE_AGENTS });
1817
+ }
1818
+ parseJson(value, fieldName) {
1819
+ if (!value) return void 0;
1820
+ if (typeof value !== "string") return value;
1821
+ try {
1822
+ return JSON.parse(value);
1823
+ } catch (error) {
1824
+ const details = {
1825
+ value: value.length > 100 ? value.substring(0, 100) + "..." : value
1858
1826
  };
1859
- return this.saveResource({ resource: newResource });
1827
+ if (fieldName) {
1828
+ details.field = fieldName;
1829
+ }
1830
+ throw new MastraError(
1831
+ {
1832
+ id: createStorageErrorId("LIBSQL", "PARSE_JSON", "INVALID_JSON"),
1833
+ domain: ErrorDomain.STORAGE,
1834
+ category: ErrorCategory.SYSTEM,
1835
+ text: `Failed to parse JSON${fieldName ? ` for field "${fieldName}"` : ""}: ${error instanceof Error ? error.message : "Unknown error"}`,
1836
+ details
1837
+ },
1838
+ error
1839
+ );
1860
1840
  }
1861
- const updatedResource = {
1862
- ...existingResource,
1863
- workingMemory: workingMemory !== void 0 ? workingMemory : existingResource.workingMemory,
1864
- metadata: {
1865
- ...existingResource.metadata,
1866
- ...metadata
1867
- },
1868
- updatedAt: /* @__PURE__ */ new Date()
1841
+ }
1842
+ parseRow(row) {
1843
+ return {
1844
+ id: row.id,
1845
+ name: row.name,
1846
+ description: row.description,
1847
+ instructions: row.instructions,
1848
+ model: this.parseJson(row.model, "model"),
1849
+ tools: this.parseJson(row.tools, "tools"),
1850
+ defaultOptions: this.parseJson(row.defaultOptions, "defaultOptions"),
1851
+ workflows: this.parseJson(row.workflows, "workflows"),
1852
+ agents: this.parseJson(row.agents, "agents"),
1853
+ inputProcessors: this.parseJson(row.inputProcessors, "inputProcessors"),
1854
+ outputProcessors: this.parseJson(row.outputProcessors, "outputProcessors"),
1855
+ memory: this.parseJson(row.memory, "memory"),
1856
+ scorers: this.parseJson(row.scorers, "scorers"),
1857
+ metadata: this.parseJson(row.metadata, "metadata"),
1858
+ createdAt: new Date(row.createdAt),
1859
+ updatedAt: new Date(row.updatedAt)
1869
1860
  };
1870
- const updates = [];
1871
- const values = [];
1872
- if (workingMemory !== void 0) {
1873
- updates.push("workingMemory = ?");
1874
- values.push(workingMemory);
1875
- }
1876
- if (metadata) {
1877
- updates.push("metadata = ?");
1878
- values.push(JSON.stringify(updatedResource.metadata));
1879
- }
1880
- updates.push("updatedAt = ?");
1881
- values.push(updatedResource.updatedAt.toISOString());
1882
- values.push(resourceId);
1883
- await this.client.execute({
1884
- sql: `UPDATE ${TABLE_RESOURCES} SET ${updates.join(", ")} WHERE id = ?`,
1885
- args: values
1886
- });
1887
- return updatedResource;
1888
1861
  }
1889
- async getThreadById({ threadId }) {
1862
+ async getAgentById({ id }) {
1890
1863
  try {
1891
- const result = await this.operations.load({
1892
- tableName: TABLE_THREADS,
1893
- keys: { id: threadId }
1864
+ const result = await this.#db.select({
1865
+ tableName: TABLE_AGENTS,
1866
+ keys: { id }
1894
1867
  });
1895
- if (!result) {
1896
- return null;
1897
- }
1898
- return {
1899
- ...result,
1900
- metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata,
1901
- createdAt: new Date(result.createdAt),
1902
- updatedAt: new Date(result.updatedAt)
1903
- };
1868
+ return result ? this.parseRow(result) : null;
1904
1869
  } catch (error) {
1905
1870
  throw new MastraError(
1906
1871
  {
1907
- id: createStorageErrorId("LIBSQL", "GET_THREAD_BY_ID", "FAILED"),
1872
+ id: createStorageErrorId("LIBSQL", "GET_AGENT_BY_ID", "FAILED"),
1908
1873
  domain: ErrorDomain.STORAGE,
1909
1874
  category: ErrorCategory.THIRD_PARTY,
1910
- details: { threadId }
1875
+ details: { agentId: id }
1911
1876
  },
1912
1877
  error
1913
1878
  );
1914
1879
  }
1915
1880
  }
1916
- async listThreadsByResourceId(args) {
1917
- const { resourceId, page = 0, perPage: perPageInput, orderBy } = args;
1918
- if (page < 0) {
1881
+ async createAgent({ agent }) {
1882
+ try {
1883
+ const now = /* @__PURE__ */ new Date();
1884
+ await this.#db.insert({
1885
+ tableName: TABLE_AGENTS,
1886
+ record: {
1887
+ id: agent.id,
1888
+ name: agent.name,
1889
+ description: agent.description ?? null,
1890
+ instructions: agent.instructions,
1891
+ model: agent.model,
1892
+ tools: agent.tools ?? null,
1893
+ defaultOptions: agent.defaultOptions ?? null,
1894
+ workflows: agent.workflows ?? null,
1895
+ agents: agent.agents ?? null,
1896
+ inputProcessors: agent.inputProcessors ?? null,
1897
+ outputProcessors: agent.outputProcessors ?? null,
1898
+ memory: agent.memory ?? null,
1899
+ scorers: agent.scorers ?? null,
1900
+ metadata: agent.metadata ?? null,
1901
+ createdAt: now,
1902
+ updatedAt: now
1903
+ }
1904
+ });
1905
+ return {
1906
+ ...agent,
1907
+ createdAt: now,
1908
+ updatedAt: now
1909
+ };
1910
+ } catch (error) {
1919
1911
  throw new MastraError(
1920
1912
  {
1921
- id: createStorageErrorId("LIBSQL", "LIST_THREADS_BY_RESOURCE_ID", "INVALID_PAGE"),
1913
+ id: createStorageErrorId("LIBSQL", "CREATE_AGENT", "FAILED"),
1922
1914
  domain: ErrorDomain.STORAGE,
1923
- category: ErrorCategory.USER,
1924
- details: { page }
1915
+ category: ErrorCategory.THIRD_PARTY,
1916
+ details: { agentId: agent.id }
1925
1917
  },
1926
- new Error("page must be >= 0")
1918
+ error
1927
1919
  );
1928
1920
  }
1929
- const perPage = normalizePerPage(perPageInput, 100);
1930
- const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
1931
- const { field, direction } = this.parseOrderBy(orderBy);
1921
+ }
1922
+ async updateAgent({ id, ...updates }) {
1932
1923
  try {
1933
- const baseQuery = `FROM ${TABLE_THREADS} WHERE resourceId = ?`;
1934
- const queryParams = [resourceId];
1935
- const mapRowToStorageThreadType = (row) => ({
1936
- id: row.id,
1937
- resourceId: row.resourceId,
1938
- title: row.title,
1939
- createdAt: new Date(row.createdAt),
1940
- // Convert string to Date
1941
- updatedAt: new Date(row.updatedAt),
1942
- // Convert string to Date
1943
- metadata: typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata
1944
- });
1945
- const countResult = await this.client.execute({
1946
- sql: `SELECT COUNT(*) as count ${baseQuery}`,
1947
- args: queryParams
1948
- });
1949
- const total = Number(countResult.rows?.[0]?.count ?? 0);
1950
- if (total === 0) {
1951
- return {
1952
- threads: [],
1953
- total: 0,
1954
- page,
1955
- perPage: perPageForResponse,
1956
- hasMore: false
1957
- };
1924
+ const existingAgent = await this.getAgentById({ id });
1925
+ if (!existingAgent) {
1926
+ throw new MastraError({
1927
+ id: createStorageErrorId("LIBSQL", "UPDATE_AGENT", "NOT_FOUND"),
1928
+ domain: ErrorDomain.STORAGE,
1929
+ category: ErrorCategory.USER,
1930
+ text: `Agent ${id} not found`,
1931
+ details: { agentId: id }
1932
+ });
1958
1933
  }
1959
- const limitValue = perPageInput === false ? total : perPage;
1960
- const dataResult = await this.client.execute({
1961
- sql: `SELECT * ${baseQuery} ORDER BY "${field}" ${direction} LIMIT ? OFFSET ?`,
1962
- args: [...queryParams, limitValue, offset]
1963
- });
1964
- const threads = (dataResult.rows || []).map(mapRowToStorageThreadType);
1965
- return {
1966
- threads,
1967
- total,
1968
- page,
1969
- perPage: perPageForResponse,
1970
- hasMore: perPageInput === false ? false : offset + perPage < total
1934
+ const data = {
1935
+ updatedAt: /* @__PURE__ */ new Date()
1971
1936
  };
1937
+ if (updates.name !== void 0) data.name = updates.name;
1938
+ if (updates.description !== void 0) data.description = updates.description;
1939
+ if (updates.instructions !== void 0) data.instructions = updates.instructions;
1940
+ if (updates.model !== void 0) data.model = updates.model;
1941
+ if (updates.tools !== void 0) data.tools = updates.tools;
1942
+ if (updates.defaultOptions !== void 0) data.defaultOptions = updates.defaultOptions;
1943
+ if (updates.workflows !== void 0) data.workflows = updates.workflows;
1944
+ if (updates.agents !== void 0) data.agents = updates.agents;
1945
+ if (updates.inputProcessors !== void 0) data.inputProcessors = updates.inputProcessors;
1946
+ if (updates.outputProcessors !== void 0) data.outputProcessors = updates.outputProcessors;
1947
+ if (updates.memory !== void 0) data.memory = updates.memory;
1948
+ if (updates.scorers !== void 0) data.scorers = updates.scorers;
1949
+ if (updates.metadata !== void 0) {
1950
+ data.metadata = { ...existingAgent.metadata, ...updates.metadata };
1951
+ }
1952
+ if (Object.keys(data).length > 1) {
1953
+ await this.#db.update({
1954
+ tableName: TABLE_AGENTS,
1955
+ keys: { id },
1956
+ data
1957
+ });
1958
+ }
1959
+ const updatedAgent = await this.getAgentById({ id });
1960
+ if (!updatedAgent) {
1961
+ throw new MastraError({
1962
+ id: createStorageErrorId("LIBSQL", "UPDATE_AGENT", "NOT_FOUND_AFTER_UPDATE"),
1963
+ domain: ErrorDomain.STORAGE,
1964
+ category: ErrorCategory.SYSTEM,
1965
+ text: `Agent ${id} not found after update`,
1966
+ details: { agentId: id }
1967
+ });
1968
+ }
1969
+ return updatedAgent;
1972
1970
  } catch (error) {
1973
- const mastraError = new MastraError(
1971
+ if (error instanceof MastraError) {
1972
+ throw error;
1973
+ }
1974
+ throw new MastraError(
1974
1975
  {
1975
- id: createStorageErrorId("LIBSQL", "LIST_THREADS_BY_RESOURCE_ID", "FAILED"),
1976
+ id: createStorageErrorId("LIBSQL", "UPDATE_AGENT", "FAILED"),
1976
1977
  domain: ErrorDomain.STORAGE,
1977
1978
  category: ErrorCategory.THIRD_PARTY,
1978
- details: { resourceId }
1979
+ details: { agentId: id }
1979
1980
  },
1980
1981
  error
1981
1982
  );
1982
- this.logger?.trackException?.(mastraError);
1983
- this.logger?.error?.(mastraError.toString());
1984
- return {
1985
- threads: [],
1986
- total: 0,
1987
- page,
1988
- perPage: perPageForResponse,
1989
- hasMore: false
1990
- };
1991
1983
  }
1992
1984
  }
1993
- async saveThread({ thread }) {
1985
+ async deleteAgent({ id }) {
1994
1986
  try {
1995
- await this.operations.insert({
1996
- tableName: TABLE_THREADS,
1997
- record: {
1998
- ...thread,
1999
- metadata: JSON.stringify(thread.metadata)
2000
- }
1987
+ await this.#db.delete({
1988
+ tableName: TABLE_AGENTS,
1989
+ keys: { id }
2001
1990
  });
2002
- return thread;
2003
1991
  } catch (error) {
2004
- const mastraError = new MastraError(
1992
+ throw new MastraError(
2005
1993
  {
2006
- id: createStorageErrorId("LIBSQL", "SAVE_THREAD", "FAILED"),
1994
+ id: createStorageErrorId("LIBSQL", "DELETE_AGENT", "FAILED"),
2007
1995
  domain: ErrorDomain.STORAGE,
2008
1996
  category: ErrorCategory.THIRD_PARTY,
2009
- details: { threadId: thread.id }
1997
+ details: { agentId: id }
2010
1998
  },
2011
1999
  error
2012
2000
  );
2013
- this.logger?.trackException?.(mastraError);
2014
- this.logger?.error?.(mastraError.toString());
2015
- throw mastraError;
2016
2001
  }
2017
2002
  }
2018
- async updateThread({
2019
- id,
2020
- title,
2021
- metadata
2022
- }) {
2023
- const thread = await this.getThreadById({ threadId: id });
2024
- if (!thread) {
2025
- throw new MastraError({
2026
- id: createStorageErrorId("LIBSQL", "UPDATE_THREAD", "NOT_FOUND"),
2027
- domain: ErrorDomain.STORAGE,
2028
- category: ErrorCategory.USER,
2029
- text: `Thread ${id} not found`,
2030
- details: {
2031
- status: 404,
2032
- threadId: id
2033
- }
2034
- });
2035
- }
2036
- const updatedThread = {
2037
- ...thread,
2038
- title,
2039
- metadata: {
2040
- ...thread.metadata,
2041
- ...metadata
2042
- }
2043
- };
2044
- try {
2045
- await this.client.execute({
2046
- sql: `UPDATE ${TABLE_THREADS} SET title = ?, metadata = ? WHERE id = ?`,
2047
- args: [title, JSON.stringify(updatedThread.metadata), id]
2048
- });
2049
- return updatedThread;
2050
- } catch (error) {
2003
+ async listAgents(args) {
2004
+ const { page = 0, perPage: perPageInput, orderBy } = args || {};
2005
+ const { field, direction } = this.parseOrderBy(orderBy);
2006
+ if (page < 0) {
2051
2007
  throw new MastraError(
2052
2008
  {
2053
- id: createStorageErrorId("LIBSQL", "UPDATE_THREAD", "FAILED"),
2009
+ id: createStorageErrorId("LIBSQL", "LIST_AGENTS", "INVALID_PAGE"),
2054
2010
  domain: ErrorDomain.STORAGE,
2055
- category: ErrorCategory.THIRD_PARTY,
2056
- text: `Failed to update thread ${id}`,
2057
- details: { threadId: id }
2011
+ category: ErrorCategory.USER,
2012
+ details: { page }
2058
2013
  },
2059
- error
2014
+ new Error("page must be >= 0")
2060
2015
  );
2061
2016
  }
2062
- }
2063
- async deleteThread({ threadId }) {
2017
+ const perPage = normalizePerPage(perPageInput, 100);
2018
+ const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
2064
2019
  try {
2065
- await this.client.execute({
2066
- sql: `DELETE FROM ${TABLE_MESSAGES} WHERE thread_id = ?`,
2067
- args: [threadId]
2068
- });
2069
- await this.client.execute({
2070
- sql: `DELETE FROM ${TABLE_THREADS} WHERE id = ?`,
2071
- args: [threadId]
2020
+ const total = await this.#db.selectTotalCount({ tableName: TABLE_AGENTS });
2021
+ if (total === 0) {
2022
+ return {
2023
+ agents: [],
2024
+ total: 0,
2025
+ page,
2026
+ perPage: perPageForResponse,
2027
+ hasMore: false
2028
+ };
2029
+ }
2030
+ const limitValue = perPageInput === false ? total : perPage;
2031
+ const rows = await this.#db.selectMany({
2032
+ tableName: TABLE_AGENTS,
2033
+ orderBy: `"${field}" ${direction}`,
2034
+ limit: limitValue,
2035
+ offset
2072
2036
  });
2037
+ const agents = rows.map((row) => this.parseRow(row));
2038
+ return {
2039
+ agents,
2040
+ total,
2041
+ page,
2042
+ perPage: perPageForResponse,
2043
+ hasMore: perPageInput === false ? false : offset + perPage < total
2044
+ };
2073
2045
  } catch (error) {
2074
2046
  throw new MastraError(
2075
2047
  {
2076
- id: createStorageErrorId("LIBSQL", "DELETE_THREAD", "FAILED"),
2048
+ id: createStorageErrorId("LIBSQL", "LIST_AGENTS", "FAILED"),
2077
2049
  domain: ErrorDomain.STORAGE,
2078
- category: ErrorCategory.THIRD_PARTY,
2079
- details: { threadId }
2050
+ category: ErrorCategory.THIRD_PARTY
2080
2051
  },
2081
2052
  error
2082
2053
  );
2083
2054
  }
2084
2055
  }
2085
2056
  };
2086
- function createExecuteWriteOperationWithRetry({
2087
- logger,
2088
- maxRetries,
2089
- initialBackoffMs
2090
- }) {
2091
- return async function executeWriteOperationWithRetry(operationFn, operationDescription) {
2092
- let retries = 0;
2093
- while (true) {
2094
- try {
2095
- return await operationFn();
2096
- } catch (error) {
2097
- if (error.message && (error.message.includes("SQLITE_BUSY") || error.message.includes("database is locked")) && retries < maxRetries) {
2098
- retries++;
2099
- const backoffTime = initialBackoffMs * Math.pow(2, retries - 1);
2100
- logger.warn(
2101
- `LibSQLStore: Encountered SQLITE_BUSY during ${operationDescription}. Retrying (${retries}/${maxRetries}) in ${backoffTime}ms...`
2102
- );
2103
- await new Promise((resolve) => setTimeout(resolve, backoffTime));
2104
- } else {
2105
- logger.error(`LibSQLStore: Error during ${operationDescription} after ${retries} retries: ${error}`);
2106
- throw error;
2107
- }
2108
- }
2109
- }
2110
- };
2111
- }
2112
- function prepareStatement({ tableName, record }) {
2113
- const parsedTableName = parseSqlIdentifier(tableName, "table name");
2114
- const columns = Object.keys(record).map((col) => parseSqlIdentifier(col, "column name"));
2115
- const values = Object.values(record).map((v) => {
2116
- if (typeof v === `undefined` || v === null) {
2117
- return null;
2118
- }
2119
- if (v instanceof Date) {
2120
- return v.toISOString();
2121
- }
2122
- return typeof v === "object" ? JSON.stringify(v) : v;
2123
- });
2124
- const placeholders = values.map(() => "?").join(", ");
2125
- return {
2126
- sql: `INSERT OR REPLACE INTO ${parsedTableName} (${columns.join(", ")}) VALUES (${placeholders})`,
2127
- args: values
2128
- };
2129
- }
2130
- function prepareUpdateStatement({
2131
- tableName,
2132
- updates,
2133
- keys
2134
- }) {
2135
- const parsedTableName = parseSqlIdentifier(tableName, "table name");
2136
- const schema = TABLE_SCHEMAS[tableName];
2137
- const updateColumns = Object.keys(updates).map((col) => parseSqlIdentifier(col, "column name"));
2138
- const updateValues = Object.values(updates).map(transformToSqlValue);
2139
- const setClause = updateColumns.map((col) => `${col} = ?`).join(", ");
2140
- const whereClause = prepareWhereClause(keys, schema);
2141
- return {
2142
- sql: `UPDATE ${parsedTableName} SET ${setClause}${whereClause.sql}`,
2143
- args: [...updateValues, ...whereClause.args]
2144
- };
2145
- }
2146
- function transformToSqlValue(value) {
2147
- if (typeof value === "undefined" || value === null) {
2148
- return null;
2057
+ var MemoryLibSQL = class extends MemoryStorage {
2058
+ #client;
2059
+ #db;
2060
+ constructor(config) {
2061
+ super();
2062
+ const client = resolveClient(config);
2063
+ this.#client = client;
2064
+ this.#db = new LibSQLDB({ client, maxRetries: config.maxRetries, initialBackoffMs: config.initialBackoffMs });
2065
+ }
2066
+ async init() {
2067
+ await this.#db.createTable({ tableName: TABLE_THREADS, schema: TABLE_SCHEMAS[TABLE_THREADS] });
2068
+ await this.#db.createTable({ tableName: TABLE_MESSAGES, schema: TABLE_SCHEMAS[TABLE_MESSAGES] });
2069
+ await this.#db.createTable({ tableName: TABLE_RESOURCES, schema: TABLE_SCHEMAS[TABLE_RESOURCES] });
2070
+ await this.#db.alterTable({
2071
+ tableName: TABLE_MESSAGES,
2072
+ schema: TABLE_SCHEMAS[TABLE_MESSAGES],
2073
+ ifNotExists: ["resourceId"]
2074
+ });
2149
2075
  }
2150
- if (value instanceof Date) {
2151
- return value.toISOString();
2076
+ async dangerouslyClearAll() {
2077
+ await this.#db.deleteData({ tableName: TABLE_MESSAGES });
2078
+ await this.#db.deleteData({ tableName: TABLE_THREADS });
2079
+ await this.#db.deleteData({ tableName: TABLE_RESOURCES });
2152
2080
  }
2153
- return typeof value === "object" ? JSON.stringify(value) : value;
2154
- }
2155
- function prepareDeleteStatement({ tableName, keys }) {
2156
- const parsedTableName = parseSqlIdentifier(tableName, "table name");
2157
- const whereClause = prepareWhereClause(keys, TABLE_SCHEMAS[tableName]);
2158
- return {
2159
- sql: `DELETE FROM ${parsedTableName}${whereClause.sql}`,
2160
- args: whereClause.args
2161
- };
2162
- }
2163
- function prepareWhereClause(filters, schema) {
2164
- const conditions = [];
2165
- const args = [];
2166
- for (const [columnName, filterValue] of Object.entries(filters)) {
2167
- const column = schema[columnName];
2168
- if (!column) {
2169
- throw new Error(`Unknown column: ${columnName}`);
2081
+ parseRow(row) {
2082
+ let content = row.content;
2083
+ try {
2084
+ content = JSON.parse(row.content);
2085
+ } catch {
2170
2086
  }
2171
- const parsedColumn = parseSqlIdentifier(columnName, "column name");
2172
- const result = buildCondition2(parsedColumn, filterValue);
2173
- conditions.push(result.condition);
2174
- args.push(...result.args);
2175
- }
2176
- return {
2177
- sql: conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "",
2178
- args
2179
- };
2180
- }
2181
- function buildCondition2(columnName, filterValue) {
2182
- if (filterValue === null) {
2183
- return { condition: `${columnName} IS NULL`, args: [] };
2184
- }
2185
- if (typeof filterValue === "object" && filterValue !== null && ("startAt" in filterValue || "endAt" in filterValue)) {
2186
- return buildDateRangeCondition(columnName, filterValue);
2187
- }
2188
- return {
2189
- condition: `${columnName} = ?`,
2190
- args: [transformToSqlValue(filterValue)]
2191
- };
2192
- }
2193
- function buildDateRangeCondition(columnName, range) {
2194
- const conditions = [];
2195
- const args = [];
2196
- if (range.startAt !== void 0) {
2197
- conditions.push(`${columnName} >= ?`);
2198
- args.push(transformToSqlValue(range.startAt));
2199
- }
2200
- if (range.endAt !== void 0) {
2201
- conditions.push(`${columnName} <= ?`);
2202
- args.push(transformToSqlValue(range.endAt));
2203
- }
2204
- if (conditions.length === 0) {
2205
- throw new Error("Date range must specify at least startAt or endAt");
2206
- }
2207
- return {
2208
- condition: conditions.join(" AND "),
2209
- args
2210
- };
2211
- }
2212
- function buildDateRangeFilter(dateRange, columnName = "createdAt") {
2213
- if (!dateRange?.start && !dateRange?.end) {
2214
- return {};
2215
- }
2216
- const filter = {};
2217
- if (dateRange.start) {
2218
- filter.startAt = new Date(dateRange.start).toISOString();
2087
+ const result = {
2088
+ id: row.id,
2089
+ content,
2090
+ role: row.role,
2091
+ createdAt: new Date(row.createdAt),
2092
+ threadId: row.thread_id,
2093
+ resourceId: row.resourceId
2094
+ };
2095
+ if (row.type && row.type !== `v2`) result.type = row.type;
2096
+ return result;
2219
2097
  }
2220
- if (dateRange.end) {
2221
- filter.endAt = new Date(dateRange.end).toISOString();
2098
+ async _getIncludedMessages({ include }) {
2099
+ if (!include || include.length === 0) return null;
2100
+ const unionQueries = [];
2101
+ const params = [];
2102
+ for (const inc of include) {
2103
+ const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
2104
+ unionQueries.push(
2105
+ `
2106
+ SELECT * FROM (
2107
+ WITH target_thread AS (
2108
+ SELECT thread_id FROM "${TABLE_MESSAGES}" WHERE id = ?
2109
+ ),
2110
+ numbered_messages AS (
2111
+ SELECT
2112
+ id, content, role, type, "createdAt", thread_id, "resourceId",
2113
+ ROW_NUMBER() OVER (ORDER BY "createdAt" ASC) as row_num
2114
+ FROM "${TABLE_MESSAGES}"
2115
+ WHERE thread_id = (SELECT thread_id FROM target_thread)
2116
+ ),
2117
+ target_positions AS (
2118
+ SELECT row_num as target_pos
2119
+ FROM numbered_messages
2120
+ WHERE id = ?
2121
+ )
2122
+ SELECT DISTINCT m.*
2123
+ FROM numbered_messages m
2124
+ CROSS JOIN target_positions t
2125
+ WHERE m.row_num BETWEEN (t.target_pos - ?) AND (t.target_pos + ?)
2126
+ )
2127
+ `
2128
+ // Keep ASC for final sorting after fetching context
2129
+ );
2130
+ params.push(id, id, withPreviousMessages, withNextMessages);
2131
+ }
2132
+ const finalQuery = unionQueries.join(" UNION ALL ") + ' ORDER BY "createdAt" ASC';
2133
+ const includedResult = await this.#client.execute({ sql: finalQuery, args: params });
2134
+ const includedRows = includedResult.rows?.map((row) => this.parseRow(row));
2135
+ const seen = /* @__PURE__ */ new Set();
2136
+ const dedupedRows = includedRows.filter((row) => {
2137
+ if (seen.has(row.id)) return false;
2138
+ seen.add(row.id);
2139
+ return true;
2140
+ });
2141
+ return dedupedRows;
2222
2142
  }
2223
- return { [columnName]: filter };
2224
- }
2225
- function transformFromSqlRow({
2226
- tableName,
2227
- sqlRow
2228
- }) {
2229
- const result = {};
2230
- const jsonColumns = new Set(
2231
- Object.keys(TABLE_SCHEMAS[tableName]).filter((key) => TABLE_SCHEMAS[tableName][key].type === "jsonb").map((key) => key)
2232
- );
2233
- const dateColumns = new Set(
2234
- Object.keys(TABLE_SCHEMAS[tableName]).filter((key) => TABLE_SCHEMAS[tableName][key].type === "timestamp").map((key) => key)
2235
- );
2236
- for (const [key, value] of Object.entries(sqlRow)) {
2237
- if (value === null || value === void 0) {
2238
- result[key] = value;
2239
- continue;
2143
+ async listMessagesById({ messageIds }) {
2144
+ if (messageIds.length === 0) return { messages: [] };
2145
+ try {
2146
+ const sql = `
2147
+ SELECT
2148
+ id,
2149
+ content,
2150
+ role,
2151
+ type,
2152
+ "createdAt",
2153
+ thread_id,
2154
+ "resourceId"
2155
+ FROM "${TABLE_MESSAGES}"
2156
+ WHERE id IN (${messageIds.map(() => "?").join(", ")})
2157
+ ORDER BY "createdAt" DESC
2158
+ `;
2159
+ const result = await this.#client.execute({ sql, args: messageIds });
2160
+ if (!result.rows) return { messages: [] };
2161
+ const list = new MessageList().add(result.rows.map(this.parseRow), "memory");
2162
+ return { messages: list.get.all.db() };
2163
+ } catch (error) {
2164
+ throw new MastraError(
2165
+ {
2166
+ id: createStorageErrorId("LIBSQL", "LIST_MESSAGES_BY_ID", "FAILED"),
2167
+ domain: ErrorDomain.STORAGE,
2168
+ category: ErrorCategory.THIRD_PARTY,
2169
+ details: { messageIds: JSON.stringify(messageIds) }
2170
+ },
2171
+ error
2172
+ );
2240
2173
  }
2241
- if (dateColumns.has(key) && typeof value === "string") {
2242
- result[key] = new Date(value);
2243
- continue;
2174
+ }
2175
+ async listMessages(args) {
2176
+ const { threadId, resourceId, include, filter, perPage: perPageInput, page = 0, orderBy } = args;
2177
+ const threadIds = Array.isArray(threadId) ? threadId : [threadId];
2178
+ if (threadIds.length === 0 || threadIds.some((id) => !id.trim())) {
2179
+ throw new MastraError(
2180
+ {
2181
+ id: createStorageErrorId("LIBSQL", "LIST_MESSAGES", "INVALID_THREAD_ID"),
2182
+ domain: ErrorDomain.STORAGE,
2183
+ category: ErrorCategory.THIRD_PARTY,
2184
+ details: { threadId: Array.isArray(threadId) ? threadId.join(",") : threadId }
2185
+ },
2186
+ new Error("threadId must be a non-empty string or array of non-empty strings")
2187
+ );
2244
2188
  }
2245
- if (jsonColumns.has(key) && typeof value === "string") {
2246
- result[key] = safelyParseJSON(value);
2247
- continue;
2189
+ if (page < 0) {
2190
+ throw new MastraError(
2191
+ {
2192
+ id: createStorageErrorId("LIBSQL", "LIST_MESSAGES", "INVALID_PAGE"),
2193
+ domain: ErrorDomain.STORAGE,
2194
+ category: ErrorCategory.USER,
2195
+ details: { page }
2196
+ },
2197
+ new Error("page must be >= 0")
2198
+ );
2248
2199
  }
2249
- result[key] = value;
2250
- }
2251
- return result;
2252
- }
2253
-
2254
- // src/storage/domains/observability/index.ts
2255
- var ObservabilityLibSQL = class extends ObservabilityStorage {
2256
- operations;
2257
- constructor({ operations }) {
2258
- super();
2259
- this.operations = operations;
2260
- }
2261
- async createSpan(span) {
2200
+ const perPage = normalizePerPage(perPageInput, 40);
2201
+ const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
2262
2202
  try {
2263
- const now = (/* @__PURE__ */ new Date()).toISOString();
2264
- const record = {
2265
- ...span,
2266
- createdAt: now,
2267
- updatedAt: now
2203
+ const { field, direction } = this.parseOrderBy(orderBy, "ASC");
2204
+ const orderByStatement = `ORDER BY "${field}" ${direction}`;
2205
+ const threadPlaceholders = threadIds.map(() => "?").join(", ");
2206
+ const conditions = [`thread_id IN (${threadPlaceholders})`];
2207
+ const queryParams = [...threadIds];
2208
+ if (resourceId) {
2209
+ conditions.push(`"resourceId" = ?`);
2210
+ queryParams.push(resourceId);
2211
+ }
2212
+ if (filter?.dateRange?.start) {
2213
+ conditions.push(`"createdAt" >= ?`);
2214
+ queryParams.push(
2215
+ filter.dateRange.start instanceof Date ? filter.dateRange.start.toISOString() : filter.dateRange.start
2216
+ );
2217
+ }
2218
+ if (filter?.dateRange?.end) {
2219
+ conditions.push(`"createdAt" <= ?`);
2220
+ queryParams.push(
2221
+ filter.dateRange.end instanceof Date ? filter.dateRange.end.toISOString() : filter.dateRange.end
2222
+ );
2223
+ }
2224
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2225
+ const countResult = await this.#client.execute({
2226
+ sql: `SELECT COUNT(*) as count FROM ${TABLE_MESSAGES} ${whereClause}`,
2227
+ args: queryParams
2228
+ });
2229
+ const total = Number(countResult.rows?.[0]?.count ?? 0);
2230
+ const limitValue = perPageInput === false ? total : perPage;
2231
+ const dataResult = await this.#client.execute({
2232
+ sql: `SELECT id, content, role, type, "createdAt", "resourceId", "thread_id" FROM ${TABLE_MESSAGES} ${whereClause} ${orderByStatement} LIMIT ? OFFSET ?`,
2233
+ args: [...queryParams, limitValue, offset]
2234
+ });
2235
+ const messages = (dataResult.rows || []).map((row) => this.parseRow(row));
2236
+ if (total === 0 && messages.length === 0 && (!include || include.length === 0)) {
2237
+ return {
2238
+ messages: [],
2239
+ total: 0,
2240
+ page,
2241
+ perPage: perPageForResponse,
2242
+ hasMore: false
2243
+ };
2244
+ }
2245
+ const messageIds = new Set(messages.map((m) => m.id));
2246
+ if (include && include.length > 0) {
2247
+ const includeMessages = await this._getIncludedMessages({ include });
2248
+ if (includeMessages) {
2249
+ for (const includeMsg of includeMessages) {
2250
+ if (!messageIds.has(includeMsg.id)) {
2251
+ messages.push(includeMsg);
2252
+ messageIds.add(includeMsg.id);
2253
+ }
2254
+ }
2255
+ }
2256
+ }
2257
+ const list = new MessageList().add(messages, "memory");
2258
+ let finalMessages = list.get.all.db();
2259
+ finalMessages = finalMessages.sort((a, b) => {
2260
+ const isDateField = field === "createdAt" || field === "updatedAt";
2261
+ const aValue = isDateField ? new Date(a[field]).getTime() : a[field];
2262
+ const bValue = isDateField ? new Date(b[field]).getTime() : b[field];
2263
+ if (typeof aValue === "number" && typeof bValue === "number") {
2264
+ return direction === "ASC" ? aValue - bValue : bValue - aValue;
2265
+ }
2266
+ return direction === "ASC" ? String(aValue).localeCompare(String(bValue)) : String(bValue).localeCompare(String(aValue));
2267
+ });
2268
+ const threadIdSet = new Set(threadIds);
2269
+ const returnedThreadMessageIds = new Set(
2270
+ finalMessages.filter((m) => m.threadId && threadIdSet.has(m.threadId)).map((m) => m.id)
2271
+ );
2272
+ const allThreadMessagesReturned = returnedThreadMessageIds.size >= total;
2273
+ const hasMore = perPageInput !== false && !allThreadMessagesReturned && offset + perPage < total;
2274
+ return {
2275
+ messages: finalMessages,
2276
+ total,
2277
+ page,
2278
+ perPage: perPageForResponse,
2279
+ hasMore
2268
2280
  };
2269
- return this.operations.insert({ tableName: TABLE_SPANS, record });
2270
2281
  } catch (error) {
2271
- throw new MastraError(
2282
+ const mastraError = new MastraError(
2272
2283
  {
2273
- id: createStorageErrorId("LIBSQL", "CREATE_SPAN", "FAILED"),
2284
+ id: createStorageErrorId("LIBSQL", "LIST_MESSAGES", "FAILED"),
2274
2285
  domain: ErrorDomain.STORAGE,
2275
- category: ErrorCategory.USER,
2286
+ category: ErrorCategory.THIRD_PARTY,
2276
2287
  details: {
2277
- spanId: span.spanId,
2278
- traceId: span.traceId,
2279
- spanType: span.spanType,
2280
- spanName: span.name
2288
+ threadId: Array.isArray(threadId) ? threadId.join(",") : threadId,
2289
+ resourceId: resourceId ?? ""
2281
2290
  }
2282
2291
  },
2283
2292
  error
2284
2293
  );
2294
+ this.logger?.error?.(mastraError.toString());
2295
+ this.logger?.trackException?.(mastraError);
2296
+ return {
2297
+ messages: [],
2298
+ total: 0,
2299
+ page,
2300
+ perPage: perPageForResponse,
2301
+ hasMore: false
2302
+ };
2285
2303
  }
2286
2304
  }
2287
- async getTrace(traceId) {
2305
+ async saveMessages({ messages }) {
2306
+ if (messages.length === 0) return { messages };
2288
2307
  try {
2289
- const spans = await this.operations.loadMany({
2290
- tableName: TABLE_SPANS,
2291
- whereClause: { sql: " WHERE traceId = ?", args: [traceId] },
2292
- orderBy: "startedAt DESC"
2308
+ const threadId = messages[0]?.threadId;
2309
+ if (!threadId) {
2310
+ throw new Error("Thread ID is required");
2311
+ }
2312
+ const batchStatements = messages.map((message) => {
2313
+ const time = message.createdAt || /* @__PURE__ */ new Date();
2314
+ if (!message.threadId) {
2315
+ throw new Error(
2316
+ `Expected to find a threadId for message, but couldn't find one. An unexpected error has occurred.`
2317
+ );
2318
+ }
2319
+ if (!message.resourceId) {
2320
+ throw new Error(
2321
+ `Expected to find a resourceId for message, but couldn't find one. An unexpected error has occurred.`
2322
+ );
2323
+ }
2324
+ return {
2325
+ sql: `INSERT INTO "${TABLE_MESSAGES}" (id, thread_id, content, role, type, "createdAt", "resourceId")
2326
+ VALUES (?, ?, ?, ?, ?, ?, ?)
2327
+ ON CONFLICT(id) DO UPDATE SET
2328
+ thread_id=excluded.thread_id,
2329
+ content=excluded.content,
2330
+ role=excluded.role,
2331
+ type=excluded.type,
2332
+ "resourceId"=excluded."resourceId"
2333
+ `,
2334
+ args: [
2335
+ message.id,
2336
+ message.threadId,
2337
+ typeof message.content === "object" ? JSON.stringify(message.content) : message.content,
2338
+ message.role,
2339
+ message.type || "v2",
2340
+ time instanceof Date ? time.toISOString() : time,
2341
+ message.resourceId
2342
+ ]
2343
+ };
2293
2344
  });
2294
- if (!spans || spans.length === 0) {
2295
- return null;
2345
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2346
+ batchStatements.push({
2347
+ sql: `UPDATE "${TABLE_THREADS}" SET "updatedAt" = ? WHERE id = ?`,
2348
+ args: [now, threadId]
2349
+ });
2350
+ const BATCH_SIZE = 50;
2351
+ const messageStatements = batchStatements.slice(0, -1);
2352
+ const threadUpdateStatement = batchStatements[batchStatements.length - 1];
2353
+ for (let i = 0; i < messageStatements.length; i += BATCH_SIZE) {
2354
+ const batch = messageStatements.slice(i, i + BATCH_SIZE);
2355
+ if (batch.length > 0) {
2356
+ await this.#client.batch(batch, "write");
2357
+ }
2296
2358
  }
2297
- return {
2298
- traceId,
2299
- spans: spans.map((span) => transformFromSqlRow({ tableName: TABLE_SPANS, sqlRow: span }))
2300
- };
2359
+ if (threadUpdateStatement) {
2360
+ await this.#client.execute(threadUpdateStatement);
2361
+ }
2362
+ const list = new MessageList().add(messages, "memory");
2363
+ return { messages: list.get.all.db() };
2301
2364
  } catch (error) {
2302
2365
  throw new MastraError(
2303
2366
  {
2304
- id: createStorageErrorId("LIBSQL", "GET_TRACE", "FAILED"),
2367
+ id: createStorageErrorId("LIBSQL", "SAVE_MESSAGES", "FAILED"),
2305
2368
  domain: ErrorDomain.STORAGE,
2306
- category: ErrorCategory.USER,
2307
- details: {
2308
- traceId
2309
- }
2369
+ category: ErrorCategory.THIRD_PARTY
2310
2370
  },
2311
2371
  error
2312
2372
  );
2313
2373
  }
2314
2374
  }
2315
- async updateSpan({
2316
- spanId,
2317
- traceId,
2318
- updates
2375
+ async updateMessages({
2376
+ messages
2319
2377
  }) {
2320
- try {
2321
- await this.operations.update({
2322
- tableName: TABLE_SPANS,
2323
- keys: { spanId, traceId },
2324
- data: { ...updates, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }
2325
- });
2326
- } catch (error) {
2327
- throw new MastraError(
2328
- {
2329
- id: createStorageErrorId("LIBSQL", "UPDATE_SPAN", "FAILED"),
2330
- domain: ErrorDomain.STORAGE,
2331
- category: ErrorCategory.USER,
2332
- details: {
2333
- spanId,
2334
- traceId
2335
- }
2336
- },
2337
- error
2338
- );
2378
+ if (messages.length === 0) {
2379
+ return [];
2339
2380
  }
2340
- }
2341
- async getTracesPaginated({
2342
- filters,
2343
- pagination
2344
- }) {
2345
- const page = pagination?.page ?? 0;
2346
- const perPage = pagination?.perPage ?? 10;
2347
- const { entityId, entityType, ...actualFilters } = filters || {};
2348
- const filtersWithDateRange = {
2349
- ...actualFilters,
2350
- ...buildDateRangeFilter(pagination?.dateRange, "startedAt"),
2351
- parentSpanId: null
2381
+ const messageIds = messages.map((m) => m.id);
2382
+ const placeholders = messageIds.map(() => "?").join(",");
2383
+ const selectSql = `SELECT * FROM ${TABLE_MESSAGES} WHERE id IN (${placeholders})`;
2384
+ const existingResult = await this.#client.execute({ sql: selectSql, args: messageIds });
2385
+ const existingMessages = existingResult.rows.map((row) => this.parseRow(row));
2386
+ if (existingMessages.length === 0) {
2387
+ return [];
2388
+ }
2389
+ const batchStatements = [];
2390
+ const threadIdsToUpdate = /* @__PURE__ */ new Set();
2391
+ const columnMapping = {
2392
+ threadId: "thread_id"
2352
2393
  };
2353
- const whereClause = prepareWhereClause(filtersWithDateRange, SPAN_SCHEMA);
2354
- let actualWhereClause = whereClause.sql || "";
2355
- if (entityId && entityType) {
2356
- const statement = `name = ?`;
2357
- let name = "";
2358
- if (entityType === "workflow") {
2359
- name = `workflow run: '${entityId}'`;
2360
- } else if (entityType === "agent") {
2361
- name = `agent run: '${entityId}'`;
2362
- } else {
2363
- const error = new MastraError({
2364
- id: createStorageErrorId("LIBSQL", "GET_TRACES_PAGINATED", "INVALID_ENTITY_TYPE"),
2365
- domain: ErrorDomain.STORAGE,
2366
- category: ErrorCategory.USER,
2367
- details: {
2368
- entityType
2369
- },
2370
- text: `Cannot filter by entity type: ${entityType}`
2371
- });
2372
- this.logger?.trackException(error);
2373
- throw error;
2394
+ for (const existingMessage of existingMessages) {
2395
+ const updatePayload = messages.find((m) => m.id === existingMessage.id);
2396
+ if (!updatePayload) continue;
2397
+ const { id, ...fieldsToUpdate } = updatePayload;
2398
+ if (Object.keys(fieldsToUpdate).length === 0) continue;
2399
+ threadIdsToUpdate.add(existingMessage.threadId);
2400
+ if (updatePayload.threadId && updatePayload.threadId !== existingMessage.threadId) {
2401
+ threadIdsToUpdate.add(updatePayload.threadId);
2374
2402
  }
2375
- whereClause.args.push(name);
2376
- if (actualWhereClause) {
2377
- actualWhereClause += ` AND ${statement}`;
2378
- } else {
2379
- actualWhereClause += `WHERE ${statement}`;
2403
+ const setClauses = [];
2404
+ const args = [];
2405
+ const updatableFields = { ...fieldsToUpdate };
2406
+ if (updatableFields.content) {
2407
+ const newContent = {
2408
+ ...existingMessage.content,
2409
+ ...updatableFields.content,
2410
+ // Deep merge metadata if it exists on both
2411
+ ...existingMessage.content?.metadata && updatableFields.content.metadata ? {
2412
+ metadata: {
2413
+ ...existingMessage.content.metadata,
2414
+ ...updatableFields.content.metadata
2415
+ }
2416
+ } : {}
2417
+ };
2418
+ setClauses.push(`${parseSqlIdentifier("content", "column name")} = ?`);
2419
+ args.push(JSON.stringify(newContent));
2420
+ delete updatableFields.content;
2421
+ }
2422
+ for (const key in updatableFields) {
2423
+ if (Object.prototype.hasOwnProperty.call(updatableFields, key)) {
2424
+ const dbKey = columnMapping[key] || key;
2425
+ setClauses.push(`${parseSqlIdentifier(dbKey, "column name")} = ?`);
2426
+ let value = updatableFields[key];
2427
+ if (typeof value === "object" && value !== null) {
2428
+ value = JSON.stringify(value);
2429
+ }
2430
+ args.push(value);
2431
+ }
2432
+ }
2433
+ if (setClauses.length === 0) continue;
2434
+ args.push(id);
2435
+ const sql = `UPDATE ${TABLE_MESSAGES} SET ${setClauses.join(", ")} WHERE id = ?`;
2436
+ batchStatements.push({ sql, args });
2437
+ }
2438
+ if (batchStatements.length === 0) {
2439
+ return existingMessages;
2440
+ }
2441
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2442
+ for (const threadId of threadIdsToUpdate) {
2443
+ if (threadId) {
2444
+ batchStatements.push({
2445
+ sql: `UPDATE ${TABLE_THREADS} SET updatedAt = ? WHERE id = ?`,
2446
+ args: [now, threadId]
2447
+ });
2380
2448
  }
2381
2449
  }
2382
- const orderBy = "startedAt DESC";
2383
- let count = 0;
2450
+ await this.#client.batch(batchStatements, "write");
2451
+ const updatedResult = await this.#client.execute({ sql: selectSql, args: messageIds });
2452
+ return updatedResult.rows.map((row) => this.parseRow(row));
2453
+ }
2454
+ async deleteMessages(messageIds) {
2455
+ if (!messageIds || messageIds.length === 0) {
2456
+ return;
2457
+ }
2384
2458
  try {
2385
- count = await this.operations.loadTotalCount({
2386
- tableName: TABLE_SPANS,
2387
- whereClause: { sql: actualWhereClause, args: whereClause.args }
2388
- });
2459
+ const BATCH_SIZE = 100;
2460
+ const threadIds = /* @__PURE__ */ new Set();
2461
+ const tx = await this.#client.transaction("write");
2462
+ try {
2463
+ for (let i = 0; i < messageIds.length; i += BATCH_SIZE) {
2464
+ const batch = messageIds.slice(i, i + BATCH_SIZE);
2465
+ const placeholders = batch.map(() => "?").join(",");
2466
+ const result = await tx.execute({
2467
+ sql: `SELECT DISTINCT thread_id FROM "${TABLE_MESSAGES}" WHERE id IN (${placeholders})`,
2468
+ args: batch
2469
+ });
2470
+ result.rows?.forEach((row) => {
2471
+ if (row.thread_id) threadIds.add(row.thread_id);
2472
+ });
2473
+ await tx.execute({
2474
+ sql: `DELETE FROM "${TABLE_MESSAGES}" WHERE id IN (${placeholders})`,
2475
+ args: batch
2476
+ });
2477
+ }
2478
+ if (threadIds.size > 0) {
2479
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2480
+ for (const threadId of threadIds) {
2481
+ await tx.execute({
2482
+ sql: `UPDATE "${TABLE_THREADS}" SET "updatedAt" = ? WHERE id = ?`,
2483
+ args: [now, threadId]
2484
+ });
2485
+ }
2486
+ }
2487
+ await tx.commit();
2488
+ } catch (error) {
2489
+ await tx.rollback();
2490
+ throw error;
2491
+ }
2389
2492
  } catch (error) {
2390
2493
  throw new MastraError(
2391
2494
  {
2392
- id: createStorageErrorId("LIBSQL", "GET_TRACES_PAGINATED", "COUNT_FAILED"),
2495
+ id: createStorageErrorId("LIBSQL", "DELETE_MESSAGES", "FAILED"),
2393
2496
  domain: ErrorDomain.STORAGE,
2394
- category: ErrorCategory.USER
2497
+ category: ErrorCategory.THIRD_PARTY,
2498
+ details: { messageIds: messageIds.join(", ") }
2395
2499
  },
2396
2500
  error
2397
2501
  );
2398
2502
  }
2399
- if (count === 0) {
2400
- return {
2401
- pagination: {
2402
- total: 0,
2403
- page,
2404
- perPage,
2405
- hasMore: false
2406
- },
2407
- spans: []
2503
+ }
2504
+ async getResourceById({ resourceId }) {
2505
+ const result = await this.#db.select({
2506
+ tableName: TABLE_RESOURCES,
2507
+ keys: { id: resourceId }
2508
+ });
2509
+ if (!result) {
2510
+ return null;
2511
+ }
2512
+ return {
2513
+ ...result,
2514
+ // Ensure workingMemory is always returned as a string, even if auto-parsed as JSON
2515
+ workingMemory: result.workingMemory && typeof result.workingMemory === "object" ? JSON.stringify(result.workingMemory) : result.workingMemory,
2516
+ metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata,
2517
+ createdAt: new Date(result.createdAt),
2518
+ updatedAt: new Date(result.updatedAt)
2519
+ };
2520
+ }
2521
+ async saveResource({ resource }) {
2522
+ await this.#db.insert({
2523
+ tableName: TABLE_RESOURCES,
2524
+ record: {
2525
+ ...resource,
2526
+ metadata: JSON.stringify(resource.metadata)
2527
+ }
2528
+ });
2529
+ return resource;
2530
+ }
2531
+ async updateResource({
2532
+ resourceId,
2533
+ workingMemory,
2534
+ metadata
2535
+ }) {
2536
+ const existingResource = await this.getResourceById({ resourceId });
2537
+ if (!existingResource) {
2538
+ const newResource = {
2539
+ id: resourceId,
2540
+ workingMemory,
2541
+ metadata: metadata || {},
2542
+ createdAt: /* @__PURE__ */ new Date(),
2543
+ updatedAt: /* @__PURE__ */ new Date()
2408
2544
  };
2545
+ return this.saveResource({ resource: newResource });
2546
+ }
2547
+ const updatedResource = {
2548
+ ...existingResource,
2549
+ workingMemory: workingMemory !== void 0 ? workingMemory : existingResource.workingMemory,
2550
+ metadata: {
2551
+ ...existingResource.metadata,
2552
+ ...metadata
2553
+ },
2554
+ updatedAt: /* @__PURE__ */ new Date()
2555
+ };
2556
+ const updates = [];
2557
+ const values = [];
2558
+ if (workingMemory !== void 0) {
2559
+ updates.push("workingMemory = ?");
2560
+ values.push(workingMemory);
2561
+ }
2562
+ if (metadata) {
2563
+ updates.push("metadata = ?");
2564
+ values.push(JSON.stringify(updatedResource.metadata));
2409
2565
  }
2566
+ updates.push("updatedAt = ?");
2567
+ values.push(updatedResource.updatedAt.toISOString());
2568
+ values.push(resourceId);
2569
+ await this.#client.execute({
2570
+ sql: `UPDATE ${TABLE_RESOURCES} SET ${updates.join(", ")} WHERE id = ?`,
2571
+ args: values
2572
+ });
2573
+ return updatedResource;
2574
+ }
2575
+ async getThreadById({ threadId }) {
2410
2576
  try {
2411
- const spans = await this.operations.loadMany({
2412
- tableName: TABLE_SPANS,
2413
- whereClause: {
2414
- sql: actualWhereClause,
2415
- args: whereClause.args
2416
- },
2417
- orderBy,
2418
- offset: page * perPage,
2419
- limit: perPage
2577
+ const result = await this.#db.select({
2578
+ tableName: TABLE_THREADS,
2579
+ keys: { id: threadId }
2420
2580
  });
2581
+ if (!result) {
2582
+ return null;
2583
+ }
2421
2584
  return {
2422
- pagination: {
2423
- total: count,
2424
- page,
2425
- perPage,
2426
- hasMore: spans.length === perPage
2427
- },
2428
- spans: spans.map((span) => transformFromSqlRow({ tableName: TABLE_SPANS, sqlRow: span }))
2585
+ ...result,
2586
+ metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata,
2587
+ createdAt: new Date(result.createdAt),
2588
+ updatedAt: new Date(result.updatedAt)
2429
2589
  };
2430
2590
  } catch (error) {
2431
2591
  throw new MastraError(
2432
2592
  {
2433
- id: createStorageErrorId("LIBSQL", "GET_TRACES_PAGINATED", "FAILED"),
2593
+ id: createStorageErrorId("LIBSQL", "GET_THREAD_BY_ID", "FAILED"),
2434
2594
  domain: ErrorDomain.STORAGE,
2435
- category: ErrorCategory.USER
2595
+ category: ErrorCategory.THIRD_PARTY,
2596
+ details: { threadId }
2436
2597
  },
2437
2598
  error
2438
2599
  );
2439
2600
  }
2440
2601
  }
2441
- async batchCreateSpans(args) {
2602
+ async listThreadsByResourceId(args) {
2603
+ const { resourceId, page = 0, perPage: perPageInput, orderBy } = args;
2604
+ if (page < 0) {
2605
+ throw new MastraError(
2606
+ {
2607
+ id: createStorageErrorId("LIBSQL", "LIST_THREADS_BY_RESOURCE_ID", "INVALID_PAGE"),
2608
+ domain: ErrorDomain.STORAGE,
2609
+ category: ErrorCategory.USER,
2610
+ details: { page }
2611
+ },
2612
+ new Error("page must be >= 0")
2613
+ );
2614
+ }
2615
+ const perPage = normalizePerPage(perPageInput, 100);
2616
+ const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
2617
+ const { field, direction } = this.parseOrderBy(orderBy);
2442
2618
  try {
2443
- const now = (/* @__PURE__ */ new Date()).toISOString();
2444
- return this.operations.batchInsert({
2445
- tableName: TABLE_SPANS,
2446
- records: args.records.map((record) => ({
2447
- ...record,
2448
- createdAt: now,
2449
- updatedAt: now
2450
- }))
2619
+ const baseQuery = `FROM ${TABLE_THREADS} WHERE resourceId = ?`;
2620
+ const queryParams = [resourceId];
2621
+ const mapRowToStorageThreadType = (row) => ({
2622
+ id: row.id,
2623
+ resourceId: row.resourceId,
2624
+ title: row.title,
2625
+ createdAt: new Date(row.createdAt),
2626
+ // Convert string to Date
2627
+ updatedAt: new Date(row.updatedAt),
2628
+ // Convert string to Date
2629
+ metadata: typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata
2630
+ });
2631
+ const countResult = await this.#client.execute({
2632
+ sql: `SELECT COUNT(*) as count ${baseQuery}`,
2633
+ args: queryParams
2634
+ });
2635
+ const total = Number(countResult.rows?.[0]?.count ?? 0);
2636
+ if (total === 0) {
2637
+ return {
2638
+ threads: [],
2639
+ total: 0,
2640
+ page,
2641
+ perPage: perPageForResponse,
2642
+ hasMore: false
2643
+ };
2644
+ }
2645
+ const limitValue = perPageInput === false ? total : perPage;
2646
+ const dataResult = await this.#client.execute({
2647
+ sql: `SELECT * ${baseQuery} ORDER BY "${field}" ${direction} LIMIT ? OFFSET ?`,
2648
+ args: [...queryParams, limitValue, offset]
2451
2649
  });
2650
+ const threads = (dataResult.rows || []).map(mapRowToStorageThreadType);
2651
+ return {
2652
+ threads,
2653
+ total,
2654
+ page,
2655
+ perPage: perPageForResponse,
2656
+ hasMore: perPageInput === false ? false : offset + perPage < total
2657
+ };
2452
2658
  } catch (error) {
2453
- throw new MastraError(
2659
+ const mastraError = new MastraError(
2454
2660
  {
2455
- id: createStorageErrorId("LIBSQL", "BATCH_CREATE_SPANS", "FAILED"),
2661
+ id: createStorageErrorId("LIBSQL", "LIST_THREADS_BY_RESOURCE_ID", "FAILED"),
2456
2662
  domain: ErrorDomain.STORAGE,
2457
- category: ErrorCategory.USER
2663
+ category: ErrorCategory.THIRD_PARTY,
2664
+ details: { resourceId }
2458
2665
  },
2459
2666
  error
2460
2667
  );
2668
+ this.logger?.trackException?.(mastraError);
2669
+ this.logger?.error?.(mastraError.toString());
2670
+ return {
2671
+ threads: [],
2672
+ total: 0,
2673
+ page,
2674
+ perPage: perPageForResponse,
2675
+ hasMore: false
2676
+ };
2461
2677
  }
2462
2678
  }
2463
- async batchUpdateSpans(args) {
2679
+ async saveThread({ thread }) {
2464
2680
  try {
2465
- return this.operations.batchUpdate({
2466
- tableName: TABLE_SPANS,
2467
- updates: args.records.map((record) => ({
2468
- keys: { spanId: record.spanId, traceId: record.traceId },
2469
- data: { ...record.updates, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }
2470
- }))
2681
+ await this.#db.insert({
2682
+ tableName: TABLE_THREADS,
2683
+ record: {
2684
+ ...thread,
2685
+ metadata: JSON.stringify(thread.metadata)
2686
+ }
2471
2687
  });
2688
+ return thread;
2472
2689
  } catch (error) {
2473
- throw new MastraError(
2690
+ const mastraError = new MastraError(
2474
2691
  {
2475
- id: createStorageErrorId("LIBSQL", "BATCH_UPDATE_SPANS", "FAILED"),
2692
+ id: createStorageErrorId("LIBSQL", "SAVE_THREAD", "FAILED"),
2476
2693
  domain: ErrorDomain.STORAGE,
2477
- category: ErrorCategory.USER
2694
+ category: ErrorCategory.THIRD_PARTY,
2695
+ details: { threadId: thread.id }
2478
2696
  },
2479
2697
  error
2480
2698
  );
2699
+ this.logger?.trackException?.(mastraError);
2700
+ this.logger?.error?.(mastraError.toString());
2701
+ throw mastraError;
2481
2702
  }
2482
2703
  }
2483
- async batchDeleteTraces(args) {
2704
+ async updateThread({
2705
+ id,
2706
+ title,
2707
+ metadata
2708
+ }) {
2709
+ const thread = await this.getThreadById({ threadId: id });
2710
+ if (!thread) {
2711
+ throw new MastraError({
2712
+ id: createStorageErrorId("LIBSQL", "UPDATE_THREAD", "NOT_FOUND"),
2713
+ domain: ErrorDomain.STORAGE,
2714
+ category: ErrorCategory.USER,
2715
+ text: `Thread ${id} not found`,
2716
+ details: {
2717
+ status: 404,
2718
+ threadId: id
2719
+ }
2720
+ });
2721
+ }
2722
+ const updatedThread = {
2723
+ ...thread,
2724
+ title,
2725
+ metadata: {
2726
+ ...thread.metadata,
2727
+ ...metadata
2728
+ }
2729
+ };
2484
2730
  try {
2485
- const keys = args.traceIds.map((traceId) => ({ traceId }));
2486
- return this.operations.batchDelete({
2487
- tableName: TABLE_SPANS,
2488
- keys
2731
+ await this.#client.execute({
2732
+ sql: `UPDATE ${TABLE_THREADS} SET title = ?, metadata = ? WHERE id = ?`,
2733
+ args: [title, JSON.stringify(updatedThread.metadata), id]
2489
2734
  });
2735
+ return updatedThread;
2490
2736
  } catch (error) {
2491
2737
  throw new MastraError(
2492
2738
  {
2493
- id: createStorageErrorId("LIBSQL", "BATCH_DELETE_TRACES", "FAILED"),
2739
+ id: createStorageErrorId("LIBSQL", "UPDATE_THREAD", "FAILED"),
2494
2740
  domain: ErrorDomain.STORAGE,
2495
- category: ErrorCategory.USER
2741
+ category: ErrorCategory.THIRD_PARTY,
2742
+ text: `Failed to update thread ${id}`,
2743
+ details: { threadId: id }
2496
2744
  },
2497
2745
  error
2498
2746
  );
2499
2747
  }
2500
2748
  }
2501
- };
2502
- var StoreOperationsLibSQL = class extends StoreOperations {
2503
- client;
2504
- /**
2505
- * Maximum number of retries for write operations if an SQLITE_BUSY error occurs.
2506
- * @default 5
2507
- */
2508
- maxRetries;
2509
- /**
2510
- * Initial backoff time in milliseconds for retrying write operations on SQLITE_BUSY.
2511
- * The backoff time will double with each retry (exponential backoff).
2512
- * @default 100
2513
- */
2514
- initialBackoffMs;
2515
- constructor({
2516
- client,
2517
- maxRetries,
2518
- initialBackoffMs
2519
- }) {
2520
- super();
2521
- this.client = client;
2522
- this.maxRetries = maxRetries ?? 5;
2523
- this.initialBackoffMs = initialBackoffMs ?? 100;
2524
- }
2525
- async hasColumn(table, column) {
2526
- const result = await this.client.execute({
2527
- sql: `PRAGMA table_info(${table})`
2528
- });
2529
- return (await result.rows)?.some((row) => row.name === column);
2530
- }
2531
- getCreateTableSQL(tableName, schema) {
2532
- const parsedTableName = parseSqlIdentifier(tableName, "table name");
2533
- const columns = Object.entries(schema).map(([name, col]) => {
2534
- const parsedColumnName = parseSqlIdentifier(name, "column name");
2535
- const type = this.getSqlType(col.type);
2536
- const nullable = col.nullable ? "" : "NOT NULL";
2537
- const primaryKey = col.primaryKey ? "PRIMARY KEY" : "";
2538
- return `${parsedColumnName} ${type} ${nullable} ${primaryKey}`.trim();
2539
- });
2540
- if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
2541
- const stmnt = `CREATE TABLE IF NOT EXISTS ${parsedTableName} (
2542
- ${columns.join(",\n")},
2543
- PRIMARY KEY (workflow_name, run_id)
2544
- )`;
2545
- return stmnt;
2546
- }
2547
- if (tableName === TABLE_SPANS) {
2548
- const stmnt = `CREATE TABLE IF NOT EXISTS ${parsedTableName} (
2549
- ${columns.join(",\n")},
2550
- PRIMARY KEY (traceId, spanId)
2551
- )`;
2552
- return stmnt;
2553
- }
2554
- return `CREATE TABLE IF NOT EXISTS ${parsedTableName} (${columns.join(", ")})`;
2555
- }
2556
- async createTable({
2557
- tableName,
2558
- schema
2559
- }) {
2749
+ async deleteThread({ threadId }) {
2560
2750
  try {
2561
- this.logger.debug(`Creating database table`, { tableName, operation: "schema init" });
2562
- const sql = this.getCreateTableSQL(tableName, schema);
2563
- await this.client.execute(sql);
2751
+ await this.#client.execute({
2752
+ sql: `DELETE FROM ${TABLE_MESSAGES} WHERE thread_id = ?`,
2753
+ args: [threadId]
2754
+ });
2755
+ await this.#client.execute({
2756
+ sql: `DELETE FROM ${TABLE_THREADS} WHERE id = ?`,
2757
+ args: [threadId]
2758
+ });
2564
2759
  } catch (error) {
2565
2760
  throw new MastraError(
2566
2761
  {
2567
- id: createStorageErrorId("LIBSQL", "CREATE_TABLE", "FAILED"),
2762
+ id: createStorageErrorId("LIBSQL", "DELETE_THREAD", "FAILED"),
2568
2763
  domain: ErrorDomain.STORAGE,
2569
2764
  category: ErrorCategory.THIRD_PARTY,
2570
- details: {
2571
- tableName
2572
- }
2765
+ details: { threadId }
2573
2766
  },
2574
2767
  error
2575
- );
2576
- }
2577
- }
2578
- getSqlType(type) {
2579
- switch (type) {
2580
- case "bigint":
2581
- return "INTEGER";
2582
- // SQLite uses INTEGER for all integer sizes
2583
- case "timestamp":
2584
- return "TEXT";
2585
- // Store timestamps as ISO strings in SQLite
2586
- // jsonb falls through to base class which returns 'JSONB'
2587
- // SQLite's flexible type system treats JSONB as TEXT affinity
2588
- default:
2589
- return super.getSqlType(type);
2590
- }
2591
- }
2592
- async doInsert({
2593
- tableName,
2594
- record
2595
- }) {
2596
- await this.client.execute(
2597
- prepareStatement({
2598
- tableName,
2599
- record
2600
- })
2601
- );
2602
- }
2603
- insert(args) {
2604
- const executeWriteOperationWithRetry = createExecuteWriteOperationWithRetry({
2605
- logger: this.logger,
2606
- maxRetries: this.maxRetries,
2607
- initialBackoffMs: this.initialBackoffMs
2608
- });
2609
- return executeWriteOperationWithRetry(() => this.doInsert(args), `insert into table ${args.tableName}`);
2610
- }
2611
- async load({ tableName, keys }) {
2612
- const parsedTableName = parseSqlIdentifier(tableName, "table name");
2613
- const parsedKeys = Object.keys(keys).map((key) => parseSqlIdentifier(key, "column name"));
2614
- const conditions = parsedKeys.map((key) => `${key} = ?`).join(" AND ");
2615
- const values = Object.values(keys);
2616
- const result = await this.client.execute({
2617
- sql: `SELECT * FROM ${parsedTableName} WHERE ${conditions} ORDER BY createdAt DESC LIMIT 1`,
2618
- args: values
2619
- });
2620
- if (!result.rows || result.rows.length === 0) {
2621
- return null;
2622
- }
2623
- const row = result.rows[0];
2624
- const parsed = Object.fromEntries(
2625
- Object.entries(row || {}).map(([k, v]) => {
2626
- try {
2627
- return [k, typeof v === "string" ? v.startsWith("{") || v.startsWith("[") ? JSON.parse(v) : v : v];
2628
- } catch {
2629
- return [k, v];
2630
- }
2631
- })
2632
- );
2633
- return parsed;
2634
- }
2635
- async loadMany({
2636
- tableName,
2637
- whereClause,
2638
- orderBy,
2639
- offset,
2640
- limit,
2641
- args
2642
- }) {
2643
- const parsedTableName = parseSqlIdentifier(tableName, "table name");
2644
- let statement = `SELECT * FROM ${parsedTableName}`;
2645
- if (whereClause?.sql) {
2646
- statement += `${whereClause.sql}`;
2647
- }
2648
- if (orderBy) {
2649
- statement += ` ORDER BY ${orderBy}`;
2650
- }
2651
- if (limit) {
2652
- statement += ` LIMIT ${limit}`;
2653
- }
2654
- if (offset) {
2655
- statement += ` OFFSET ${offset}`;
2768
+ );
2656
2769
  }
2657
- const result = await this.client.execute({
2658
- sql: statement,
2659
- args: [...whereClause?.args ?? [], ...args ?? []]
2660
- });
2661
- return result.rows;
2662
2770
  }
2663
- async loadTotalCount({
2664
- tableName,
2665
- whereClause
2666
- }) {
2667
- const parsedTableName = parseSqlIdentifier(tableName, "table name");
2668
- const statement = `SELECT COUNT(*) as count FROM ${parsedTableName} ${whereClause ? `${whereClause.sql}` : ""}`;
2669
- const result = await this.client.execute({
2670
- sql: statement,
2671
- args: whereClause?.args ?? []
2672
- });
2673
- if (!result.rows || result.rows.length === 0) {
2674
- return 0;
2675
- }
2676
- return result.rows[0]?.count ?? 0;
2771
+ };
2772
+ var ObservabilityLibSQL = class extends ObservabilityStorage {
2773
+ #db;
2774
+ constructor(config) {
2775
+ super();
2776
+ const client = resolveClient(config);
2777
+ this.#db = new LibSQLDB({ client, maxRetries: config.maxRetries, initialBackoffMs: config.initialBackoffMs });
2677
2778
  }
2678
- update(args) {
2679
- const executeWriteOperationWithRetry = createExecuteWriteOperationWithRetry({
2680
- logger: this.logger,
2681
- maxRetries: this.maxRetries,
2682
- initialBackoffMs: this.initialBackoffMs
2683
- });
2684
- return executeWriteOperationWithRetry(() => this.executeUpdate(args), `update table ${args.tableName}`);
2779
+ async init() {
2780
+ await this.#db.createTable({ tableName: TABLE_SPANS, schema: SPAN_SCHEMA });
2685
2781
  }
2686
- async executeUpdate({
2687
- tableName,
2688
- keys,
2689
- data
2690
- }) {
2691
- await this.client.execute(prepareUpdateStatement({ tableName, updates: data, keys }));
2782
+ async dangerouslyClearAll() {
2783
+ await this.#db.deleteData({ tableName: TABLE_SPANS });
2692
2784
  }
2693
- async doBatchInsert({
2694
- tableName,
2695
- records
2696
- }) {
2697
- if (records.length === 0) return;
2698
- const batchStatements = records.map((r) => prepareStatement({ tableName, record: r }));
2699
- await this.client.batch(batchStatements, "write");
2785
+ get tracingStrategy() {
2786
+ return {
2787
+ preferred: "batch-with-updates",
2788
+ supported: ["batch-with-updates", "insert-only"]
2789
+ };
2700
2790
  }
2701
- batchInsert(args) {
2702
- const executeWriteOperationWithRetry = createExecuteWriteOperationWithRetry({
2703
- logger: this.logger,
2704
- maxRetries: this.maxRetries,
2705
- initialBackoffMs: this.initialBackoffMs
2706
- });
2707
- return executeWriteOperationWithRetry(
2708
- () => this.doBatchInsert(args),
2709
- `batch insert into table ${args.tableName}`
2710
- ).catch((error) => {
2791
+ async createSpan(args) {
2792
+ const { span } = args;
2793
+ try {
2794
+ const startedAt = span.startedAt instanceof Date ? span.startedAt.toISOString() : span.startedAt;
2795
+ const endedAt = span.endedAt instanceof Date ? span.endedAt.toISOString() : span.endedAt;
2796
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2797
+ const record = {
2798
+ ...span,
2799
+ startedAt,
2800
+ endedAt,
2801
+ createdAt: now,
2802
+ updatedAt: now
2803
+ };
2804
+ return this.#db.insert({ tableName: TABLE_SPANS, record });
2805
+ } catch (error) {
2711
2806
  throw new MastraError(
2712
2807
  {
2713
- id: createStorageErrorId("LIBSQL", "BATCH_INSERT", "FAILED"),
2808
+ id: createStorageErrorId("LIBSQL", "CREATE_SPAN", "FAILED"),
2714
2809
  domain: ErrorDomain.STORAGE,
2715
- category: ErrorCategory.THIRD_PARTY,
2810
+ category: ErrorCategory.USER,
2716
2811
  details: {
2717
- tableName: args.tableName
2812
+ spanId: span.spanId,
2813
+ traceId: span.traceId,
2814
+ spanType: span.spanType,
2815
+ name: span.name
2718
2816
  }
2719
2817
  },
2720
2818
  error
2721
2819
  );
2722
- });
2820
+ }
2723
2821
  }
2724
- /**
2725
- * Public batch update method with retry logic
2726
- */
2727
- batchUpdate(args) {
2728
- const executeWriteOperationWithRetry = createExecuteWriteOperationWithRetry({
2729
- logger: this.logger,
2730
- maxRetries: this.maxRetries,
2731
- initialBackoffMs: this.initialBackoffMs
2732
- });
2733
- return executeWriteOperationWithRetry(
2734
- () => this.executeBatchUpdate(args),
2735
- `batch update in table ${args.tableName}`
2736
- ).catch((error) => {
2822
+ async getSpan(args) {
2823
+ const { traceId, spanId } = args;
2824
+ try {
2825
+ const rows = await this.#db.selectMany({
2826
+ tableName: TABLE_SPANS,
2827
+ whereClause: { sql: " WHERE traceId = ? AND spanId = ?", args: [traceId, spanId] },
2828
+ limit: 1
2829
+ });
2830
+ if (!rows || rows.length === 0) {
2831
+ return null;
2832
+ }
2833
+ return {
2834
+ span: transformFromSqlRow({ tableName: TABLE_SPANS, sqlRow: rows[0] })
2835
+ };
2836
+ } catch (error) {
2737
2837
  throw new MastraError(
2738
2838
  {
2739
- id: createStorageErrorId("LIBSQL", "BATCH_UPDATE", "FAILED"),
2839
+ id: createStorageErrorId("LIBSQL", "GET_SPAN", "FAILED"),
2740
2840
  domain: ErrorDomain.STORAGE,
2741
- category: ErrorCategory.THIRD_PARTY,
2742
- details: {
2743
- tableName: args.tableName
2744
- }
2841
+ category: ErrorCategory.USER,
2842
+ details: { traceId, spanId }
2745
2843
  },
2746
2844
  error
2747
2845
  );
2748
- });
2846
+ }
2749
2847
  }
2750
- /**
2751
- * Updates multiple records in batch. Each record can be updated based on single or composite keys.
2752
- */
2753
- async executeBatchUpdate({
2754
- tableName,
2755
- updates
2756
- }) {
2757
- if (updates.length === 0) return;
2758
- const batchStatements = updates.map(
2759
- ({ keys, data }) => prepareUpdateStatement({
2760
- tableName,
2761
- updates: data,
2762
- keys
2763
- })
2764
- );
2765
- await this.client.batch(batchStatements, "write");
2848
+ async getRootSpan(args) {
2849
+ const { traceId } = args;
2850
+ try {
2851
+ const rows = await this.#db.selectMany({
2852
+ tableName: TABLE_SPANS,
2853
+ whereClause: { sql: " WHERE traceId = ? AND parentSpanId IS NULL", args: [traceId] },
2854
+ limit: 1
2855
+ });
2856
+ if (!rows || rows.length === 0) {
2857
+ return null;
2858
+ }
2859
+ return {
2860
+ span: transformFromSqlRow({ tableName: TABLE_SPANS, sqlRow: rows[0] })
2861
+ };
2862
+ } catch (error) {
2863
+ throw new MastraError(
2864
+ {
2865
+ id: createStorageErrorId("LIBSQL", "GET_ROOT_SPAN", "FAILED"),
2866
+ domain: ErrorDomain.STORAGE,
2867
+ category: ErrorCategory.USER,
2868
+ details: { traceId }
2869
+ },
2870
+ error
2871
+ );
2872
+ }
2766
2873
  }
2767
- /**
2768
- * Public batch delete method with retry logic
2769
- */
2770
- batchDelete({ tableName, keys }) {
2771
- const executeWriteOperationWithRetry = createExecuteWriteOperationWithRetry({
2772
- logger: this.logger,
2773
- maxRetries: this.maxRetries,
2774
- initialBackoffMs: this.initialBackoffMs
2775
- });
2776
- return executeWriteOperationWithRetry(
2777
- () => this.executeBatchDelete({ tableName, keys }),
2778
- `batch delete from table ${tableName}`
2779
- ).catch((error) => {
2874
+ async getTrace(args) {
2875
+ const { traceId } = args;
2876
+ try {
2877
+ const spans = await this.#db.selectMany({
2878
+ tableName: TABLE_SPANS,
2879
+ whereClause: { sql: " WHERE traceId = ?", args: [traceId] },
2880
+ orderBy: "startedAt ASC"
2881
+ });
2882
+ if (!spans || spans.length === 0) {
2883
+ return null;
2884
+ }
2885
+ return {
2886
+ traceId,
2887
+ spans: spans.map((span) => transformFromSqlRow({ tableName: TABLE_SPANS, sqlRow: span }))
2888
+ };
2889
+ } catch (error) {
2780
2890
  throw new MastraError(
2781
2891
  {
2782
- id: createStorageErrorId("LIBSQL", "BATCH_DELETE", "FAILED"),
2892
+ id: createStorageErrorId("LIBSQL", "GET_TRACE", "FAILED"),
2783
2893
  domain: ErrorDomain.STORAGE,
2784
- category: ErrorCategory.THIRD_PARTY,
2894
+ category: ErrorCategory.USER,
2785
2895
  details: {
2786
- tableName
2896
+ traceId
2787
2897
  }
2788
2898
  },
2789
2899
  error
2790
2900
  );
2791
- });
2901
+ }
2792
2902
  }
2793
- /**
2794
- * Deletes multiple records in batch. Each record can be deleted based on single or composite keys.
2795
- */
2796
- async executeBatchDelete({
2797
- tableName,
2798
- keys
2799
- }) {
2800
- if (keys.length === 0) return;
2801
- const batchStatements = keys.map(
2802
- (keyObj) => prepareDeleteStatement({
2803
- tableName,
2804
- keys: keyObj
2805
- })
2806
- );
2807
- await this.client.batch(batchStatements, "write");
2903
+ async updateSpan(args) {
2904
+ const { traceId, spanId, updates } = args;
2905
+ try {
2906
+ const data = { ...updates };
2907
+ if (data.endedAt instanceof Date) {
2908
+ data.endedAt = data.endedAt.toISOString();
2909
+ }
2910
+ if (data.startedAt instanceof Date) {
2911
+ data.startedAt = data.startedAt.toISOString();
2912
+ }
2913
+ data.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2914
+ await this.#db.update({
2915
+ tableName: TABLE_SPANS,
2916
+ keys: { spanId, traceId },
2917
+ data
2918
+ });
2919
+ } catch (error) {
2920
+ throw new MastraError(
2921
+ {
2922
+ id: createStorageErrorId("LIBSQL", "UPDATE_SPAN", "FAILED"),
2923
+ domain: ErrorDomain.STORAGE,
2924
+ category: ErrorCategory.USER,
2925
+ details: {
2926
+ spanId,
2927
+ traceId
2928
+ }
2929
+ },
2930
+ error
2931
+ );
2932
+ }
2808
2933
  }
2809
- /**
2810
- * Alters table schema to add columns if they don't exist
2811
- * @param tableName Name of the table
2812
- * @param schema Schema of the table
2813
- * @param ifNotExists Array of column names to add if they don't exist
2814
- */
2815
- async alterTable({
2816
- tableName,
2817
- schema,
2818
- ifNotExists
2819
- }) {
2820
- const parsedTableName = parseSqlIdentifier(tableName, "table name");
2934
+ async listTraces(args) {
2935
+ const { filters, pagination, orderBy } = listTracesArgsSchema.parse(args);
2936
+ const { page, perPage } = pagination;
2937
+ const tableName = parseSqlIdentifier(TABLE_SPANS, "table name");
2821
2938
  try {
2822
- const pragmaQuery = `PRAGMA table_info(${parsedTableName})`;
2823
- const result = await this.client.execute(pragmaQuery);
2824
- const existingColumnNames = new Set(result.rows.map((row) => row.name.toLowerCase()));
2825
- for (const columnName of ifNotExists) {
2826
- if (!existingColumnNames.has(columnName.toLowerCase()) && schema[columnName]) {
2827
- const columnDef = schema[columnName];
2828
- const sqlType = this.getSqlType(columnDef.type);
2829
- const nullable = columnDef.nullable === false ? "NOT NULL" : "";
2830
- const defaultValue = columnDef.nullable === false ? this.getDefaultValue(columnDef.type) : "";
2831
- const alterSql = `ALTER TABLE ${parsedTableName} ADD COLUMN "${columnName}" ${sqlType} ${nullable} ${defaultValue}`.trim();
2832
- await this.client.execute(alterSql);
2833
- this.logger?.debug?.(`Added column ${columnName} to table ${parsedTableName}`);
2939
+ const conditions = ["parentSpanId IS NULL"];
2940
+ const queryArgs = [];
2941
+ if (filters) {
2942
+ if (filters.startedAt?.start) {
2943
+ conditions.push(`startedAt >= ?`);
2944
+ queryArgs.push(filters.startedAt.start.toISOString());
2945
+ }
2946
+ if (filters.startedAt?.end) {
2947
+ conditions.push(`startedAt <= ?`);
2948
+ queryArgs.push(filters.startedAt.end.toISOString());
2949
+ }
2950
+ if (filters.endedAt?.start) {
2951
+ conditions.push(`endedAt >= ?`);
2952
+ queryArgs.push(filters.endedAt.start.toISOString());
2953
+ }
2954
+ if (filters.endedAt?.end) {
2955
+ conditions.push(`endedAt <= ?`);
2956
+ queryArgs.push(filters.endedAt.end.toISOString());
2957
+ }
2958
+ if (filters.spanType !== void 0) {
2959
+ conditions.push(`spanType = ?`);
2960
+ queryArgs.push(filters.spanType);
2961
+ }
2962
+ if (filters.entityType !== void 0) {
2963
+ conditions.push(`entityType = ?`);
2964
+ queryArgs.push(filters.entityType);
2965
+ }
2966
+ if (filters.entityId !== void 0) {
2967
+ conditions.push(`entityId = ?`);
2968
+ queryArgs.push(filters.entityId);
2969
+ }
2970
+ if (filters.entityName !== void 0) {
2971
+ conditions.push(`entityName = ?`);
2972
+ queryArgs.push(filters.entityName);
2973
+ }
2974
+ if (filters.userId !== void 0) {
2975
+ conditions.push(`userId = ?`);
2976
+ queryArgs.push(filters.userId);
2977
+ }
2978
+ if (filters.organizationId !== void 0) {
2979
+ conditions.push(`organizationId = ?`);
2980
+ queryArgs.push(filters.organizationId);
2981
+ }
2982
+ if (filters.resourceId !== void 0) {
2983
+ conditions.push(`resourceId = ?`);
2984
+ queryArgs.push(filters.resourceId);
2985
+ }
2986
+ if (filters.runId !== void 0) {
2987
+ conditions.push(`runId = ?`);
2988
+ queryArgs.push(filters.runId);
2989
+ }
2990
+ if (filters.sessionId !== void 0) {
2991
+ conditions.push(`sessionId = ?`);
2992
+ queryArgs.push(filters.sessionId);
2993
+ }
2994
+ if (filters.threadId !== void 0) {
2995
+ conditions.push(`threadId = ?`);
2996
+ queryArgs.push(filters.threadId);
2997
+ }
2998
+ if (filters.requestId !== void 0) {
2999
+ conditions.push(`requestId = ?`);
3000
+ queryArgs.push(filters.requestId);
2834
3001
  }
3002
+ if (filters.environment !== void 0) {
3003
+ conditions.push(`environment = ?`);
3004
+ queryArgs.push(filters.environment);
3005
+ }
3006
+ if (filters.source !== void 0) {
3007
+ conditions.push(`source = ?`);
3008
+ queryArgs.push(filters.source);
3009
+ }
3010
+ if (filters.serviceName !== void 0) {
3011
+ conditions.push(`serviceName = ?`);
3012
+ queryArgs.push(filters.serviceName);
3013
+ }
3014
+ if (filters.scope != null) {
3015
+ for (const [key, value] of Object.entries(filters.scope)) {
3016
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key)) {
3017
+ throw new MastraError({
3018
+ id: createStorageErrorId("LIBSQL", "LIST_TRACES", "INVALID_FILTER_KEY"),
3019
+ domain: ErrorDomain.STORAGE,
3020
+ category: ErrorCategory.USER,
3021
+ details: { key }
3022
+ });
3023
+ }
3024
+ conditions.push(`json_extract(scope, '$.${key}') = ?`);
3025
+ queryArgs.push(typeof value === "string" ? value : JSON.stringify(value));
3026
+ }
3027
+ }
3028
+ if (filters.metadata != null) {
3029
+ for (const [key, value] of Object.entries(filters.metadata)) {
3030
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key)) {
3031
+ throw new MastraError({
3032
+ id: createStorageErrorId("LIBSQL", "LIST_TRACES", "INVALID_FILTER_KEY"),
3033
+ domain: ErrorDomain.STORAGE,
3034
+ category: ErrorCategory.USER,
3035
+ details: { key }
3036
+ });
3037
+ }
3038
+ conditions.push(`json_extract(metadata, '$.${key}') = ?`);
3039
+ queryArgs.push(typeof value === "string" ? value : JSON.stringify(value));
3040
+ }
3041
+ }
3042
+ if (filters.tags != null && filters.tags.length > 0) {
3043
+ for (const tag of filters.tags) {
3044
+ conditions.push(`EXISTS (SELECT 1 FROM json_each(${tableName}.tags) WHERE value = ?)`);
3045
+ queryArgs.push(tag);
3046
+ }
3047
+ }
3048
+ if (filters.status !== void 0) {
3049
+ switch (filters.status) {
3050
+ case TraceStatus.ERROR:
3051
+ conditions.push(`error IS NOT NULL`);
3052
+ break;
3053
+ case TraceStatus.RUNNING:
3054
+ conditions.push(`endedAt IS NULL AND error IS NULL`);
3055
+ break;
3056
+ case TraceStatus.SUCCESS:
3057
+ conditions.push(`endedAt IS NOT NULL AND error IS NULL`);
3058
+ break;
3059
+ }
3060
+ }
3061
+ if (filters.hasChildError !== void 0) {
3062
+ if (filters.hasChildError) {
3063
+ conditions.push(`EXISTS (
3064
+ SELECT 1 FROM ${tableName} c
3065
+ WHERE c.traceId = ${tableName}.traceId AND c.error IS NOT NULL
3066
+ )`);
3067
+ } else {
3068
+ conditions.push(`NOT EXISTS (
3069
+ SELECT 1 FROM ${tableName} c
3070
+ WHERE c.traceId = ${tableName}.traceId AND c.error IS NOT NULL
3071
+ )`);
3072
+ }
3073
+ }
3074
+ }
3075
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
3076
+ const sortField = orderBy.field;
3077
+ const sortDirection = orderBy.direction;
3078
+ let orderByClause;
3079
+ if (sortField === "endedAt") {
3080
+ orderByClause = sortDirection === "DESC" ? `CASE WHEN ${sortField} IS NULL THEN 0 ELSE 1 END, ${sortField} DESC` : `CASE WHEN ${sortField} IS NULL THEN 1 ELSE 0 END, ${sortField} ASC`;
3081
+ } else {
3082
+ orderByClause = `${sortField} ${sortDirection}`;
3083
+ }
3084
+ const count = await this.#db.selectTotalCount({
3085
+ tableName: TABLE_SPANS,
3086
+ whereClause: { sql: whereClause, args: queryArgs }
3087
+ });
3088
+ if (count === 0) {
3089
+ return {
3090
+ pagination: {
3091
+ total: 0,
3092
+ page,
3093
+ perPage,
3094
+ hasMore: false
3095
+ },
3096
+ spans: []
3097
+ };
2835
3098
  }
3099
+ const spans = await this.#db.selectMany({
3100
+ tableName: TABLE_SPANS,
3101
+ whereClause: { sql: whereClause, args: queryArgs },
3102
+ orderBy: orderByClause,
3103
+ offset: page * perPage,
3104
+ limit: perPage
3105
+ });
3106
+ return {
3107
+ pagination: {
3108
+ total: count,
3109
+ page,
3110
+ perPage,
3111
+ hasMore: (page + 1) * perPage < count
3112
+ },
3113
+ spans: spans.map((span) => transformFromSqlRow({ tableName: TABLE_SPANS, sqlRow: span }))
3114
+ };
3115
+ } catch (error) {
3116
+ throw new MastraError(
3117
+ {
3118
+ id: createStorageErrorId("LIBSQL", "LIST_TRACES", "FAILED"),
3119
+ domain: ErrorDomain.STORAGE,
3120
+ category: ErrorCategory.USER
3121
+ },
3122
+ error
3123
+ );
3124
+ }
3125
+ }
3126
+ async batchCreateSpans(args) {
3127
+ try {
3128
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3129
+ const records = args.records.map((record) => {
3130
+ const startedAt = record.startedAt instanceof Date ? record.startedAt.toISOString() : record.startedAt;
3131
+ const endedAt = record.endedAt instanceof Date ? record.endedAt.toISOString() : record.endedAt;
3132
+ return {
3133
+ ...record,
3134
+ startedAt,
3135
+ endedAt,
3136
+ createdAt: now,
3137
+ updatedAt: now
3138
+ };
3139
+ });
3140
+ return this.#db.batchInsert({
3141
+ tableName: TABLE_SPANS,
3142
+ records
3143
+ });
2836
3144
  } catch (error) {
2837
3145
  throw new MastraError(
2838
3146
  {
2839
- id: createStorageErrorId("LIBSQL", "ALTER_TABLE", "FAILED"),
3147
+ id: createStorageErrorId("LIBSQL", "BATCH_CREATE_SPANS", "FAILED"),
2840
3148
  domain: ErrorDomain.STORAGE,
2841
- category: ErrorCategory.THIRD_PARTY,
2842
- details: {
2843
- tableName
2844
- }
3149
+ category: ErrorCategory.USER
2845
3150
  },
2846
3151
  error
2847
3152
  );
2848
3153
  }
2849
3154
  }
2850
- async clearTable({ tableName }) {
2851
- const parsedTableName = parseSqlIdentifier(tableName, "table name");
3155
+ async batchUpdateSpans(args) {
3156
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2852
3157
  try {
2853
- await this.client.execute(`DELETE FROM ${parsedTableName}`);
2854
- } catch (e) {
2855
- const mastraError = new MastraError(
3158
+ return this.#db.batchUpdate({
3159
+ tableName: TABLE_SPANS,
3160
+ updates: args.records.map((record) => {
3161
+ const data = { ...record.updates };
3162
+ if (data.endedAt instanceof Date) {
3163
+ data.endedAt = data.endedAt.toISOString();
3164
+ }
3165
+ if (data.startedAt instanceof Date) {
3166
+ data.startedAt = data.startedAt.toISOString();
3167
+ }
3168
+ data.updatedAt = now;
3169
+ return {
3170
+ keys: { spanId: record.spanId, traceId: record.traceId },
3171
+ data
3172
+ };
3173
+ })
3174
+ });
3175
+ } catch (error) {
3176
+ throw new MastraError(
2856
3177
  {
2857
- id: createStorageErrorId("LIBSQL", "CLEAR_TABLE", "FAILED"),
3178
+ id: createStorageErrorId("LIBSQL", "BATCH_UPDATE_SPANS", "FAILED"),
2858
3179
  domain: ErrorDomain.STORAGE,
2859
- category: ErrorCategory.THIRD_PARTY,
2860
- details: {
2861
- tableName
2862
- }
3180
+ category: ErrorCategory.USER
2863
3181
  },
2864
- e
3182
+ error
2865
3183
  );
2866
- this.logger?.trackException?.(mastraError);
2867
- this.logger?.error?.(mastraError.toString());
2868
3184
  }
2869
3185
  }
2870
- async dropTable({ tableName }) {
2871
- const parsedTableName = parseSqlIdentifier(tableName, "table name");
3186
+ async batchDeleteTraces(args) {
2872
3187
  try {
2873
- await this.client.execute(`DROP TABLE IF EXISTS ${parsedTableName}`);
2874
- } catch (e) {
3188
+ const keys = args.traceIds.map((traceId) => ({ traceId }));
3189
+ return this.#db.batchDelete({
3190
+ tableName: TABLE_SPANS,
3191
+ keys
3192
+ });
3193
+ } catch (error) {
2875
3194
  throw new MastraError(
2876
3195
  {
2877
- id: createStorageErrorId("LIBSQL", "DROP_TABLE", "FAILED"),
3196
+ id: createStorageErrorId("LIBSQL", "BATCH_DELETE_TRACES", "FAILED"),
2878
3197
  domain: ErrorDomain.STORAGE,
2879
- category: ErrorCategory.THIRD_PARTY,
2880
- details: {
2881
- tableName
2882
- }
3198
+ category: ErrorCategory.USER
2883
3199
  },
2884
- e
3200
+ error
2885
3201
  );
2886
3202
  }
2887
3203
  }
2888
3204
  };
2889
3205
  var ScoresLibSQL = class extends ScoresStorage {
2890
- operations;
2891
- client;
2892
- constructor({ client, operations }) {
3206
+ #db;
3207
+ #client;
3208
+ constructor(config) {
2893
3209
  super();
2894
- this.operations = operations;
2895
- this.client = client;
3210
+ const client = resolveClient(config);
3211
+ this.#client = client;
3212
+ this.#db = new LibSQLDB({ client, maxRetries: config.maxRetries, initialBackoffMs: config.initialBackoffMs });
3213
+ }
3214
+ async init() {
3215
+ await this.#db.createTable({ tableName: TABLE_SCORERS, schema: SCORERS_SCHEMA });
3216
+ await this.#db.alterTable({
3217
+ tableName: TABLE_SCORERS,
3218
+ schema: SCORERS_SCHEMA,
3219
+ ifNotExists: ["spanId", "requestContext"]
3220
+ });
3221
+ }
3222
+ async dangerouslyClearAll() {
3223
+ await this.#db.deleteData({ tableName: TABLE_SCORERS });
2896
3224
  }
2897
3225
  async listScoresByRunId({
2898
3226
  runId,
@@ -2900,7 +3228,7 @@ var ScoresLibSQL = class extends ScoresStorage {
2900
3228
  }) {
2901
3229
  try {
2902
3230
  const { page, perPage: perPageInput } = pagination;
2903
- const countResult = await this.client.execute({
3231
+ const countResult = await this.#client.execute({
2904
3232
  sql: `SELECT COUNT(*) as count FROM ${TABLE_SCORERS} WHERE runId = ?`,
2905
3233
  args: [runId]
2906
3234
  });
@@ -2920,7 +3248,7 @@ var ScoresLibSQL = class extends ScoresStorage {
2920
3248
  const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
2921
3249
  const limitValue = perPageInput === false ? total : perPage;
2922
3250
  const end = perPageInput === false ? total : start + perPage;
2923
- const result = await this.client.execute({
3251
+ const result = await this.#client.execute({
2924
3252
  sql: `SELECT * FROM ${TABLE_SCORERS} WHERE runId = ? ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
2925
3253
  args: [runId, limitValue, start]
2926
3254
  });
@@ -2973,7 +3301,7 @@ var ScoresLibSQL = class extends ScoresStorage {
2973
3301
  queryParams.push(source);
2974
3302
  }
2975
3303
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2976
- const countResult = await this.client.execute({
3304
+ const countResult = await this.#client.execute({
2977
3305
  sql: `SELECT COUNT(*) as count FROM ${TABLE_SCORERS} ${whereClause}`,
2978
3306
  args: queryParams
2979
3307
  });
@@ -2993,7 +3321,7 @@ var ScoresLibSQL = class extends ScoresStorage {
2993
3321
  const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
2994
3322
  const limitValue = perPageInput === false ? total : perPage;
2995
3323
  const end = perPageInput === false ? total : start + perPage;
2996
- const result = await this.client.execute({
3324
+ const result = await this.#client.execute({
2997
3325
  sql: `SELECT * FROM ${TABLE_SCORERS} ${whereClause} ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
2998
3326
  args: [...queryParams, limitValue, start]
2999
3327
  });
@@ -3028,7 +3356,7 @@ var ScoresLibSQL = class extends ScoresStorage {
3028
3356
  });
3029
3357
  }
3030
3358
  async getScoreById({ id }) {
3031
- const result = await this.client.execute({
3359
+ const result = await this.#client.execute({
3032
3360
  sql: `SELECT * FROM ${TABLE_SCORERS} WHERE id = ?`,
3033
3361
  args: [id]
3034
3362
  });
@@ -3045,7 +3373,7 @@ var ScoresLibSQL = class extends ScoresStorage {
3045
3373
  domain: ErrorDomain.STORAGE,
3046
3374
  category: ErrorCategory.USER,
3047
3375
  details: {
3048
- scorer: score.scorer?.id ?? "unknown",
3376
+ scorer: typeof score.scorer?.id === "string" ? score.scorer.id : String(score.scorer?.id ?? "unknown"),
3049
3377
  entityId: score.entityId ?? "unknown",
3050
3378
  entityType: score.entityType ?? "unknown",
3051
3379
  traceId: score.traceId ?? "",
@@ -3058,7 +3386,7 @@ var ScoresLibSQL = class extends ScoresStorage {
3058
3386
  try {
3059
3387
  const id = crypto.randomUUID();
3060
3388
  const now = /* @__PURE__ */ new Date();
3061
- await this.operations.insert({
3389
+ await this.#db.insert({
3062
3390
  tableName: TABLE_SCORERS,
3063
3391
  record: {
3064
3392
  ...parsedScore,
@@ -3086,7 +3414,7 @@ var ScoresLibSQL = class extends ScoresStorage {
3086
3414
  }) {
3087
3415
  try {
3088
3416
  const { page, perPage: perPageInput } = pagination;
3089
- const countResult = await this.client.execute({
3417
+ const countResult = await this.#client.execute({
3090
3418
  sql: `SELECT COUNT(*) as count FROM ${TABLE_SCORERS} WHERE entityId = ? AND entityType = ?`,
3091
3419
  args: [entityId, entityType]
3092
3420
  });
@@ -3106,7 +3434,7 @@ var ScoresLibSQL = class extends ScoresStorage {
3106
3434
  const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
3107
3435
  const limitValue = perPageInput === false ? total : perPage;
3108
3436
  const end = perPageInput === false ? total : start + perPage;
3109
- const result = await this.client.execute({
3437
+ const result = await this.#client.execute({
3110
3438
  sql: `SELECT * FROM ${TABLE_SCORERS} WHERE entityId = ? AND entityType = ? ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
3111
3439
  args: [entityId, entityType, limitValue, start]
3112
3440
  });
@@ -3140,14 +3468,14 @@ var ScoresLibSQL = class extends ScoresStorage {
3140
3468
  const { page, perPage: perPageInput } = pagination;
3141
3469
  const perPage = normalizePerPage(perPageInput, 100);
3142
3470
  const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
3143
- const countSQLResult = await this.client.execute({
3471
+ const countSQLResult = await this.#client.execute({
3144
3472
  sql: `SELECT COUNT(*) as count FROM ${TABLE_SCORERS} WHERE traceId = ? AND spanId = ?`,
3145
3473
  args: [traceId, spanId]
3146
3474
  });
3147
3475
  const total = Number(countSQLResult.rows?.[0]?.count ?? 0);
3148
3476
  const limitValue = perPageInput === false ? total : perPage;
3149
3477
  const end = perPageInput === false ? total : start + perPage;
3150
- const result = await this.client.execute({
3478
+ const result = await this.#client.execute({
3151
3479
  sql: `SELECT * FROM ${TABLE_SCORERS} WHERE traceId = ? AND spanId = ? ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
3152
3480
  args: [traceId, spanId, limitValue, start]
3153
3481
  });
@@ -3192,37 +3520,49 @@ function parseWorkflowRun(row) {
3192
3520
  };
3193
3521
  }
3194
3522
  var WorkflowsLibSQL = class extends WorkflowsStorage {
3195
- operations;
3196
- client;
3197
- maxRetries;
3198
- initialBackoffMs;
3199
- constructor({
3200
- operations,
3201
- client,
3202
- maxRetries = 5,
3203
- initialBackoffMs = 500
3204
- }) {
3523
+ #db;
3524
+ #client;
3525
+ executeWithRetry;
3526
+ constructor(config) {
3205
3527
  super();
3206
- this.operations = operations;
3207
- this.client = client;
3208
- this.maxRetries = maxRetries;
3209
- this.initialBackoffMs = initialBackoffMs;
3528
+ const client = resolveClient(config);
3529
+ const maxRetries = config.maxRetries ?? 5;
3530
+ const initialBackoffMs = config.initialBackoffMs ?? 500;
3531
+ this.#client = client;
3532
+ this.#db = new LibSQLDB({ client, maxRetries, initialBackoffMs });
3533
+ this.executeWithRetry = createExecuteWriteOperationWithRetry({
3534
+ logger: this.logger,
3535
+ maxRetries,
3536
+ initialBackoffMs
3537
+ });
3210
3538
  this.setupPragmaSettings().catch(
3211
3539
  (err) => this.logger.warn("LibSQL Workflows: Failed to setup PRAGMA settings.", err)
3212
3540
  );
3213
3541
  }
3542
+ async init() {
3543
+ const schema = TABLE_SCHEMAS[TABLE_WORKFLOW_SNAPSHOT];
3544
+ await this.#db.createTable({ tableName: TABLE_WORKFLOW_SNAPSHOT, schema });
3545
+ await this.#db.alterTable({
3546
+ tableName: TABLE_WORKFLOW_SNAPSHOT,
3547
+ schema,
3548
+ ifNotExists: ["resourceId"]
3549
+ });
3550
+ }
3551
+ async dangerouslyClearAll() {
3552
+ await this.#db.deleteData({ tableName: TABLE_WORKFLOW_SNAPSHOT });
3553
+ }
3214
3554
  async setupPragmaSettings() {
3215
3555
  try {
3216
- await this.client.execute("PRAGMA busy_timeout = 10000;");
3556
+ await this.#client.execute("PRAGMA busy_timeout = 10000;");
3217
3557
  this.logger.debug("LibSQL Workflows: PRAGMA busy_timeout=10000 set.");
3218
3558
  try {
3219
- await this.client.execute("PRAGMA journal_mode = WAL;");
3559
+ await this.#client.execute("PRAGMA journal_mode = WAL;");
3220
3560
  this.logger.debug("LibSQL Workflows: PRAGMA journal_mode=WAL set.");
3221
3561
  } catch {
3222
3562
  this.logger.debug("LibSQL Workflows: WAL mode not supported, using default journal mode.");
3223
3563
  }
3224
3564
  try {
3225
- await this.client.execute("PRAGMA synchronous = NORMAL;");
3565
+ await this.#client.execute("PRAGMA synchronous = NORMAL;");
3226
3566
  this.logger.debug("LibSQL Workflows: PRAGMA synchronous=NORMAL set.");
3227
3567
  } catch {
3228
3568
  this.logger.debug("LibSQL Workflows: Failed to set synchronous mode.");
@@ -3231,44 +3571,6 @@ var WorkflowsLibSQL = class extends WorkflowsStorage {
3231
3571
  this.logger.warn("LibSQL Workflows: Failed to set PRAGMA settings.", err);
3232
3572
  }
3233
3573
  }
3234
- async executeWithRetry(operation) {
3235
- let attempts = 0;
3236
- let backoff = this.initialBackoffMs;
3237
- while (attempts < this.maxRetries) {
3238
- try {
3239
- return await operation();
3240
- } catch (error) {
3241
- this.logger.debug("LibSQL Workflows: Error caught in retry loop", {
3242
- errorType: error.constructor.name,
3243
- errorCode: error.code,
3244
- errorMessage: error.message,
3245
- attempts,
3246
- maxRetries: this.maxRetries
3247
- });
3248
- const isLockError = error.code === "SQLITE_BUSY" || error.code === "SQLITE_LOCKED" || error.message?.toLowerCase().includes("database is locked") || error.message?.toLowerCase().includes("database table is locked") || error.message?.toLowerCase().includes("table is locked") || error.constructor.name === "SqliteError" && error.message?.toLowerCase().includes("locked");
3249
- if (isLockError) {
3250
- attempts++;
3251
- if (attempts >= this.maxRetries) {
3252
- this.logger.error(
3253
- `LibSQL Workflows: Operation failed after ${this.maxRetries} attempts due to database lock: ${error.message}`,
3254
- { error, attempts, maxRetries: this.maxRetries }
3255
- );
3256
- throw error;
3257
- }
3258
- this.logger.warn(
3259
- `LibSQL Workflows: Attempt ${attempts} failed due to database lock. Retrying in ${backoff}ms...`,
3260
- { errorMessage: error.message, attempts, backoff, maxRetries: this.maxRetries }
3261
- );
3262
- await new Promise((resolve) => setTimeout(resolve, backoff));
3263
- backoff *= 2;
3264
- } else {
3265
- this.logger.error("LibSQL Workflows: Non-lock error occurred, not retrying", { error });
3266
- throw error;
3267
- }
3268
- }
3269
- }
3270
- throw new Error("LibSQL Workflows: Max retries reached, but no error was re-thrown from the loop.");
3271
- }
3272
3574
  async updateWorkflowResults({
3273
3575
  workflowName,
3274
3576
  runId,
@@ -3277,7 +3579,7 @@ var WorkflowsLibSQL = class extends WorkflowsStorage {
3277
3579
  requestContext
3278
3580
  }) {
3279
3581
  return this.executeWithRetry(async () => {
3280
- const tx = await this.client.transaction("write");
3582
+ const tx = await this.#client.transaction("write");
3281
3583
  try {
3282
3584
  const existingSnapshotResult = await tx.execute({
3283
3585
  sql: `SELECT snapshot FROM ${TABLE_WORKFLOW_SNAPSHOT} WHERE workflow_name = ? AND run_id = ?`,
@@ -3317,7 +3619,7 @@ var WorkflowsLibSQL = class extends WorkflowsStorage {
3317
3619
  }
3318
3620
  throw error;
3319
3621
  }
3320
- });
3622
+ }, "updateWorkflowResults");
3321
3623
  }
3322
3624
  async updateWorkflowState({
3323
3625
  workflowName,
@@ -3325,7 +3627,7 @@ var WorkflowsLibSQL = class extends WorkflowsStorage {
3325
3627
  opts
3326
3628
  }) {
3327
3629
  return this.executeWithRetry(async () => {
3328
- const tx = await this.client.transaction("write");
3630
+ const tx = await this.#client.transaction("write");
3329
3631
  try {
3330
3632
  const existingSnapshotResult = await tx.execute({
3331
3633
  sql: `SELECT snapshot FROM ${TABLE_WORKFLOW_SNAPSHOT} WHERE workflow_name = ? AND run_id = ?`,
@@ -3354,24 +3656,27 @@ var WorkflowsLibSQL = class extends WorkflowsStorage {
3354
3656
  }
3355
3657
  throw error;
3356
3658
  }
3357
- });
3659
+ }, "updateWorkflowState");
3358
3660
  }
3359
3661
  async persistWorkflowSnapshot({
3360
3662
  workflowName,
3361
3663
  runId,
3362
3664
  resourceId,
3363
- snapshot
3665
+ snapshot,
3666
+ createdAt,
3667
+ updatedAt
3364
3668
  }) {
3669
+ const now = /* @__PURE__ */ new Date();
3365
3670
  const data = {
3366
3671
  workflow_name: workflowName,
3367
3672
  run_id: runId,
3368
3673
  resourceId,
3369
3674
  snapshot,
3370
- createdAt: /* @__PURE__ */ new Date(),
3371
- updatedAt: /* @__PURE__ */ new Date()
3675
+ createdAt: createdAt ?? now,
3676
+ updatedAt: updatedAt ?? now
3372
3677
  };
3373
3678
  this.logger.debug("Persisting workflow snapshot", { workflowName, runId, data });
3374
- await this.operations.insert({
3679
+ await this.#db.insert({
3375
3680
  tableName: TABLE_WORKFLOW_SNAPSHOT,
3376
3681
  record: data
3377
3682
  });
@@ -3381,7 +3686,7 @@ var WorkflowsLibSQL = class extends WorkflowsStorage {
3381
3686
  runId
3382
3687
  }) {
3383
3688
  this.logger.debug("Loading workflow snapshot", { workflowName, runId });
3384
- const d = await this.operations.load({
3689
+ const d = await this.#db.select({
3385
3690
  tableName: TABLE_WORKFLOW_SNAPSHOT,
3386
3691
  keys: { workflow_name: workflowName, run_id: runId }
3387
3692
  });
@@ -3403,7 +3708,7 @@ var WorkflowsLibSQL = class extends WorkflowsStorage {
3403
3708
  }
3404
3709
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
3405
3710
  try {
3406
- const result = await this.client.execute({
3711
+ const result = await this.#client.execute({
3407
3712
  sql: `SELECT * FROM ${TABLE_WORKFLOW_SNAPSHOT} ${whereClause} ORDER BY createdAt DESC LIMIT 1`,
3408
3713
  args
3409
3714
  });
@@ -3423,22 +3728,24 @@ var WorkflowsLibSQL = class extends WorkflowsStorage {
3423
3728
  }
3424
3729
  }
3425
3730
  async deleteWorkflowRunById({ runId, workflowName }) {
3426
- try {
3427
- await this.client.execute({
3428
- sql: `DELETE FROM ${TABLE_WORKFLOW_SNAPSHOT} WHERE workflow_name = ? AND run_id = ?`,
3429
- args: [workflowName, runId]
3430
- });
3431
- } catch (error) {
3432
- throw new MastraError(
3433
- {
3434
- id: createStorageErrorId("LIBSQL", "DELETE_WORKFLOW_RUN_BY_ID", "FAILED"),
3435
- domain: ErrorDomain.STORAGE,
3436
- category: ErrorCategory.THIRD_PARTY,
3437
- details: { runId, workflowName }
3438
- },
3439
- error
3440
- );
3441
- }
3731
+ return this.executeWithRetry(async () => {
3732
+ try {
3733
+ await this.#client.execute({
3734
+ sql: `DELETE FROM ${TABLE_WORKFLOW_SNAPSHOT} WHERE workflow_name = ? AND run_id = ?`,
3735
+ args: [workflowName, runId]
3736
+ });
3737
+ } catch (error) {
3738
+ throw new MastraError(
3739
+ {
3740
+ id: createStorageErrorId("LIBSQL", "DELETE_WORKFLOW_RUN_BY_ID", "FAILED"),
3741
+ domain: ErrorDomain.STORAGE,
3742
+ category: ErrorCategory.THIRD_PARTY,
3743
+ details: { runId, workflowName }
3744
+ },
3745
+ error
3746
+ );
3747
+ }
3748
+ }, "deleteWorkflowRunById");
3442
3749
  }
3443
3750
  async listWorkflowRuns({
3444
3751
  workflowName,
@@ -3469,7 +3776,7 @@ var WorkflowsLibSQL = class extends WorkflowsStorage {
3469
3776
  args.push(toDate.toISOString());
3470
3777
  }
3471
3778
  if (resourceId) {
3472
- const hasResourceId = await this.operations.hasColumn(TABLE_WORKFLOW_SNAPSHOT, "resourceId");
3779
+ const hasResourceId = await this.#db.hasColumn(TABLE_WORKFLOW_SNAPSHOT, "resourceId");
3473
3780
  if (hasResourceId) {
3474
3781
  conditions.push("resourceId = ?");
3475
3782
  args.push(resourceId);
@@ -3481,7 +3788,7 @@ var WorkflowsLibSQL = class extends WorkflowsStorage {
3481
3788
  let total = 0;
3482
3789
  const usePagination = typeof perPage === "number" && typeof page === "number";
3483
3790
  if (usePagination) {
3484
- const countResult = await this.client.execute({
3791
+ const countResult = await this.#client.execute({
3485
3792
  sql: `SELECT COUNT(*) as count FROM ${TABLE_WORKFLOW_SNAPSHOT} ${whereClause}`,
3486
3793
  args
3487
3794
  });
@@ -3489,7 +3796,7 @@ var WorkflowsLibSQL = class extends WorkflowsStorage {
3489
3796
  }
3490
3797
  const normalizedPerPage = usePagination ? normalizePerPage(perPage, Number.MAX_SAFE_INTEGER) : 0;
3491
3798
  const offset = usePagination ? page * normalizedPerPage : 0;
3492
- const result = await this.client.execute({
3799
+ const result = await this.#client.execute({
3493
3800
  sql: `SELECT * FROM ${TABLE_WORKFLOW_SNAPSHOT} ${whereClause} ORDER BY createdAt DESC${usePagination ? ` LIMIT ? OFFSET ?` : ""}`,
3494
3801
  args: usePagination ? [...args, normalizedPerPage, offset] : args
3495
3802
  });
@@ -3536,18 +3843,17 @@ var LibSQLStore = class extends MastraStorage {
3536
3843
  } else {
3537
3844
  this.client = config.client;
3538
3845
  }
3539
- const operations = new StoreOperationsLibSQL({
3846
+ const domainConfig = {
3540
3847
  client: this.client,
3541
3848
  maxRetries: this.maxRetries,
3542
3849
  initialBackoffMs: this.initialBackoffMs
3543
- });
3544
- const scores = new ScoresLibSQL({ client: this.client, operations });
3545
- const workflows = new WorkflowsLibSQL({ client: this.client, operations });
3546
- const memory = new MemoryLibSQL({ client: this.client, operations });
3547
- const observability = new ObservabilityLibSQL({ operations });
3548
- const agents = new AgentsLibSQL({ client: this.client, operations });
3850
+ };
3851
+ const scores = new ScoresLibSQL(domainConfig);
3852
+ const workflows = new WorkflowsLibSQL(domainConfig);
3853
+ const memory = new MemoryLibSQL(domainConfig);
3854
+ const observability = new ObservabilityLibSQL(domainConfig);
3855
+ const agents = new AgentsLibSQL(domainConfig);
3549
3856
  this.stores = {
3550
- operations,
3551
3857
  scores,
3552
3858
  workflows,
3553
3859
  memory,
@@ -3562,187 +3868,12 @@ var LibSQLStore = class extends MastraStorage {
3562
3868
  hasColumn: true,
3563
3869
  createTable: true,
3564
3870
  deleteMessages: true,
3565
- observabilityInstance: true,
3871
+ observability: true,
3872
+ indexManagement: false,
3566
3873
  listScoresBySpan: true,
3567
3874
  agents: true
3568
3875
  };
3569
3876
  }
3570
- async createTable({
3571
- tableName,
3572
- schema
3573
- }) {
3574
- await this.stores.operations.createTable({ tableName, schema });
3575
- }
3576
- /**
3577
- * Alters table schema to add columns if they don't exist
3578
- * @param tableName Name of the table
3579
- * @param schema Schema of the table
3580
- * @param ifNotExists Array of column names to add if they don't exist
3581
- */
3582
- async alterTable({
3583
- tableName,
3584
- schema,
3585
- ifNotExists
3586
- }) {
3587
- await this.stores.operations.alterTable({ tableName, schema, ifNotExists });
3588
- }
3589
- async clearTable({ tableName }) {
3590
- await this.stores.operations.clearTable({ tableName });
3591
- }
3592
- async dropTable({ tableName }) {
3593
- await this.stores.operations.dropTable({ tableName });
3594
- }
3595
- insert(args) {
3596
- return this.stores.operations.insert(args);
3597
- }
3598
- batchInsert(args) {
3599
- return this.stores.operations.batchInsert(args);
3600
- }
3601
- async load({ tableName, keys }) {
3602
- return this.stores.operations.load({ tableName, keys });
3603
- }
3604
- async getThreadById({ threadId }) {
3605
- return this.stores.memory.getThreadById({ threadId });
3606
- }
3607
- async saveThread({ thread }) {
3608
- return this.stores.memory.saveThread({ thread });
3609
- }
3610
- async updateThread({
3611
- id,
3612
- title,
3613
- metadata
3614
- }) {
3615
- return this.stores.memory.updateThread({ id, title, metadata });
3616
- }
3617
- async deleteThread({ threadId }) {
3618
- return this.stores.memory.deleteThread({ threadId });
3619
- }
3620
- async listMessagesById({ messageIds }) {
3621
- return this.stores.memory.listMessagesById({ messageIds });
3622
- }
3623
- async saveMessages(args) {
3624
- const result = await this.stores.memory.saveMessages({ messages: args.messages });
3625
- return { messages: result.messages };
3626
- }
3627
- async updateMessages({
3628
- messages
3629
- }) {
3630
- return this.stores.memory.updateMessages({ messages });
3631
- }
3632
- async deleteMessages(messageIds) {
3633
- return this.stores.memory.deleteMessages(messageIds);
3634
- }
3635
- async getScoreById({ id }) {
3636
- return this.stores.scores.getScoreById({ id });
3637
- }
3638
- async saveScore(score) {
3639
- return this.stores.scores.saveScore(score);
3640
- }
3641
- async listScoresByScorerId({
3642
- scorerId,
3643
- entityId,
3644
- entityType,
3645
- source,
3646
- pagination
3647
- }) {
3648
- return this.stores.scores.listScoresByScorerId({ scorerId, entityId, entityType, source, pagination });
3649
- }
3650
- async listScoresByRunId({
3651
- runId,
3652
- pagination
3653
- }) {
3654
- return this.stores.scores.listScoresByRunId({ runId, pagination });
3655
- }
3656
- async listScoresByEntityId({
3657
- entityId,
3658
- entityType,
3659
- pagination
3660
- }) {
3661
- return this.stores.scores.listScoresByEntityId({ entityId, entityType, pagination });
3662
- }
3663
- /**
3664
- * WORKFLOWS
3665
- */
3666
- async updateWorkflowResults({
3667
- workflowName,
3668
- runId,
3669
- stepId,
3670
- result,
3671
- requestContext
3672
- }) {
3673
- return this.stores.workflows.updateWorkflowResults({ workflowName, runId, stepId, result, requestContext });
3674
- }
3675
- async updateWorkflowState({
3676
- workflowName,
3677
- runId,
3678
- opts
3679
- }) {
3680
- return this.stores.workflows.updateWorkflowState({ workflowName, runId, opts });
3681
- }
3682
- async persistWorkflowSnapshot({
3683
- workflowName,
3684
- runId,
3685
- resourceId,
3686
- snapshot
3687
- }) {
3688
- return this.stores.workflows.persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot });
3689
- }
3690
- async loadWorkflowSnapshot({
3691
- workflowName,
3692
- runId
3693
- }) {
3694
- return this.stores.workflows.loadWorkflowSnapshot({ workflowName, runId });
3695
- }
3696
- async listWorkflowRuns(args = {}) {
3697
- return this.stores.workflows.listWorkflowRuns(args);
3698
- }
3699
- async getWorkflowRunById({
3700
- runId,
3701
- workflowName
3702
- }) {
3703
- return this.stores.workflows.getWorkflowRunById({ runId, workflowName });
3704
- }
3705
- async deleteWorkflowRunById({ runId, workflowName }) {
3706
- return this.stores.workflows.deleteWorkflowRunById({ runId, workflowName });
3707
- }
3708
- async getResourceById({ resourceId }) {
3709
- return this.stores.memory.getResourceById({ resourceId });
3710
- }
3711
- async saveResource({ resource }) {
3712
- return this.stores.memory.saveResource({ resource });
3713
- }
3714
- async updateResource({
3715
- resourceId,
3716
- workingMemory,
3717
- metadata
3718
- }) {
3719
- return this.stores.memory.updateResource({ resourceId, workingMemory, metadata });
3720
- }
3721
- async createSpan(span) {
3722
- return this.stores.observability.createSpan(span);
3723
- }
3724
- async updateSpan(params) {
3725
- return this.stores.observability.updateSpan(params);
3726
- }
3727
- async getTrace(traceId) {
3728
- return this.stores.observability.getTrace(traceId);
3729
- }
3730
- async getTracesPaginated(args) {
3731
- return this.stores.observability.getTracesPaginated(args);
3732
- }
3733
- async listScoresBySpan({
3734
- traceId,
3735
- spanId,
3736
- pagination
3737
- }) {
3738
- return this.stores.scores.listScoresBySpan({ traceId, spanId, pagination });
3739
- }
3740
- async batchCreateSpans(args) {
3741
- return this.stores.observability.batchCreateSpans(args);
3742
- }
3743
- async batchUpdateSpans(args) {
3744
- return this.stores.observability.batchUpdateSpans(args);
3745
- }
3746
3877
  };
3747
3878
 
3748
3879
  // src/vector/prompt.ts