@mastra/libsql 0.0.0-mcp-changeset-20250707162621 → 0.0.0-memory-system-message-error-20250813233316

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.
Files changed (51) hide show
  1. package/CHANGELOG.md +288 -9
  2. package/LICENSE.md +12 -4
  3. package/dist/index.cjs +1469 -868
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.ts +4 -6
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +1460 -859
  8. package/dist/index.js.map +1 -0
  9. package/dist/storage/domains/legacy-evals/index.d.ts +18 -0
  10. package/dist/storage/domains/legacy-evals/index.d.ts.map +1 -0
  11. package/dist/storage/domains/memory/index.d.ts +87 -0
  12. package/dist/storage/domains/memory/index.d.ts.map +1 -0
  13. package/dist/storage/domains/operations/index.d.ts +61 -0
  14. package/dist/storage/domains/operations/index.d.ts.map +1 -0
  15. package/dist/storage/domains/scores/index.d.ts +46 -0
  16. package/dist/storage/domains/scores/index.d.ts.map +1 -0
  17. package/dist/storage/domains/traces/index.d.ts +21 -0
  18. package/dist/storage/domains/traces/index.d.ts.map +1 -0
  19. package/dist/storage/domains/utils.d.ts +16 -0
  20. package/dist/storage/domains/utils.d.ts.map +1 -0
  21. package/dist/storage/domains/workflows/index.d.ts +34 -0
  22. package/dist/storage/domains/workflows/index.d.ts.map +1 -0
  23. package/dist/storage/index.d.ts +222 -0
  24. package/dist/storage/index.d.ts.map +1 -0
  25. package/dist/vector/filter.d.ts +29 -0
  26. package/dist/vector/filter.d.ts.map +1 -0
  27. package/dist/vector/index.d.ts +72 -0
  28. package/dist/vector/index.d.ts.map +1 -0
  29. package/dist/vector/prompt.d.ts +6 -0
  30. package/dist/vector/prompt.d.ts.map +1 -0
  31. package/dist/vector/sql-builder.d.ts +9 -0
  32. package/dist/vector/sql-builder.d.ts.map +1 -0
  33. package/package.json +11 -10
  34. package/src/storage/domains/legacy-evals/index.ts +149 -0
  35. package/src/storage/domains/memory/index.ts +881 -0
  36. package/src/storage/domains/operations/index.ts +296 -0
  37. package/src/storage/domains/scores/index.ts +226 -0
  38. package/src/storage/domains/traces/index.ts +150 -0
  39. package/src/storage/domains/utils.ts +67 -0
  40. package/src/storage/domains/workflows/index.ts +198 -0
  41. package/src/storage/index.test.ts +2 -573
  42. package/src/storage/index.ts +194 -1336
  43. package/src/vector/index.test.ts +22 -1
  44. package/src/vector/index.ts +2 -2
  45. package/src/vector/sql-builder.ts +51 -33
  46. package/tsconfig.build.json +9 -0
  47. package/tsconfig.json +1 -1
  48. package/tsup.config.ts +17 -0
  49. package/dist/_tsup-dts-rollup.d.cts +0 -332
  50. package/dist/_tsup-dts-rollup.d.ts +0 -332
  51. package/dist/index.d.cts +0 -6
package/dist/index.js CHANGED
@@ -3,8 +3,9 @@ import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
3
3
  import { parseSqlIdentifier, parseFieldKey } from '@mastra/core/utils';
4
4
  import { MastraVector } from '@mastra/core/vector';
5
5
  import { BaseFilterTranslator } from '@mastra/core/vector/filter';
6
+ import { MastraStorage, StoreOperations, TABLE_WORKFLOW_SNAPSHOT, ScoresStorage, TABLE_SCORERS, safelyParseJSON, TracesStorage, TABLE_TRACES, WorkflowsStorage, MemoryStorage, resolveMessageLimit, TABLE_MESSAGES, TABLE_THREADS, TABLE_RESOURCES, LegacyEvalsStorage, TABLE_EVALS } from '@mastra/core/storage';
7
+ import { parseSqlIdentifier as parseSqlIdentifier$1 } from '@mastra/core';
6
8
  import { MessageList } from '@mastra/core/agent';
7
- import { MastraStorage, TABLE_WORKFLOW_SNAPSHOT, TABLE_THREADS, TABLE_MESSAGES, TABLE_EVALS, TABLE_TRACES, TABLE_RESOURCES } from '@mastra/core/storage';
8
9
 
9
10
  // src/vector/index.ts
10
11
  var LibSQLFilterTranslator = class extends BaseFilterTranslator {
@@ -75,11 +76,11 @@ var LibSQLFilterTranslator = class extends BaseFilterTranslator {
75
76
  };
76
77
  var createBasicOperator = (symbol) => {
77
78
  return (key, value) => {
78
- const jsonPathKey = parseJsonPathKey(key);
79
+ const jsonPath = getJsonPath(key);
79
80
  return {
80
81
  sql: `CASE
81
- WHEN ? IS NULL THEN json_extract(metadata, '$."${jsonPathKey}"') IS ${symbol === "=" ? "" : "NOT"} NULL
82
- ELSE json_extract(metadata, '$."${jsonPathKey}"') ${symbol} ?
82
+ WHEN ? IS NULL THEN json_extract(metadata, ${jsonPath}) IS ${symbol === "=" ? "" : "NOT"} NULL
83
+ ELSE json_extract(metadata, ${jsonPath}) ${symbol} ?
83
84
  END`,
84
85
  needsValue: true,
85
86
  transformValue: () => {
@@ -90,16 +91,19 @@ var createBasicOperator = (symbol) => {
90
91
  };
91
92
  var createNumericOperator = (symbol) => {
92
93
  return (key) => {
93
- const jsonPathKey = parseJsonPathKey(key);
94
+ const jsonPath = getJsonPath(key);
94
95
  return {
95
- sql: `CAST(json_extract(metadata, '$."${jsonPathKey}"') AS NUMERIC) ${symbol} ?`,
96
+ sql: `CAST(json_extract(metadata, ${jsonPath}) AS NUMERIC) ${symbol} ?`,
96
97
  needsValue: true
97
98
  };
98
99
  };
99
100
  };
100
- var validateJsonArray = (key) => `json_valid(json_extract(metadata, '$."${key}"'))
101
- AND json_type(json_extract(metadata, '$."${key}"')) = 'array'`;
102
- var pattern = /json_extract\(metadata, '\$\."[^"]*"(\."[^"]*")*'\)/g;
101
+ var validateJsonArray = (key) => {
102
+ const jsonPath = getJsonPath(key);
103
+ return `json_valid(json_extract(metadata, ${jsonPath}))
104
+ AND json_type(json_extract(metadata, ${jsonPath})) = 'array'`;
105
+ };
106
+ var pattern = /json_extract\(metadata, '\$\.(?:"[^"]*"(?:\."[^"]*")*|[^']+)'\)/g;
103
107
  function buildElemMatchConditions(value) {
104
108
  const conditions = Object.entries(value).map(([field, fieldValue]) => {
105
109
  if (field.startsWith("$")) {
@@ -108,12 +112,13 @@ function buildElemMatchConditions(value) {
108
112
  return { sql: elemSql, values };
109
113
  } else if (typeof fieldValue === "object" && !Array.isArray(fieldValue)) {
110
114
  const { sql, values } = buildCondition(field, fieldValue);
111
- const elemSql = sql.replace(pattern, `json_extract(elem.value, '$."${field}"')`);
115
+ const jsonPath = parseJsonPathKey(field);
116
+ const elemSql = sql.replace(pattern, `json_extract(elem.value, '$.${jsonPath}')`);
112
117
  return { sql: elemSql, values };
113
118
  } else {
114
- const parsedFieldKey = parseFieldKey(field);
119
+ const jsonPath = parseJsonPathKey(field);
115
120
  return {
116
- sql: `json_extract(elem.value, '$."${parsedFieldKey}"') = ?`,
121
+ sql: `json_extract(elem.value, '$.${jsonPath}') = ?`,
117
122
  values: [fieldValue]
118
123
  };
119
124
  }
@@ -129,7 +134,7 @@ var FILTER_OPERATORS = {
129
134
  $lte: createNumericOperator("<="),
130
135
  // Array Operators
131
136
  $in: (key, value) => {
132
- const jsonPathKey = parseJsonPathKey(key);
137
+ const jsonPath = getJsonPath(key);
133
138
  const arr = Array.isArray(value) ? value : [value];
134
139
  if (arr.length === 0) {
135
140
  return { sql: "1 = 0", needsValue: true, transformValue: () => [] };
@@ -138,12 +143,12 @@ var FILTER_OPERATORS = {
138
143
  return {
139
144
  sql: `(
140
145
  CASE
141
- WHEN ${validateJsonArray(jsonPathKey)} THEN
146
+ WHEN ${validateJsonArray(key)} THEN
142
147
  EXISTS (
143
- SELECT 1 FROM json_each(json_extract(metadata, '$."${jsonPathKey}"')) as elem
148
+ SELECT 1 FROM json_each(json_extract(metadata, ${jsonPath})) as elem
144
149
  WHERE elem.value IN (SELECT value FROM json_each(?))
145
150
  )
146
- ELSE json_extract(metadata, '$."${jsonPathKey}"') IN (${paramPlaceholders})
151
+ ELSE json_extract(metadata, ${jsonPath}) IN (${paramPlaceholders})
147
152
  END
148
153
  )`,
149
154
  needsValue: true,
@@ -151,7 +156,7 @@ var FILTER_OPERATORS = {
151
156
  };
152
157
  },
153
158
  $nin: (key, value) => {
154
- const jsonPathKey = parseJsonPathKey(key);
159
+ const jsonPath = getJsonPath(key);
155
160
  const arr = Array.isArray(value) ? value : [value];
156
161
  if (arr.length === 0) {
157
162
  return { sql: "1 = 1", needsValue: true, transformValue: () => [] };
@@ -160,12 +165,12 @@ var FILTER_OPERATORS = {
160
165
  return {
161
166
  sql: `(
162
167
  CASE
163
- WHEN ${validateJsonArray(jsonPathKey)} THEN
168
+ WHEN ${validateJsonArray(key)} THEN
164
169
  NOT EXISTS (
165
- SELECT 1 FROM json_each(json_extract(metadata, '$."${jsonPathKey}"')) as elem
170
+ SELECT 1 FROM json_each(json_extract(metadata, ${jsonPath})) as elem
166
171
  WHERE elem.value IN (SELECT value FROM json_each(?))
167
172
  )
168
- ELSE json_extract(metadata, '$."${jsonPathKey}"') NOT IN (${paramPlaceholders})
173
+ ELSE json_extract(metadata, ${jsonPath}) NOT IN (${paramPlaceholders})
169
174
  END
170
175
  )`,
171
176
  needsValue: true,
@@ -173,7 +178,7 @@ var FILTER_OPERATORS = {
173
178
  };
174
179
  },
175
180
  $all: (key, value) => {
176
- const jsonPathKey = parseJsonPathKey(key);
181
+ const jsonPath = getJsonPath(key);
177
182
  let sql;
178
183
  const arrayValue = Array.isArray(value) ? value : [value];
179
184
  if (arrayValue.length === 0) {
@@ -181,13 +186,13 @@ var FILTER_OPERATORS = {
181
186
  } else {
182
187
  sql = `(
183
188
  CASE
184
- WHEN ${validateJsonArray(jsonPathKey)} THEN
189
+ WHEN ${validateJsonArray(key)} THEN
185
190
  NOT EXISTS (
186
191
  SELECT value
187
192
  FROM json_each(?)
188
193
  WHERE value NOT IN (
189
194
  SELECT value
190
- FROM json_each(json_extract(metadata, '$."${jsonPathKey}"'))
195
+ FROM json_each(json_extract(metadata, ${jsonPath}))
191
196
  )
192
197
  )
193
198
  ELSE FALSE
@@ -206,7 +211,7 @@ var FILTER_OPERATORS = {
206
211
  };
207
212
  },
208
213
  $elemMatch: (key, value) => {
209
- const jsonPathKey = parseJsonPathKey(key);
214
+ const jsonPath = getJsonPath(key);
210
215
  if (typeof value !== "object" || Array.isArray(value)) {
211
216
  throw new Error("$elemMatch requires an object with conditions");
212
217
  }
@@ -214,10 +219,10 @@ var FILTER_OPERATORS = {
214
219
  return {
215
220
  sql: `(
216
221
  CASE
217
- WHEN ${validateJsonArray(jsonPathKey)} THEN
222
+ WHEN ${validateJsonArray(key)} THEN
218
223
  EXISTS (
219
224
  SELECT 1
220
- FROM json_each(json_extract(metadata, '$."${jsonPathKey}"')) as elem
225
+ FROM json_each(json_extract(metadata, ${jsonPath})) as elem
221
226
  WHERE ${conditions.map((c) => c.sql).join(" AND ")}
222
227
  )
223
228
  ELSE FALSE
@@ -229,9 +234,9 @@ var FILTER_OPERATORS = {
229
234
  },
230
235
  // Element Operators
231
236
  $exists: (key) => {
232
- const jsonPathKey = parseJsonPathKey(key);
237
+ const jsonPath = getJsonPath(key);
233
238
  return {
234
- sql: `json_extract(metadata, '$."${jsonPathKey}"') IS NOT NULL`,
239
+ sql: `json_extract(metadata, ${jsonPath}) IS NOT NULL`,
235
240
  needsValue: false
236
241
  };
237
242
  },
@@ -250,12 +255,12 @@ var FILTER_OPERATORS = {
250
255
  needsValue: false
251
256
  }),
252
257
  $size: (key, paramIndex) => {
253
- const jsonPathKey = parseJsonPathKey(key);
258
+ const jsonPath = getJsonPath(key);
254
259
  return {
255
260
  sql: `(
256
261
  CASE
257
- WHEN json_type(json_extract(metadata, '$."${jsonPathKey}"')) = 'array' THEN
258
- json_array_length(json_extract(metadata, '$."${jsonPathKey}"')) = $${paramIndex}
262
+ WHEN json_type(json_extract(metadata, ${jsonPath})) = 'array' THEN
263
+ json_array_length(json_extract(metadata, ${jsonPath})) = $${paramIndex}
259
264
  ELSE FALSE
260
265
  END
261
266
  )`,
@@ -374,7 +379,14 @@ function isFilterResult(obj) {
374
379
  }
375
380
  var parseJsonPathKey = (key) => {
376
381
  const parsedKey = parseFieldKey(key);
377
- return parsedKey.replace(/\./g, '"."');
382
+ if (parsedKey.includes(".")) {
383
+ return parsedKey.split(".").map((segment) => `"${segment}"`).join(".");
384
+ }
385
+ return parsedKey;
386
+ };
387
+ var getJsonPath = (key) => {
388
+ const jsonPathKey = parseJsonPathKey(key);
389
+ return `'$.${jsonPathKey}'`;
378
390
  };
379
391
  function escapeLikePattern(str) {
380
392
  return str.replace(/([%_\\])/g, "\\$1");
@@ -399,8 +411,9 @@ function buildCondition(key, value, parentPath) {
399
411
  return handleLogicalOperator(key, value);
400
412
  }
401
413
  if (!value || typeof value !== "object") {
414
+ const jsonPath = getJsonPath(key);
402
415
  return {
403
- sql: `json_extract(metadata, '$."${key.replace(/\./g, '"."')}"') = ?`,
416
+ sql: `json_extract(metadata, ${jsonPath}) = ?`,
404
417
  values: [value]
405
418
  };
406
419
  }
@@ -546,7 +559,8 @@ var LibSQLVector = class extends MastraVector {
546
559
  topK = 10,
547
560
  filter,
548
561
  includeVector = false,
549
- minScore = 0
562
+ minScore = -1
563
+ // Default to -1 to include all results (cosine similarity ranges from -1 to 1)
550
564
  }) {
551
565
  try {
552
566
  if (!Number.isInteger(topK) || topK <= 0) {
@@ -649,7 +663,7 @@ var LibSQLVector = class extends MastraVector {
649
663
  await tx.commit();
650
664
  return vectorIds;
651
665
  } catch (error) {
652
- await tx.rollback();
666
+ !tx.closed && await tx.rollback();
653
667
  if (error instanceof Error && error.message?.includes("dimensions are different")) {
654
668
  const match = error.message.match(/dimensions are different: (\d+) != (\d+)/);
655
669
  if (match) {
@@ -904,270 +918,235 @@ var LibSQLVector = class extends MastraVector {
904
918
  });
905
919
  }
906
920
  };
907
- function safelyParseJSON(jsonString) {
908
- try {
909
- return JSON.parse(jsonString);
910
- } catch {
911
- return {};
921
+ function transformEvalRow(row) {
922
+ const resultValue = JSON.parse(row.result);
923
+ const testInfoValue = row.test_info ? JSON.parse(row.test_info) : void 0;
924
+ if (!resultValue || typeof resultValue !== "object" || !("score" in resultValue)) {
925
+ throw new Error(`Invalid MetricResult format: ${JSON.stringify(resultValue)}`);
912
926
  }
927
+ return {
928
+ input: row.input,
929
+ output: row.output,
930
+ result: resultValue,
931
+ agentName: row.agent_name,
932
+ metricName: row.metric_name,
933
+ instructions: row.instructions,
934
+ testInfo: testInfoValue,
935
+ globalRunId: row.global_run_id,
936
+ runId: row.run_id,
937
+ createdAt: row.created_at
938
+ };
913
939
  }
914
- var LibSQLStore = class extends MastraStorage {
940
+ var LegacyEvalsLibSQL = class extends LegacyEvalsStorage {
915
941
  client;
916
- maxRetries;
917
- initialBackoffMs;
918
- constructor(config) {
919
- super({ name: `LibSQLStore` });
920
- this.maxRetries = config.maxRetries ?? 5;
921
- this.initialBackoffMs = config.initialBackoffMs ?? 100;
922
- if (config.url.endsWith(":memory:")) {
923
- this.shouldCacheInit = false;
924
- }
925
- this.client = createClient(config);
926
- if (config.url.startsWith("file:") || config.url.includes(":memory:")) {
927
- this.client.execute("PRAGMA journal_mode=WAL;").then(() => this.logger.debug("LibSQLStore: PRAGMA journal_mode=WAL set.")).catch((err) => this.logger.warn("LibSQLStore: Failed to set PRAGMA journal_mode=WAL.", err));
928
- this.client.execute("PRAGMA busy_timeout = 5000;").then(() => this.logger.debug("LibSQLStore: PRAGMA busy_timeout=5000 set.")).catch((err) => this.logger.warn("LibSQLStore: Failed to set PRAGMA busy_timeout.", err));
929
- }
930
- }
931
- get supports() {
932
- return {
933
- selectByIncludeResourceScope: true,
934
- resourceWorkingMemory: true
935
- };
936
- }
937
- getCreateTableSQL(tableName, schema) {
938
- const parsedTableName = parseSqlIdentifier(tableName, "table name");
939
- const columns = Object.entries(schema).map(([name, col]) => {
940
- const parsedColumnName = parseSqlIdentifier(name, "column name");
941
- let type = col.type.toUpperCase();
942
- if (type === "TEXT") type = "TEXT";
943
- if (type === "TIMESTAMP") type = "TEXT";
944
- const nullable = col.nullable ? "" : "NOT NULL";
945
- const primaryKey = col.primaryKey ? "PRIMARY KEY" : "";
946
- return `${parsedColumnName} ${type} ${nullable} ${primaryKey}`.trim();
947
- });
948
- if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
949
- const stmnt = `CREATE TABLE IF NOT EXISTS ${parsedTableName} (
950
- ${columns.join(",\n")},
951
- PRIMARY KEY (workflow_name, run_id)
952
- )`;
953
- return stmnt;
954
- }
955
- return `CREATE TABLE IF NOT EXISTS ${parsedTableName} (${columns.join(", ")})`;
942
+ constructor({ client }) {
943
+ super();
944
+ this.client = client;
956
945
  }
957
- async createTable({
958
- tableName,
959
- schema
960
- }) {
946
+ /** @deprecated use getEvals instead */
947
+ async getEvalsByAgentName(agentName, type) {
961
948
  try {
962
- this.logger.debug(`Creating database table`, { tableName, operation: "schema init" });
963
- const sql = this.getCreateTableSQL(tableName, schema);
964
- await this.client.execute(sql);
949
+ const baseQuery = `SELECT * FROM ${TABLE_EVALS} WHERE agent_name = ?`;
950
+ const typeCondition = type === "test" ? " AND test_info IS NOT NULL AND test_info->>'testPath' IS NOT NULL" : type === "live" ? " AND (test_info IS NULL OR test_info->>'testPath' IS NULL)" : "";
951
+ const result = await this.client.execute({
952
+ sql: `${baseQuery}${typeCondition} ORDER BY created_at DESC`,
953
+ args: [agentName]
954
+ });
955
+ return result.rows?.map((row) => transformEvalRow(row)) ?? [];
965
956
  } catch (error) {
957
+ if (error instanceof Error && error.message.includes("no such table")) {
958
+ return [];
959
+ }
966
960
  throw new MastraError(
967
961
  {
968
- id: "LIBSQL_STORE_CREATE_TABLE_FAILED",
962
+ id: "LIBSQL_STORE_GET_EVALS_BY_AGENT_NAME_FAILED",
969
963
  domain: ErrorDomain.STORAGE,
970
964
  category: ErrorCategory.THIRD_PARTY,
971
- details: {
972
- tableName
973
- }
965
+ details: { agentName }
974
966
  },
975
967
  error
976
968
  );
977
969
  }
978
970
  }
979
- getSqlType(type) {
980
- switch (type) {
981
- case "bigint":
982
- return "INTEGER";
983
- // SQLite uses INTEGER for all integer sizes
984
- case "jsonb":
985
- return "TEXT";
986
- // Store JSON as TEXT in SQLite
987
- default:
988
- return super.getSqlType(type);
971
+ async getEvals(options = {}) {
972
+ const { agentName, type, page = 0, perPage = 100, dateRange } = options;
973
+ const fromDate = dateRange?.start;
974
+ const toDate = dateRange?.end;
975
+ const conditions = [];
976
+ const queryParams = [];
977
+ if (agentName) {
978
+ conditions.push(`agent_name = ?`);
979
+ queryParams.push(agentName);
989
980
  }
990
- }
991
- /**
992
- * Alters table schema to add columns if they don't exist
993
- * @param tableName Name of the table
994
- * @param schema Schema of the table
995
- * @param ifNotExists Array of column names to add if they don't exist
996
- */
997
- async alterTable({
998
- tableName,
999
- schema,
1000
- ifNotExists
1001
- }) {
1002
- const parsedTableName = parseSqlIdentifier(tableName, "table name");
981
+ if (type === "test") {
982
+ conditions.push(`(test_info IS NOT NULL AND json_extract(test_info, '$.testPath') IS NOT NULL)`);
983
+ } else if (type === "live") {
984
+ conditions.push(`(test_info IS NULL OR json_extract(test_info, '$.testPath') IS NULL)`);
985
+ }
986
+ if (fromDate) {
987
+ conditions.push(`created_at >= ?`);
988
+ queryParams.push(fromDate.toISOString());
989
+ }
990
+ if (toDate) {
991
+ conditions.push(`created_at <= ?`);
992
+ queryParams.push(toDate.toISOString());
993
+ }
994
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1003
995
  try {
1004
- const pragmaQuery = `PRAGMA table_info(${parsedTableName})`;
1005
- const result = await this.client.execute(pragmaQuery);
1006
- const existingColumnNames = new Set(result.rows.map((row) => row.name.toLowerCase()));
1007
- for (const columnName of ifNotExists) {
1008
- if (!existingColumnNames.has(columnName.toLowerCase()) && schema[columnName]) {
1009
- const columnDef = schema[columnName];
1010
- const sqlType = this.getSqlType(columnDef.type);
1011
- const nullable = columnDef.nullable === false ? "NOT NULL" : "";
1012
- const defaultValue = columnDef.nullable === false ? this.getDefaultValue(columnDef.type) : "";
1013
- const alterSql = `ALTER TABLE ${parsedTableName} ADD COLUMN "${columnName}" ${sqlType} ${nullable} ${defaultValue}`.trim();
1014
- await this.client.execute(alterSql);
1015
- this.logger?.debug?.(`Added column ${columnName} to table ${parsedTableName}`);
1016
- }
996
+ const countResult = await this.client.execute({
997
+ sql: `SELECT COUNT(*) as count FROM ${TABLE_EVALS} ${whereClause}`,
998
+ args: queryParams
999
+ });
1000
+ const total = Number(countResult.rows?.[0]?.count ?? 0);
1001
+ const currentOffset = page * perPage;
1002
+ const hasMore = currentOffset + perPage < total;
1003
+ if (total === 0) {
1004
+ return {
1005
+ evals: [],
1006
+ total: 0,
1007
+ page,
1008
+ perPage,
1009
+ hasMore: false
1010
+ };
1017
1011
  }
1012
+ const dataResult = await this.client.execute({
1013
+ sql: `SELECT * FROM ${TABLE_EVALS} ${whereClause} ORDER BY created_at DESC LIMIT ? OFFSET ?`,
1014
+ args: [...queryParams, perPage, currentOffset]
1015
+ });
1016
+ return {
1017
+ evals: dataResult.rows?.map((row) => transformEvalRow(row)) ?? [],
1018
+ total,
1019
+ page,
1020
+ perPage,
1021
+ hasMore
1022
+ };
1018
1023
  } catch (error) {
1019
1024
  throw new MastraError(
1020
1025
  {
1021
- id: "LIBSQL_STORE_ALTER_TABLE_FAILED",
1026
+ id: "LIBSQL_STORE_GET_EVALS_FAILED",
1022
1027
  domain: ErrorDomain.STORAGE,
1023
- category: ErrorCategory.THIRD_PARTY,
1024
- details: {
1025
- tableName
1026
- }
1028
+ category: ErrorCategory.THIRD_PARTY
1027
1029
  },
1028
1030
  error
1029
1031
  );
1030
1032
  }
1031
1033
  }
1032
- async clearTable({ tableName }) {
1033
- const parsedTableName = parseSqlIdentifier(tableName, "table name");
1034
+ };
1035
+ var MemoryLibSQL = class extends MemoryStorage {
1036
+ client;
1037
+ operations;
1038
+ constructor({ client, operations }) {
1039
+ super();
1040
+ this.client = client;
1041
+ this.operations = operations;
1042
+ }
1043
+ parseRow(row) {
1044
+ let content = row.content;
1034
1045
  try {
1035
- await this.client.execute(`DELETE FROM ${parsedTableName}`);
1036
- } catch (e) {
1037
- const mastraError = new MastraError(
1038
- {
1039
- id: "LIBSQL_STORE_CLEAR_TABLE_FAILED",
1040
- domain: ErrorDomain.STORAGE,
1041
- category: ErrorCategory.THIRD_PARTY,
1042
- details: {
1043
- tableName
1044
- }
1045
- },
1046
- e
1047
- );
1048
- this.logger?.trackException?.(mastraError);
1049
- this.logger?.error?.(mastraError.toString());
1046
+ content = JSON.parse(row.content);
1047
+ } catch {
1050
1048
  }
1051
- }
1052
- prepareStatement({ tableName, record }) {
1053
- const parsedTableName = parseSqlIdentifier(tableName, "table name");
1054
- const columns = Object.keys(record).map((col) => parseSqlIdentifier(col, "column name"));
1055
- const values = Object.values(record).map((v) => {
1056
- if (typeof v === `undefined`) {
1057
- return null;
1058
- }
1059
- if (v instanceof Date) {
1060
- return v.toISOString();
1061
- }
1062
- return typeof v === "object" ? JSON.stringify(v) : v;
1063
- });
1064
- const placeholders = values.map(() => "?").join(", ");
1065
- return {
1066
- sql: `INSERT OR REPLACE INTO ${parsedTableName} (${columns.join(", ")}) VALUES (${placeholders})`,
1067
- args: values
1049
+ const result = {
1050
+ id: row.id,
1051
+ content,
1052
+ role: row.role,
1053
+ createdAt: new Date(row.createdAt),
1054
+ threadId: row.thread_id,
1055
+ resourceId: row.resourceId
1068
1056
  };
1057
+ if (row.type && row.type !== `v2`) result.type = row.type;
1058
+ return result;
1069
1059
  }
1070
- async executeWriteOperationWithRetry(operationFn, operationDescription) {
1071
- let retries = 0;
1072
- while (true) {
1073
- try {
1074
- return await operationFn();
1075
- } catch (error) {
1076
- if (error.message && (error.message.includes("SQLITE_BUSY") || error.message.includes("database is locked")) && retries < this.maxRetries) {
1077
- retries++;
1078
- const backoffTime = this.initialBackoffMs * Math.pow(2, retries - 1);
1079
- this.logger.warn(
1080
- `LibSQLStore: Encountered SQLITE_BUSY during ${operationDescription}. Retrying (${retries}/${this.maxRetries}) in ${backoffTime}ms...`
1081
- );
1082
- await new Promise((resolve) => setTimeout(resolve, backoffTime));
1083
- } else {
1084
- this.logger.error(`LibSQLStore: Error during ${operationDescription} after ${retries} retries: ${error}`);
1085
- throw error;
1086
- }
1087
- }
1060
+ async _getIncludedMessages({
1061
+ threadId,
1062
+ selectBy
1063
+ }) {
1064
+ const include = selectBy?.include;
1065
+ if (!include) return null;
1066
+ const unionQueries = [];
1067
+ const params = [];
1068
+ for (const inc of include) {
1069
+ const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
1070
+ const searchId = inc.threadId || threadId;
1071
+ unionQueries.push(
1072
+ `
1073
+ SELECT * FROM (
1074
+ WITH numbered_messages AS (
1075
+ SELECT
1076
+ id, content, role, type, "createdAt", thread_id, "resourceId",
1077
+ ROW_NUMBER() OVER (ORDER BY "createdAt" ASC) as row_num
1078
+ FROM "${TABLE_MESSAGES}"
1079
+ WHERE thread_id = ?
1080
+ ),
1081
+ target_positions AS (
1082
+ SELECT row_num as target_pos
1083
+ FROM numbered_messages
1084
+ WHERE id = ?
1085
+ )
1086
+ SELECT DISTINCT m.*
1087
+ FROM numbered_messages m
1088
+ CROSS JOIN target_positions t
1089
+ WHERE m.row_num BETWEEN (t.target_pos - ?) AND (t.target_pos + ?)
1090
+ )
1091
+ `
1092
+ // Keep ASC for final sorting after fetching context
1093
+ );
1094
+ params.push(searchId, id, withPreviousMessages, withNextMessages);
1088
1095
  }
1096
+ const finalQuery = unionQueries.join(" UNION ALL ") + ' ORDER BY "createdAt" ASC';
1097
+ const includedResult = await this.client.execute({ sql: finalQuery, args: params });
1098
+ const includedRows = includedResult.rows?.map((row) => this.parseRow(row));
1099
+ const seen = /* @__PURE__ */ new Set();
1100
+ const dedupedRows = includedRows.filter((row) => {
1101
+ if (seen.has(row.id)) return false;
1102
+ seen.add(row.id);
1103
+ return true;
1104
+ });
1105
+ return dedupedRows;
1089
1106
  }
1090
- insert(args) {
1091
- return this.executeWriteOperationWithRetry(() => this.doInsert(args), `insert into table ${args.tableName}`);
1092
- }
1093
- async doInsert({
1094
- tableName,
1095
- record
1107
+ async getMessages({
1108
+ threadId,
1109
+ selectBy,
1110
+ format
1096
1111
  }) {
1097
- await this.client.execute(
1098
- this.prepareStatement({
1099
- tableName,
1100
- record
1101
- })
1102
- );
1103
- }
1104
- batchInsert(args) {
1105
- return this.executeWriteOperationWithRetry(
1106
- () => this.doBatchInsert(args),
1107
- `batch insert into table ${args.tableName}`
1108
- ).catch((error) => {
1109
- throw new MastraError(
1110
- {
1111
- id: "LIBSQL_STORE_BATCH_INSERT_FAILED",
1112
- domain: ErrorDomain.STORAGE,
1113
- category: ErrorCategory.THIRD_PARTY,
1114
- details: {
1115
- tableName: args.tableName
1116
- }
1117
- },
1118
- error
1119
- );
1120
- });
1121
- }
1122
- async doBatchInsert({
1123
- tableName,
1124
- records
1125
- }) {
1126
- if (records.length === 0) return;
1127
- const batchStatements = records.map((r) => this.prepareStatement({ tableName, record: r }));
1128
- await this.client.batch(batchStatements, "write");
1129
- }
1130
- async load({ tableName, keys }) {
1131
- const parsedTableName = parseSqlIdentifier(tableName, "table name");
1132
- const parsedKeys = Object.keys(keys).map((key) => parseSqlIdentifier(key, "column name"));
1133
- const conditions = parsedKeys.map((key) => `${key} = ?`).join(" AND ");
1134
- const values = Object.values(keys);
1135
- const result = await this.client.execute({
1136
- sql: `SELECT * FROM ${parsedTableName} WHERE ${conditions} ORDER BY createdAt DESC LIMIT 1`,
1137
- args: values
1138
- });
1139
- if (!result.rows || result.rows.length === 0) {
1140
- return null;
1141
- }
1142
- const row = result.rows[0];
1143
- const parsed = Object.fromEntries(
1144
- Object.entries(row || {}).map(([k, v]) => {
1145
- try {
1146
- return [k, typeof v === "string" ? v.startsWith("{") || v.startsWith("[") ? JSON.parse(v) : v : v];
1147
- } catch {
1148
- return [k, v];
1149
- }
1150
- })
1151
- );
1152
- return parsed;
1153
- }
1154
- async getThreadById({ threadId }) {
1155
1112
  try {
1156
- const result = await this.load({
1157
- tableName: TABLE_THREADS,
1158
- keys: { id: threadId }
1159
- });
1160
- if (!result) {
1161
- return null;
1113
+ const messages = [];
1114
+ const limit = resolveMessageLimit({ last: selectBy?.last, defaultLimit: 40 });
1115
+ if (selectBy?.include?.length) {
1116
+ const includeMessages = await this._getIncludedMessages({ threadId, selectBy });
1117
+ if (includeMessages) {
1118
+ messages.push(...includeMessages);
1119
+ }
1162
1120
  }
1163
- return {
1164
- ...result,
1165
- metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata
1166
- };
1121
+ const excludeIds = messages.map((m) => m.id);
1122
+ const remainingSql = `
1123
+ SELECT
1124
+ id,
1125
+ content,
1126
+ role,
1127
+ type,
1128
+ "createdAt",
1129
+ thread_id,
1130
+ "resourceId"
1131
+ FROM "${TABLE_MESSAGES}"
1132
+ WHERE thread_id = ?
1133
+ ${excludeIds.length ? `AND id NOT IN (${excludeIds.map(() => "?").join(", ")})` : ""}
1134
+ ORDER BY "createdAt" DESC
1135
+ LIMIT ?
1136
+ `;
1137
+ const remainingArgs = [threadId, ...excludeIds.length ? excludeIds : [], limit];
1138
+ const remainingResult = await this.client.execute({ sql: remainingSql, args: remainingArgs });
1139
+ if (remainingResult.rows) {
1140
+ messages.push(...remainingResult.rows.map((row) => this.parseRow(row)));
1141
+ }
1142
+ messages.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
1143
+ const list = new MessageList().add(messages, "memory");
1144
+ if (format === `v2`) return list.get.all.v2();
1145
+ return list.get.all.v1();
1167
1146
  } catch (error) {
1168
1147
  throw new MastraError(
1169
1148
  {
1170
- id: "LIBSQL_STORE_GET_THREAD_BY_ID_FAILED",
1149
+ id: "LIBSQL_STORE_GET_MESSAGES_FAILED",
1171
1150
  domain: ErrorDomain.STORAGE,
1172
1151
  category: ErrorCategory.THIRD_PARTY,
1173
1152
  details: { threadId }
@@ -1176,130 +1155,517 @@ var LibSQLStore = class extends MastraStorage {
1176
1155
  );
1177
1156
  }
1178
1157
  }
1179
- /**
1180
- * @deprecated use getThreadsByResourceIdPaginated instead for paginated results.
1181
- */
1182
- async getThreadsByResourceId(args) {
1183
- const { resourceId } = args;
1184
- try {
1185
- const baseQuery = `FROM ${TABLE_THREADS} WHERE resourceId = ?`;
1186
- const queryParams = [resourceId];
1187
- const mapRowToStorageThreadType = (row) => ({
1188
- id: row.id,
1189
- resourceId: row.resourceId,
1190
- title: row.title,
1191
- createdAt: new Date(row.createdAt),
1192
- // Convert string to Date
1193
- updatedAt: new Date(row.updatedAt),
1194
- // Convert string to Date
1195
- metadata: typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata
1196
- });
1197
- const result = await this.client.execute({
1198
- sql: `SELECT * ${baseQuery} ORDER BY createdAt DESC`,
1199
- args: queryParams
1200
- });
1201
- if (!result.rows) {
1202
- return [];
1158
+ async getMessagesPaginated(args) {
1159
+ const { threadId, format, selectBy } = args;
1160
+ const { page = 0, perPage: perPageInput, dateRange } = selectBy?.pagination || {};
1161
+ const perPage = perPageInput !== void 0 ? perPageInput : resolveMessageLimit({ last: selectBy?.last, defaultLimit: 40 });
1162
+ const fromDate = dateRange?.start;
1163
+ const toDate = dateRange?.end;
1164
+ const messages = [];
1165
+ if (selectBy?.include?.length) {
1166
+ try {
1167
+ const includeMessages = await this._getIncludedMessages({ threadId, selectBy });
1168
+ if (includeMessages) {
1169
+ messages.push(...includeMessages);
1170
+ }
1171
+ } catch (error) {
1172
+ throw new MastraError(
1173
+ {
1174
+ id: "LIBSQL_STORE_GET_MESSAGES_PAGINATED_GET_INCLUDE_MESSAGES_FAILED",
1175
+ domain: ErrorDomain.STORAGE,
1176
+ category: ErrorCategory.THIRD_PARTY,
1177
+ details: { threadId }
1178
+ },
1179
+ error
1180
+ );
1203
1181
  }
1204
- return result.rows.map(mapRowToStorageThreadType);
1205
- } catch (error) {
1206
- const mastraError = new MastraError(
1207
- {
1208
- id: "LIBSQL_STORE_GET_THREADS_BY_RESOURCE_ID_FAILED",
1209
- domain: ErrorDomain.STORAGE,
1210
- category: ErrorCategory.THIRD_PARTY,
1211
- details: { resourceId }
1212
- },
1213
- error
1214
- );
1215
- this.logger?.trackException?.(mastraError);
1216
- this.logger?.error?.(mastraError.toString());
1217
- return [];
1218
1182
  }
1219
- }
1220
- async getThreadsByResourceIdPaginated(args) {
1221
- const { resourceId, page = 0, perPage = 100 } = args;
1222
1183
  try {
1223
- const baseQuery = `FROM ${TABLE_THREADS} WHERE resourceId = ?`;
1224
- const queryParams = [resourceId];
1225
- const mapRowToStorageThreadType = (row) => ({
1226
- id: row.id,
1227
- resourceId: row.resourceId,
1228
- title: row.title,
1229
- createdAt: new Date(row.createdAt),
1230
- // Convert string to Date
1231
- updatedAt: new Date(row.updatedAt),
1232
- // Convert string to Date
1233
- metadata: typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata
1234
- });
1235
1184
  const currentOffset = page * perPage;
1185
+ const conditions = [`thread_id = ?`];
1186
+ const queryParams = [threadId];
1187
+ if (fromDate) {
1188
+ conditions.push(`"createdAt" >= ?`);
1189
+ queryParams.push(fromDate.toISOString());
1190
+ }
1191
+ if (toDate) {
1192
+ conditions.push(`"createdAt" <= ?`);
1193
+ queryParams.push(toDate.toISOString());
1194
+ }
1195
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1236
1196
  const countResult = await this.client.execute({
1237
- sql: `SELECT COUNT(*) as count ${baseQuery}`,
1197
+ sql: `SELECT COUNT(*) as count FROM ${TABLE_MESSAGES} ${whereClause}`,
1238
1198
  args: queryParams
1239
1199
  });
1240
1200
  const total = Number(countResult.rows?.[0]?.count ?? 0);
1241
- if (total === 0) {
1201
+ if (total === 0 && messages.length === 0) {
1242
1202
  return {
1243
- threads: [],
1203
+ messages: [],
1244
1204
  total: 0,
1245
1205
  page,
1246
1206
  perPage,
1247
1207
  hasMore: false
1248
1208
  };
1249
1209
  }
1210
+ const excludeIds = messages.map((m) => m.id);
1211
+ const excludeIdsParam = excludeIds.map((_, idx) => `$${idx + queryParams.length + 1}`).join(", ");
1250
1212
  const dataResult = await this.client.execute({
1251
- sql: `SELECT * ${baseQuery} ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
1252
- args: [...queryParams, perPage, currentOffset]
1213
+ sql: `SELECT id, content, role, type, "createdAt", "resourceId", "thread_id" FROM ${TABLE_MESSAGES} ${whereClause} ${excludeIds.length ? `AND id NOT IN (${excludeIdsParam})` : ""} ORDER BY "createdAt" DESC LIMIT ? OFFSET ?`,
1214
+ args: [...queryParams, ...excludeIds, perPage, currentOffset]
1253
1215
  });
1254
- const threads = (dataResult.rows || []).map(mapRowToStorageThreadType);
1216
+ messages.push(...(dataResult.rows || []).map((row) => this.parseRow(row)));
1217
+ const messagesToReturn = format === "v1" ? new MessageList().add(messages, "memory").get.all.v1() : new MessageList().add(messages, "memory").get.all.v2();
1255
1218
  return {
1256
- threads,
1219
+ messages: messagesToReturn,
1257
1220
  total,
1258
1221
  page,
1259
1222
  perPage,
1260
- hasMore: currentOffset + threads.length < total
1223
+ hasMore: currentOffset + messages.length < total
1261
1224
  };
1262
1225
  } catch (error) {
1263
1226
  const mastraError = new MastraError(
1264
1227
  {
1265
- id: "LIBSQL_STORE_GET_THREADS_BY_RESOURCE_ID_FAILED",
1228
+ id: "LIBSQL_STORE_GET_MESSAGES_PAGINATED_FAILED",
1266
1229
  domain: ErrorDomain.STORAGE,
1267
1230
  category: ErrorCategory.THIRD_PARTY,
1268
- details: { resourceId }
1231
+ details: { threadId }
1269
1232
  },
1270
1233
  error
1271
1234
  );
1272
1235
  this.logger?.trackException?.(mastraError);
1273
1236
  this.logger?.error?.(mastraError.toString());
1274
- return { threads: [], total: 0, page, perPage, hasMore: false };
1237
+ return { messages: [], total: 0, page, perPage, hasMore: false };
1275
1238
  }
1276
1239
  }
1277
- async saveThread({ thread }) {
1240
+ async saveMessages({
1241
+ messages,
1242
+ format
1243
+ }) {
1244
+ if (messages.length === 0) return messages;
1278
1245
  try {
1279
- await this.insert({
1280
- tableName: TABLE_THREADS,
1281
- record: {
1282
- ...thread,
1283
- metadata: JSON.stringify(thread.metadata)
1246
+ const threadId = messages[0]?.threadId;
1247
+ if (!threadId) {
1248
+ throw new Error("Thread ID is required");
1249
+ }
1250
+ const batchStatements = messages.map((message) => {
1251
+ const time = message.createdAt || /* @__PURE__ */ new Date();
1252
+ if (!message.threadId) {
1253
+ throw new Error(
1254
+ `Expected to find a threadId for message, but couldn't find one. An unexpected error has occurred.`
1255
+ );
1256
+ }
1257
+ if (!message.resourceId) {
1258
+ throw new Error(
1259
+ `Expected to find a resourceId for message, but couldn't find one. An unexpected error has occurred.`
1260
+ );
1284
1261
  }
1262
+ return {
1263
+ sql: `INSERT INTO "${TABLE_MESSAGES}" (id, thread_id, content, role, type, "createdAt", "resourceId")
1264
+ VALUES (?, ?, ?, ?, ?, ?, ?)
1265
+ ON CONFLICT(id) DO UPDATE SET
1266
+ thread_id=excluded.thread_id,
1267
+ content=excluded.content,
1268
+ role=excluded.role,
1269
+ type=excluded.type,
1270
+ "resourceId"=excluded."resourceId"
1271
+ `,
1272
+ args: [
1273
+ message.id,
1274
+ message.threadId,
1275
+ typeof message.content === "object" ? JSON.stringify(message.content) : message.content,
1276
+ message.role,
1277
+ message.type || "v2",
1278
+ time instanceof Date ? time.toISOString() : time,
1279
+ message.resourceId
1280
+ ]
1281
+ };
1285
1282
  });
1286
- return thread;
1283
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1284
+ batchStatements.push({
1285
+ sql: `UPDATE "${TABLE_THREADS}" SET "updatedAt" = ? WHERE id = ?`,
1286
+ args: [now, threadId]
1287
+ });
1288
+ const BATCH_SIZE = 50;
1289
+ const messageStatements = batchStatements.slice(0, -1);
1290
+ const threadUpdateStatement = batchStatements[batchStatements.length - 1];
1291
+ for (let i = 0; i < messageStatements.length; i += BATCH_SIZE) {
1292
+ const batch = messageStatements.slice(i, i + BATCH_SIZE);
1293
+ if (batch.length > 0) {
1294
+ await this.client.batch(batch, "write");
1295
+ }
1296
+ }
1297
+ if (threadUpdateStatement) {
1298
+ await this.client.execute(threadUpdateStatement);
1299
+ }
1300
+ const list = new MessageList().add(messages, "memory");
1301
+ if (format === `v2`) return list.get.all.v2();
1302
+ return list.get.all.v1();
1287
1303
  } catch (error) {
1288
- const mastraError = new MastraError(
1304
+ throw new MastraError(
1289
1305
  {
1290
- id: "LIBSQL_STORE_SAVE_THREAD_FAILED",
1306
+ id: "LIBSQL_STORE_SAVE_MESSAGES_FAILED",
1291
1307
  domain: ErrorDomain.STORAGE,
1292
- category: ErrorCategory.THIRD_PARTY,
1293
- details: { threadId: thread.id }
1308
+ category: ErrorCategory.THIRD_PARTY
1294
1309
  },
1295
1310
  error
1296
1311
  );
1297
- this.logger?.trackException?.(mastraError);
1298
- this.logger?.error?.(mastraError.toString());
1299
- throw mastraError;
1300
1312
  }
1301
1313
  }
1302
- async updateThread({
1314
+ async updateMessages({
1315
+ messages
1316
+ }) {
1317
+ if (messages.length === 0) {
1318
+ return [];
1319
+ }
1320
+ const messageIds = messages.map((m) => m.id);
1321
+ const placeholders = messageIds.map(() => "?").join(",");
1322
+ const selectSql = `SELECT * FROM ${TABLE_MESSAGES} WHERE id IN (${placeholders})`;
1323
+ const existingResult = await this.client.execute({ sql: selectSql, args: messageIds });
1324
+ const existingMessages = existingResult.rows.map((row) => this.parseRow(row));
1325
+ if (existingMessages.length === 0) {
1326
+ return [];
1327
+ }
1328
+ const batchStatements = [];
1329
+ const threadIdsToUpdate = /* @__PURE__ */ new Set();
1330
+ const columnMapping = {
1331
+ threadId: "thread_id"
1332
+ };
1333
+ for (const existingMessage of existingMessages) {
1334
+ const updatePayload = messages.find((m) => m.id === existingMessage.id);
1335
+ if (!updatePayload) continue;
1336
+ const { id, ...fieldsToUpdate } = updatePayload;
1337
+ if (Object.keys(fieldsToUpdate).length === 0) continue;
1338
+ threadIdsToUpdate.add(existingMessage.threadId);
1339
+ if (updatePayload.threadId && updatePayload.threadId !== existingMessage.threadId) {
1340
+ threadIdsToUpdate.add(updatePayload.threadId);
1341
+ }
1342
+ const setClauses = [];
1343
+ const args = [];
1344
+ const updatableFields = { ...fieldsToUpdate };
1345
+ if (updatableFields.content) {
1346
+ const newContent = {
1347
+ ...existingMessage.content,
1348
+ ...updatableFields.content,
1349
+ // Deep merge metadata if it exists on both
1350
+ ...existingMessage.content?.metadata && updatableFields.content.metadata ? {
1351
+ metadata: {
1352
+ ...existingMessage.content.metadata,
1353
+ ...updatableFields.content.metadata
1354
+ }
1355
+ } : {}
1356
+ };
1357
+ setClauses.push(`${parseSqlIdentifier$1("content", "column name")} = ?`);
1358
+ args.push(JSON.stringify(newContent));
1359
+ delete updatableFields.content;
1360
+ }
1361
+ for (const key in updatableFields) {
1362
+ if (Object.prototype.hasOwnProperty.call(updatableFields, key)) {
1363
+ const dbKey = columnMapping[key] || key;
1364
+ setClauses.push(`${parseSqlIdentifier$1(dbKey, "column name")} = ?`);
1365
+ let value = updatableFields[key];
1366
+ if (typeof value === "object" && value !== null) {
1367
+ value = JSON.stringify(value);
1368
+ }
1369
+ args.push(value);
1370
+ }
1371
+ }
1372
+ if (setClauses.length === 0) continue;
1373
+ args.push(id);
1374
+ const sql = `UPDATE ${TABLE_MESSAGES} SET ${setClauses.join(", ")} WHERE id = ?`;
1375
+ batchStatements.push({ sql, args });
1376
+ }
1377
+ if (batchStatements.length === 0) {
1378
+ return existingMessages;
1379
+ }
1380
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1381
+ for (const threadId of threadIdsToUpdate) {
1382
+ if (threadId) {
1383
+ batchStatements.push({
1384
+ sql: `UPDATE ${TABLE_THREADS} SET updatedAt = ? WHERE id = ?`,
1385
+ args: [now, threadId]
1386
+ });
1387
+ }
1388
+ }
1389
+ await this.client.batch(batchStatements, "write");
1390
+ const updatedResult = await this.client.execute({ sql: selectSql, args: messageIds });
1391
+ return updatedResult.rows.map((row) => this.parseRow(row));
1392
+ }
1393
+ async deleteMessages(messageIds) {
1394
+ if (!messageIds || messageIds.length === 0) {
1395
+ return;
1396
+ }
1397
+ try {
1398
+ const BATCH_SIZE = 100;
1399
+ const threadIds = /* @__PURE__ */ new Set();
1400
+ const tx = await this.client.transaction("write");
1401
+ try {
1402
+ for (let i = 0; i < messageIds.length; i += BATCH_SIZE) {
1403
+ const batch = messageIds.slice(i, i + BATCH_SIZE);
1404
+ const placeholders = batch.map(() => "?").join(",");
1405
+ const result = await tx.execute({
1406
+ sql: `SELECT DISTINCT thread_id FROM "${TABLE_MESSAGES}" WHERE id IN (${placeholders})`,
1407
+ args: batch
1408
+ });
1409
+ result.rows?.forEach((row) => {
1410
+ if (row.thread_id) threadIds.add(row.thread_id);
1411
+ });
1412
+ await tx.execute({
1413
+ sql: `DELETE FROM "${TABLE_MESSAGES}" WHERE id IN (${placeholders})`,
1414
+ args: batch
1415
+ });
1416
+ }
1417
+ if (threadIds.size > 0) {
1418
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1419
+ for (const threadId of threadIds) {
1420
+ await tx.execute({
1421
+ sql: `UPDATE "${TABLE_THREADS}" SET "updatedAt" = ? WHERE id = ?`,
1422
+ args: [now, threadId]
1423
+ });
1424
+ }
1425
+ }
1426
+ await tx.commit();
1427
+ } catch (error) {
1428
+ await tx.rollback();
1429
+ throw error;
1430
+ }
1431
+ } catch (error) {
1432
+ throw new MastraError(
1433
+ {
1434
+ id: "LIBSQL_STORE_DELETE_MESSAGES_FAILED",
1435
+ domain: ErrorDomain.STORAGE,
1436
+ category: ErrorCategory.THIRD_PARTY,
1437
+ details: { messageIds: messageIds.join(", ") }
1438
+ },
1439
+ error
1440
+ );
1441
+ }
1442
+ }
1443
+ async getResourceById({ resourceId }) {
1444
+ const result = await this.operations.load({
1445
+ tableName: TABLE_RESOURCES,
1446
+ keys: { id: resourceId }
1447
+ });
1448
+ if (!result) {
1449
+ return null;
1450
+ }
1451
+ return {
1452
+ ...result,
1453
+ // Ensure workingMemory is always returned as a string, even if auto-parsed as JSON
1454
+ workingMemory: result.workingMemory && typeof result.workingMemory === "object" ? JSON.stringify(result.workingMemory) : result.workingMemory,
1455
+ metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata,
1456
+ createdAt: new Date(result.createdAt),
1457
+ updatedAt: new Date(result.updatedAt)
1458
+ };
1459
+ }
1460
+ async saveResource({ resource }) {
1461
+ await this.operations.insert({
1462
+ tableName: TABLE_RESOURCES,
1463
+ record: {
1464
+ ...resource,
1465
+ metadata: JSON.stringify(resource.metadata)
1466
+ }
1467
+ });
1468
+ return resource;
1469
+ }
1470
+ async updateResource({
1471
+ resourceId,
1472
+ workingMemory,
1473
+ metadata
1474
+ }) {
1475
+ const existingResource = await this.getResourceById({ resourceId });
1476
+ if (!existingResource) {
1477
+ const newResource = {
1478
+ id: resourceId,
1479
+ workingMemory,
1480
+ metadata: metadata || {},
1481
+ createdAt: /* @__PURE__ */ new Date(),
1482
+ updatedAt: /* @__PURE__ */ new Date()
1483
+ };
1484
+ return this.saveResource({ resource: newResource });
1485
+ }
1486
+ const updatedResource = {
1487
+ ...existingResource,
1488
+ workingMemory: workingMemory !== void 0 ? workingMemory : existingResource.workingMemory,
1489
+ metadata: {
1490
+ ...existingResource.metadata,
1491
+ ...metadata
1492
+ },
1493
+ updatedAt: /* @__PURE__ */ new Date()
1494
+ };
1495
+ const updates = [];
1496
+ const values = [];
1497
+ if (workingMemory !== void 0) {
1498
+ updates.push("workingMemory = ?");
1499
+ values.push(workingMemory);
1500
+ }
1501
+ if (metadata) {
1502
+ updates.push("metadata = ?");
1503
+ values.push(JSON.stringify(updatedResource.metadata));
1504
+ }
1505
+ updates.push("updatedAt = ?");
1506
+ values.push(updatedResource.updatedAt.toISOString());
1507
+ values.push(resourceId);
1508
+ await this.client.execute({
1509
+ sql: `UPDATE ${TABLE_RESOURCES} SET ${updates.join(", ")} WHERE id = ?`,
1510
+ args: values
1511
+ });
1512
+ return updatedResource;
1513
+ }
1514
+ async getThreadById({ threadId }) {
1515
+ try {
1516
+ const result = await this.operations.load({
1517
+ tableName: TABLE_THREADS,
1518
+ keys: { id: threadId }
1519
+ });
1520
+ if (!result) {
1521
+ return null;
1522
+ }
1523
+ return {
1524
+ ...result,
1525
+ metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata,
1526
+ createdAt: new Date(result.createdAt),
1527
+ updatedAt: new Date(result.updatedAt)
1528
+ };
1529
+ } catch (error) {
1530
+ throw new MastraError(
1531
+ {
1532
+ id: "LIBSQL_STORE_GET_THREAD_BY_ID_FAILED",
1533
+ domain: ErrorDomain.STORAGE,
1534
+ category: ErrorCategory.THIRD_PARTY,
1535
+ details: { threadId }
1536
+ },
1537
+ error
1538
+ );
1539
+ }
1540
+ }
1541
+ /**
1542
+ * @deprecated use getThreadsByResourceIdPaginated instead for paginated results.
1543
+ */
1544
+ async getThreadsByResourceId(args) {
1545
+ const resourceId = args.resourceId;
1546
+ const orderBy = this.castThreadOrderBy(args.orderBy);
1547
+ const sortDirection = this.castThreadSortDirection(args.sortDirection);
1548
+ try {
1549
+ const baseQuery = `FROM ${TABLE_THREADS} WHERE resourceId = ?`;
1550
+ const queryParams = [resourceId];
1551
+ const mapRowToStorageThreadType = (row) => ({
1552
+ id: row.id,
1553
+ resourceId: row.resourceId,
1554
+ title: row.title,
1555
+ createdAt: new Date(row.createdAt),
1556
+ // Convert string to Date
1557
+ updatedAt: new Date(row.updatedAt),
1558
+ // Convert string to Date
1559
+ metadata: typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata
1560
+ });
1561
+ const result = await this.client.execute({
1562
+ sql: `SELECT * ${baseQuery} ORDER BY ${orderBy} ${sortDirection}`,
1563
+ args: queryParams
1564
+ });
1565
+ if (!result.rows) {
1566
+ return [];
1567
+ }
1568
+ return result.rows.map(mapRowToStorageThreadType);
1569
+ } catch (error) {
1570
+ const mastraError = new MastraError(
1571
+ {
1572
+ id: "LIBSQL_STORE_GET_THREADS_BY_RESOURCE_ID_FAILED",
1573
+ domain: ErrorDomain.STORAGE,
1574
+ category: ErrorCategory.THIRD_PARTY,
1575
+ details: { resourceId }
1576
+ },
1577
+ error
1578
+ );
1579
+ this.logger?.trackException?.(mastraError);
1580
+ this.logger?.error?.(mastraError.toString());
1581
+ return [];
1582
+ }
1583
+ }
1584
+ async getThreadsByResourceIdPaginated(args) {
1585
+ const { resourceId, page = 0, perPage = 100 } = args;
1586
+ const orderBy = this.castThreadOrderBy(args.orderBy);
1587
+ const sortDirection = this.castThreadSortDirection(args.sortDirection);
1588
+ try {
1589
+ const baseQuery = `FROM ${TABLE_THREADS} WHERE resourceId = ?`;
1590
+ const queryParams = [resourceId];
1591
+ const mapRowToStorageThreadType = (row) => ({
1592
+ id: row.id,
1593
+ resourceId: row.resourceId,
1594
+ title: row.title,
1595
+ createdAt: new Date(row.createdAt),
1596
+ // Convert string to Date
1597
+ updatedAt: new Date(row.updatedAt),
1598
+ // Convert string to Date
1599
+ metadata: typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata
1600
+ });
1601
+ const currentOffset = page * perPage;
1602
+ const countResult = await this.client.execute({
1603
+ sql: `SELECT COUNT(*) as count ${baseQuery}`,
1604
+ args: queryParams
1605
+ });
1606
+ const total = Number(countResult.rows?.[0]?.count ?? 0);
1607
+ if (total === 0) {
1608
+ return {
1609
+ threads: [],
1610
+ total: 0,
1611
+ page,
1612
+ perPage,
1613
+ hasMore: false
1614
+ };
1615
+ }
1616
+ const dataResult = await this.client.execute({
1617
+ sql: `SELECT * ${baseQuery} ORDER BY ${orderBy} ${sortDirection} LIMIT ? OFFSET ?`,
1618
+ args: [...queryParams, perPage, currentOffset]
1619
+ });
1620
+ const threads = (dataResult.rows || []).map(mapRowToStorageThreadType);
1621
+ return {
1622
+ threads,
1623
+ total,
1624
+ page,
1625
+ perPage,
1626
+ hasMore: currentOffset + threads.length < total
1627
+ };
1628
+ } catch (error) {
1629
+ const mastraError = new MastraError(
1630
+ {
1631
+ id: "LIBSQL_STORE_GET_THREADS_BY_RESOURCE_ID_FAILED",
1632
+ domain: ErrorDomain.STORAGE,
1633
+ category: ErrorCategory.THIRD_PARTY,
1634
+ details: { resourceId }
1635
+ },
1636
+ error
1637
+ );
1638
+ this.logger?.trackException?.(mastraError);
1639
+ this.logger?.error?.(mastraError.toString());
1640
+ return { threads: [], total: 0, page, perPage, hasMore: false };
1641
+ }
1642
+ }
1643
+ async saveThread({ thread }) {
1644
+ try {
1645
+ await this.operations.insert({
1646
+ tableName: TABLE_THREADS,
1647
+ record: {
1648
+ ...thread,
1649
+ metadata: JSON.stringify(thread.metadata)
1650
+ }
1651
+ });
1652
+ return thread;
1653
+ } catch (error) {
1654
+ const mastraError = new MastraError(
1655
+ {
1656
+ id: "LIBSQL_STORE_SAVE_THREAD_FAILED",
1657
+ domain: ErrorDomain.STORAGE,
1658
+ category: ErrorCategory.THIRD_PARTY,
1659
+ details: { threadId: thread.id }
1660
+ },
1661
+ error
1662
+ );
1663
+ this.logger?.trackException?.(mastraError);
1664
+ this.logger?.error?.(mastraError.toString());
1665
+ throw mastraError;
1666
+ }
1667
+ }
1668
+ async updateThread({
1303
1669
  id,
1304
1670
  title,
1305
1671
  metadata
@@ -1357,268 +1723,329 @@ var LibSQLStore = class extends MastraStorage {
1357
1723
  } catch (error) {
1358
1724
  throw new MastraError(
1359
1725
  {
1360
- id: "LIBSQL_STORE_DELETE_THREAD_FAILED",
1726
+ id: "LIBSQL_STORE_DELETE_THREAD_FAILED",
1727
+ domain: ErrorDomain.STORAGE,
1728
+ category: ErrorCategory.THIRD_PARTY,
1729
+ details: { threadId }
1730
+ },
1731
+ error
1732
+ );
1733
+ }
1734
+ }
1735
+ };
1736
+ function createExecuteWriteOperationWithRetry({
1737
+ logger,
1738
+ maxRetries,
1739
+ initialBackoffMs
1740
+ }) {
1741
+ return async function executeWriteOperationWithRetry(operationFn, operationDescription) {
1742
+ let retries = 0;
1743
+ while (true) {
1744
+ try {
1745
+ return await operationFn();
1746
+ } catch (error) {
1747
+ if (error.message && (error.message.includes("SQLITE_BUSY") || error.message.includes("database is locked")) && retries < maxRetries) {
1748
+ retries++;
1749
+ const backoffTime = initialBackoffMs * Math.pow(2, retries - 1);
1750
+ logger.warn(
1751
+ `LibSQLStore: Encountered SQLITE_BUSY during ${operationDescription}. Retrying (${retries}/${maxRetries}) in ${backoffTime}ms...`
1752
+ );
1753
+ await new Promise((resolve) => setTimeout(resolve, backoffTime));
1754
+ } else {
1755
+ logger.error(`LibSQLStore: Error during ${operationDescription} after ${retries} retries: ${error}`);
1756
+ throw error;
1757
+ }
1758
+ }
1759
+ }
1760
+ };
1761
+ }
1762
+ function prepareStatement({ tableName, record }) {
1763
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
1764
+ const columns = Object.keys(record).map((col) => parseSqlIdentifier(col, "column name"));
1765
+ const values = Object.values(record).map((v) => {
1766
+ if (typeof v === `undefined`) {
1767
+ return null;
1768
+ }
1769
+ if (v instanceof Date) {
1770
+ return v.toISOString();
1771
+ }
1772
+ return typeof v === "object" ? JSON.stringify(v) : v;
1773
+ });
1774
+ const placeholders = values.map(() => "?").join(", ");
1775
+ return {
1776
+ sql: `INSERT OR REPLACE INTO ${parsedTableName} (${columns.join(", ")}) VALUES (${placeholders})`,
1777
+ args: values
1778
+ };
1779
+ }
1780
+
1781
+ // src/storage/domains/operations/index.ts
1782
+ var StoreOperationsLibSQL = class extends StoreOperations {
1783
+ client;
1784
+ /**
1785
+ * Maximum number of retries for write operations if an SQLITE_BUSY error occurs.
1786
+ * @default 5
1787
+ */
1788
+ maxRetries;
1789
+ /**
1790
+ * Initial backoff time in milliseconds for retrying write operations on SQLITE_BUSY.
1791
+ * The backoff time will double with each retry (exponential backoff).
1792
+ * @default 100
1793
+ */
1794
+ initialBackoffMs;
1795
+ constructor({
1796
+ client,
1797
+ maxRetries,
1798
+ initialBackoffMs
1799
+ }) {
1800
+ super();
1801
+ this.client = client;
1802
+ this.maxRetries = maxRetries ?? 5;
1803
+ this.initialBackoffMs = initialBackoffMs ?? 100;
1804
+ }
1805
+ async hasColumn(table, column) {
1806
+ const result = await this.client.execute({
1807
+ sql: `PRAGMA table_info(${table})`
1808
+ });
1809
+ return (await result.rows)?.some((row) => row.name === column);
1810
+ }
1811
+ getCreateTableSQL(tableName, schema) {
1812
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
1813
+ const columns = Object.entries(schema).map(([name, col]) => {
1814
+ const parsedColumnName = parseSqlIdentifier(name, "column name");
1815
+ let type = col.type.toUpperCase();
1816
+ if (type === "TEXT") type = "TEXT";
1817
+ if (type === "TIMESTAMP") type = "TEXT";
1818
+ const nullable = col.nullable ? "" : "NOT NULL";
1819
+ const primaryKey = col.primaryKey ? "PRIMARY KEY" : "";
1820
+ return `${parsedColumnName} ${type} ${nullable} ${primaryKey}`.trim();
1821
+ });
1822
+ if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
1823
+ const stmnt = `CREATE TABLE IF NOT EXISTS ${parsedTableName} (
1824
+ ${columns.join(",\n")},
1825
+ PRIMARY KEY (workflow_name, run_id)
1826
+ )`;
1827
+ return stmnt;
1828
+ }
1829
+ return `CREATE TABLE IF NOT EXISTS ${parsedTableName} (${columns.join(", ")})`;
1830
+ }
1831
+ async createTable({
1832
+ tableName,
1833
+ schema
1834
+ }) {
1835
+ try {
1836
+ this.logger.debug(`Creating database table`, { tableName, operation: "schema init" });
1837
+ const sql = this.getCreateTableSQL(tableName, schema);
1838
+ await this.client.execute(sql);
1839
+ } catch (error) {
1840
+ throw new MastraError(
1841
+ {
1842
+ id: "LIBSQL_STORE_CREATE_TABLE_FAILED",
1843
+ domain: ErrorDomain.STORAGE,
1844
+ category: ErrorCategory.THIRD_PARTY,
1845
+ details: {
1846
+ tableName
1847
+ }
1848
+ },
1849
+ error
1850
+ );
1851
+ }
1852
+ }
1853
+ getSqlType(type) {
1854
+ switch (type) {
1855
+ case "bigint":
1856
+ return "INTEGER";
1857
+ // SQLite uses INTEGER for all integer sizes
1858
+ case "jsonb":
1859
+ return "TEXT";
1860
+ // Store JSON as TEXT in SQLite
1861
+ default:
1862
+ return super.getSqlType(type);
1863
+ }
1864
+ }
1865
+ async doInsert({
1866
+ tableName,
1867
+ record
1868
+ }) {
1869
+ await this.client.execute(
1870
+ prepareStatement({
1871
+ tableName,
1872
+ record
1873
+ })
1874
+ );
1875
+ }
1876
+ insert(args) {
1877
+ const executeWriteOperationWithRetry = createExecuteWriteOperationWithRetry({
1878
+ logger: this.logger,
1879
+ maxRetries: this.maxRetries,
1880
+ initialBackoffMs: this.initialBackoffMs
1881
+ });
1882
+ return executeWriteOperationWithRetry(() => this.doInsert(args), `insert into table ${args.tableName}`);
1883
+ }
1884
+ async load({ tableName, keys }) {
1885
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
1886
+ const parsedKeys = Object.keys(keys).map((key) => parseSqlIdentifier(key, "column name"));
1887
+ const conditions = parsedKeys.map((key) => `${key} = ?`).join(" AND ");
1888
+ const values = Object.values(keys);
1889
+ const result = await this.client.execute({
1890
+ sql: `SELECT * FROM ${parsedTableName} WHERE ${conditions} ORDER BY createdAt DESC LIMIT 1`,
1891
+ args: values
1892
+ });
1893
+ if (!result.rows || result.rows.length === 0) {
1894
+ return null;
1895
+ }
1896
+ const row = result.rows[0];
1897
+ const parsed = Object.fromEntries(
1898
+ Object.entries(row || {}).map(([k, v]) => {
1899
+ try {
1900
+ return [k, typeof v === "string" ? v.startsWith("{") || v.startsWith("[") ? JSON.parse(v) : v : v];
1901
+ } catch {
1902
+ return [k, v];
1903
+ }
1904
+ })
1905
+ );
1906
+ return parsed;
1907
+ }
1908
+ async doBatchInsert({
1909
+ tableName,
1910
+ records
1911
+ }) {
1912
+ if (records.length === 0) return;
1913
+ const batchStatements = records.map((r) => prepareStatement({ tableName, record: r }));
1914
+ await this.client.batch(batchStatements, "write");
1915
+ }
1916
+ batchInsert(args) {
1917
+ const executeWriteOperationWithRetry = createExecuteWriteOperationWithRetry({
1918
+ logger: this.logger,
1919
+ maxRetries: this.maxRetries,
1920
+ initialBackoffMs: this.initialBackoffMs
1921
+ });
1922
+ return executeWriteOperationWithRetry(
1923
+ () => this.doBatchInsert(args),
1924
+ `batch insert into table ${args.tableName}`
1925
+ ).catch((error) => {
1926
+ throw new MastraError(
1927
+ {
1928
+ id: "LIBSQL_STORE_BATCH_INSERT_FAILED",
1361
1929
  domain: ErrorDomain.STORAGE,
1362
1930
  category: ErrorCategory.THIRD_PARTY,
1363
- details: { threadId }
1931
+ details: {
1932
+ tableName: args.tableName
1933
+ }
1364
1934
  },
1365
1935
  error
1366
1936
  );
1367
- }
1368
- }
1369
- parseRow(row) {
1370
- let content = row.content;
1371
- try {
1372
- content = JSON.parse(row.content);
1373
- } catch {
1374
- }
1375
- const result = {
1376
- id: row.id,
1377
- content,
1378
- role: row.role,
1379
- createdAt: new Date(row.createdAt),
1380
- threadId: row.thread_id,
1381
- resourceId: row.resourceId
1382
- };
1383
- if (row.type && row.type !== `v2`) result.type = row.type;
1384
- return result;
1385
- }
1386
- async _getIncludedMessages({
1387
- threadId,
1388
- selectBy
1389
- }) {
1390
- const include = selectBy?.include;
1391
- if (!include) return null;
1392
- const unionQueries = [];
1393
- const params = [];
1394
- for (const inc of include) {
1395
- const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
1396
- const searchId = inc.threadId || threadId;
1397
- unionQueries.push(
1398
- `
1399
- SELECT * FROM (
1400
- WITH numbered_messages AS (
1401
- SELECT
1402
- id, content, role, type, "createdAt", thread_id, "resourceId",
1403
- ROW_NUMBER() OVER (ORDER BY "createdAt" ASC) as row_num
1404
- FROM "${TABLE_MESSAGES}"
1405
- WHERE thread_id = ?
1406
- ),
1407
- target_positions AS (
1408
- SELECT row_num as target_pos
1409
- FROM numbered_messages
1410
- WHERE id = ?
1411
- )
1412
- SELECT DISTINCT m.*
1413
- FROM numbered_messages m
1414
- CROSS JOIN target_positions t
1415
- WHERE m.row_num BETWEEN (t.target_pos - ?) AND (t.target_pos + ?)
1416
- )
1417
- `
1418
- // Keep ASC for final sorting after fetching context
1419
- );
1420
- params.push(searchId, id, withPreviousMessages, withNextMessages);
1421
- }
1422
- const finalQuery = unionQueries.join(" UNION ALL ") + ' ORDER BY "createdAt" ASC';
1423
- const includedResult = await this.client.execute({ sql: finalQuery, args: params });
1424
- const includedRows = includedResult.rows?.map((row) => this.parseRow(row));
1425
- const seen = /* @__PURE__ */ new Set();
1426
- const dedupedRows = includedRows.filter((row) => {
1427
- if (seen.has(row.id)) return false;
1428
- seen.add(row.id);
1429
- return true;
1430
1937
  });
1431
- return dedupedRows;
1432
1938
  }
1433
- async getMessages({
1434
- threadId,
1435
- selectBy,
1436
- format
1939
+ /**
1940
+ * Alters table schema to add columns if they don't exist
1941
+ * @param tableName Name of the table
1942
+ * @param schema Schema of the table
1943
+ * @param ifNotExists Array of column names to add if they don't exist
1944
+ */
1945
+ async alterTable({
1946
+ tableName,
1947
+ schema,
1948
+ ifNotExists
1437
1949
  }) {
1950
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
1438
1951
  try {
1439
- const messages = [];
1440
- const limit = this.resolveMessageLimit({ last: selectBy?.last, defaultLimit: 40 });
1441
- if (selectBy?.include?.length) {
1442
- const includeMessages = await this._getIncludedMessages({ threadId, selectBy });
1443
- if (includeMessages) {
1444
- messages.push(...includeMessages);
1952
+ const pragmaQuery = `PRAGMA table_info(${parsedTableName})`;
1953
+ const result = await this.client.execute(pragmaQuery);
1954
+ const existingColumnNames = new Set(result.rows.map((row) => row.name.toLowerCase()));
1955
+ for (const columnName of ifNotExists) {
1956
+ if (!existingColumnNames.has(columnName.toLowerCase()) && schema[columnName]) {
1957
+ const columnDef = schema[columnName];
1958
+ const sqlType = this.getSqlType(columnDef.type);
1959
+ const nullable = columnDef.nullable === false ? "NOT NULL" : "";
1960
+ const defaultValue = columnDef.nullable === false ? this.getDefaultValue(columnDef.type) : "";
1961
+ const alterSql = `ALTER TABLE ${parsedTableName} ADD COLUMN "${columnName}" ${sqlType} ${nullable} ${defaultValue}`.trim();
1962
+ await this.client.execute(alterSql);
1963
+ this.logger?.debug?.(`Added column ${columnName} to table ${parsedTableName}`);
1445
1964
  }
1446
1965
  }
1447
- const excludeIds = messages.map((m) => m.id);
1448
- const remainingSql = `
1449
- SELECT
1450
- id,
1451
- content,
1452
- role,
1453
- type,
1454
- "createdAt",
1455
- thread_id,
1456
- "resourceId"
1457
- FROM "${TABLE_MESSAGES}"
1458
- WHERE thread_id = ?
1459
- ${excludeIds.length ? `AND id NOT IN (${excludeIds.map(() => "?").join(", ")})` : ""}
1460
- ORDER BY "createdAt" DESC
1461
- LIMIT ?
1462
- `;
1463
- const remainingArgs = [threadId, ...excludeIds.length ? excludeIds : [], limit];
1464
- const remainingResult = await this.client.execute({ sql: remainingSql, args: remainingArgs });
1465
- if (remainingResult.rows) {
1466
- messages.push(...remainingResult.rows.map((row) => this.parseRow(row)));
1467
- }
1468
- messages.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
1469
- const list = new MessageList().add(messages, "memory");
1470
- if (format === `v2`) return list.get.all.v2();
1471
- return list.get.all.v1();
1472
1966
  } catch (error) {
1473
1967
  throw new MastraError(
1474
1968
  {
1475
- id: "LIBSQL_STORE_GET_MESSAGES_FAILED",
1969
+ id: "LIBSQL_STORE_ALTER_TABLE_FAILED",
1476
1970
  domain: ErrorDomain.STORAGE,
1477
1971
  category: ErrorCategory.THIRD_PARTY,
1478
- details: { threadId }
1972
+ details: {
1973
+ tableName
1974
+ }
1479
1975
  },
1480
1976
  error
1481
1977
  );
1482
1978
  }
1483
1979
  }
1484
- async getMessagesPaginated(args) {
1485
- const { threadId, format, selectBy } = args;
1486
- const { page = 0, perPage: perPageInput, dateRange } = selectBy?.pagination || {};
1487
- const perPage = perPageInput !== void 0 ? perPageInput : this.resolveMessageLimit({ last: selectBy?.last, defaultLimit: 40 });
1488
- const fromDate = dateRange?.start;
1489
- const toDate = dateRange?.end;
1490
- const messages = [];
1491
- if (selectBy?.include?.length) {
1492
- try {
1493
- const includeMessages = await this._getIncludedMessages({ threadId, selectBy });
1494
- if (includeMessages) {
1495
- messages.push(...includeMessages);
1496
- }
1497
- } catch (error) {
1498
- throw new MastraError(
1499
- {
1500
- id: "LIBSQL_STORE_GET_MESSAGES_PAGINATED_GET_INCLUDE_MESSAGES_FAILED",
1501
- domain: ErrorDomain.STORAGE,
1502
- category: ErrorCategory.THIRD_PARTY,
1503
- details: { threadId }
1504
- },
1505
- error
1506
- );
1507
- }
1508
- }
1980
+ async clearTable({ tableName }) {
1981
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
1509
1982
  try {
1510
- const currentOffset = page * perPage;
1511
- const conditions = [`thread_id = ?`];
1512
- const queryParams = [threadId];
1513
- if (fromDate) {
1514
- conditions.push(`"createdAt" >= ?`);
1515
- queryParams.push(fromDate.toISOString());
1516
- }
1517
- if (toDate) {
1518
- conditions.push(`"createdAt" <= ?`);
1519
- queryParams.push(toDate.toISOString());
1520
- }
1521
- const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1522
- const countResult = await this.client.execute({
1523
- sql: `SELECT COUNT(*) as count FROM ${TABLE_MESSAGES} ${whereClause}`,
1524
- args: queryParams
1525
- });
1526
- const total = Number(countResult.rows?.[0]?.count ?? 0);
1527
- if (total === 0 && messages.length === 0) {
1528
- return {
1529
- messages: [],
1530
- total: 0,
1531
- page,
1532
- perPage,
1533
- hasMore: false
1534
- };
1535
- }
1536
- const excludeIds = messages.map((m) => m.id);
1537
- const excludeIdsParam = excludeIds.map((_, idx) => `$${idx + queryParams.length + 1}`).join(", ");
1538
- const dataResult = await this.client.execute({
1539
- sql: `SELECT id, content, role, type, "createdAt", "resourceId", "thread_id" FROM ${TABLE_MESSAGES} ${whereClause} ${excludeIds.length ? `AND id NOT IN (${excludeIdsParam})` : ""} ORDER BY "createdAt" DESC LIMIT ? OFFSET ?`,
1540
- args: [...queryParams, ...excludeIds, perPage, currentOffset]
1541
- });
1542
- messages.push(...(dataResult.rows || []).map((row) => this.parseRow(row)));
1543
- const messagesToReturn = format === "v1" ? new MessageList().add(messages, "memory").get.all.v1() : new MessageList().add(messages, "memory").get.all.v2();
1544
- return {
1545
- messages: messagesToReturn,
1546
- total,
1547
- page,
1548
- perPage,
1549
- hasMore: currentOffset + messages.length < total
1550
- };
1551
- } catch (error) {
1983
+ await this.client.execute(`DELETE FROM ${parsedTableName}`);
1984
+ } catch (e) {
1552
1985
  const mastraError = new MastraError(
1553
1986
  {
1554
- id: "LIBSQL_STORE_GET_MESSAGES_PAGINATED_FAILED",
1987
+ id: "LIBSQL_STORE_CLEAR_TABLE_FAILED",
1555
1988
  domain: ErrorDomain.STORAGE,
1556
1989
  category: ErrorCategory.THIRD_PARTY,
1557
- details: { threadId }
1990
+ details: {
1991
+ tableName
1992
+ }
1558
1993
  },
1559
- error
1994
+ e
1560
1995
  );
1561
1996
  this.logger?.trackException?.(mastraError);
1562
1997
  this.logger?.error?.(mastraError.toString());
1563
- return { messages: [], total: 0, page, perPage, hasMore: false };
1564
1998
  }
1565
1999
  }
1566
- async saveMessages({
1567
- messages,
1568
- format
1569
- }) {
1570
- if (messages.length === 0) return messages;
2000
+ async dropTable({ tableName }) {
2001
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
1571
2002
  try {
1572
- const threadId = messages[0]?.threadId;
1573
- if (!threadId) {
1574
- throw new Error("Thread ID is required");
1575
- }
1576
- const batchStatements = messages.map((message) => {
1577
- const time = message.createdAt || /* @__PURE__ */ new Date();
1578
- if (!message.threadId) {
1579
- throw new Error(
1580
- `Expected to find a threadId for message, but couldn't find one. An unexpected error has occurred.`
1581
- );
1582
- }
1583
- if (!message.resourceId) {
1584
- throw new Error(
1585
- `Expected to find a resourceId for message, but couldn't find one. An unexpected error has occurred.`
1586
- );
1587
- }
1588
- return {
1589
- sql: `INSERT INTO ${TABLE_MESSAGES} (id, thread_id, content, role, type, createdAt, resourceId)
1590
- VALUES (?, ?, ?, ?, ?, ?, ?)
1591
- ON CONFLICT(id) DO UPDATE SET
1592
- thread_id=excluded.thread_id,
1593
- content=excluded.content,
1594
- role=excluded.role,
1595
- type=excluded.type,
1596
- resourceId=excluded.resourceId
1597
- `,
1598
- args: [
1599
- message.id,
1600
- message.threadId,
1601
- typeof message.content === "object" ? JSON.stringify(message.content) : message.content,
1602
- message.role,
1603
- message.type || "v2",
1604
- time instanceof Date ? time.toISOString() : time,
1605
- message.resourceId
1606
- ]
1607
- };
1608
- });
1609
- const now = (/* @__PURE__ */ new Date()).toISOString();
1610
- batchStatements.push({
1611
- sql: `UPDATE ${TABLE_THREADS} SET updatedAt = ? WHERE id = ?`,
1612
- args: [now, threadId]
2003
+ await this.client.execute(`DROP TABLE IF EXISTS ${parsedTableName}`);
2004
+ } catch (e) {
2005
+ throw new MastraError(
2006
+ {
2007
+ id: "LIBSQL_STORE_DROP_TABLE_FAILED",
2008
+ domain: ErrorDomain.STORAGE,
2009
+ category: ErrorCategory.THIRD_PARTY,
2010
+ details: {
2011
+ tableName
2012
+ }
2013
+ },
2014
+ e
2015
+ );
2016
+ }
2017
+ }
2018
+ };
2019
+ var ScoresLibSQL = class extends ScoresStorage {
2020
+ operations;
2021
+ client;
2022
+ constructor({ client, operations }) {
2023
+ super();
2024
+ this.operations = operations;
2025
+ this.client = client;
2026
+ }
2027
+ async getScoresByRunId({
2028
+ runId,
2029
+ pagination
2030
+ }) {
2031
+ try {
2032
+ const result = await this.client.execute({
2033
+ sql: `SELECT * FROM ${TABLE_SCORERS} WHERE runId = ? ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
2034
+ args: [runId, pagination.perPage + 1, pagination.page * pagination.perPage]
1613
2035
  });
1614
- await this.client.batch(batchStatements, "write");
1615
- const list = new MessageList().add(messages, "memory");
1616
- if (format === `v2`) return list.get.all.v2();
1617
- return list.get.all.v1();
2036
+ return {
2037
+ scores: result.rows?.slice(0, pagination.perPage).map((row) => this.transformScoreRow(row)) ?? [],
2038
+ pagination: {
2039
+ total: result.rows?.length ?? 0,
2040
+ page: pagination.page,
2041
+ perPage: pagination.perPage,
2042
+ hasMore: result.rows?.length > pagination.perPage
2043
+ }
2044
+ };
1618
2045
  } catch (error) {
1619
2046
  throw new MastraError(
1620
2047
  {
1621
- id: "LIBSQL_STORE_SAVE_MESSAGES_FAILED",
2048
+ id: "LIBSQL_STORE_GET_SCORES_BY_RUN_ID_FAILED",
1622
2049
  domain: ErrorDomain.STORAGE,
1623
2050
  category: ErrorCategory.THIRD_PARTY
1624
2051
  },
@@ -1626,185 +2053,151 @@ var LibSQLStore = class extends MastraStorage {
1626
2053
  );
1627
2054
  }
1628
2055
  }
1629
- async updateMessages({
1630
- messages
2056
+ async getScoresByScorerId({
2057
+ scorerId,
2058
+ entityId,
2059
+ entityType,
2060
+ source,
2061
+ pagination
1631
2062
  }) {
1632
- if (messages.length === 0) {
1633
- return [];
1634
- }
1635
- const messageIds = messages.map((m) => m.id);
1636
- const placeholders = messageIds.map(() => "?").join(",");
1637
- const selectSql = `SELECT * FROM ${TABLE_MESSAGES} WHERE id IN (${placeholders})`;
1638
- const existingResult = await this.client.execute({ sql: selectSql, args: messageIds });
1639
- const existingMessages = existingResult.rows.map((row) => this.parseRow(row));
1640
- if (existingMessages.length === 0) {
1641
- return [];
1642
- }
1643
- const batchStatements = [];
1644
- const threadIdsToUpdate = /* @__PURE__ */ new Set();
1645
- const columnMapping = {
1646
- threadId: "thread_id"
1647
- };
1648
- for (const existingMessage of existingMessages) {
1649
- const updatePayload = messages.find((m) => m.id === existingMessage.id);
1650
- if (!updatePayload) continue;
1651
- const { id, ...fieldsToUpdate } = updatePayload;
1652
- if (Object.keys(fieldsToUpdate).length === 0) continue;
1653
- threadIdsToUpdate.add(existingMessage.threadId);
1654
- if (updatePayload.threadId && updatePayload.threadId !== existingMessage.threadId) {
1655
- threadIdsToUpdate.add(updatePayload.threadId);
2063
+ try {
2064
+ const conditions = [];
2065
+ const queryParams = [];
2066
+ if (scorerId) {
2067
+ conditions.push(`scorerId = ?`);
2068
+ queryParams.push(scorerId);
1656
2069
  }
1657
- const setClauses = [];
1658
- const args = [];
1659
- const updatableFields = { ...fieldsToUpdate };
1660
- if (updatableFields.content) {
1661
- const newContent = {
1662
- ...existingMessage.content,
1663
- ...updatableFields.content,
1664
- // Deep merge metadata if it exists on both
1665
- ...existingMessage.content?.metadata && updatableFields.content.metadata ? {
1666
- metadata: {
1667
- ...existingMessage.content.metadata,
1668
- ...updatableFields.content.metadata
1669
- }
1670
- } : {}
1671
- };
1672
- setClauses.push(`${parseSqlIdentifier("content", "column name")} = ?`);
1673
- args.push(JSON.stringify(newContent));
1674
- delete updatableFields.content;
2070
+ if (entityId) {
2071
+ conditions.push(`entityId = ?`);
2072
+ queryParams.push(entityId);
1675
2073
  }
1676
- for (const key in updatableFields) {
1677
- if (Object.prototype.hasOwnProperty.call(updatableFields, key)) {
1678
- const dbKey = columnMapping[key] || key;
1679
- setClauses.push(`${parseSqlIdentifier(dbKey, "column name")} = ?`);
1680
- let value = updatableFields[key];
1681
- if (typeof value === "object" && value !== null) {
1682
- value = JSON.stringify(value);
1683
- }
1684
- args.push(value);
1685
- }
2074
+ if (entityType) {
2075
+ conditions.push(`entityType = ?`);
2076
+ queryParams.push(entityType);
1686
2077
  }
1687
- if (setClauses.length === 0) continue;
1688
- args.push(id);
1689
- const sql = `UPDATE ${TABLE_MESSAGES} SET ${setClauses.join(", ")} WHERE id = ?`;
1690
- batchStatements.push({ sql, args });
1691
- }
1692
- if (batchStatements.length === 0) {
1693
- return existingMessages;
1694
- }
1695
- const now = (/* @__PURE__ */ new Date()).toISOString();
1696
- for (const threadId of threadIdsToUpdate) {
1697
- if (threadId) {
1698
- batchStatements.push({
1699
- sql: `UPDATE ${TABLE_THREADS} SET updatedAt = ? WHERE id = ?`,
1700
- args: [now, threadId]
1701
- });
2078
+ if (source) {
2079
+ conditions.push(`source = ?`);
2080
+ queryParams.push(source);
1702
2081
  }
2082
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2083
+ const result = await this.client.execute({
2084
+ sql: `SELECT * FROM ${TABLE_SCORERS} ${whereClause} ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
2085
+ args: [...queryParams, pagination.perPage + 1, pagination.page * pagination.perPage]
2086
+ });
2087
+ return {
2088
+ scores: result.rows?.slice(0, pagination.perPage).map((row) => this.transformScoreRow(row)) ?? [],
2089
+ pagination: {
2090
+ total: result.rows?.length ?? 0,
2091
+ page: pagination.page,
2092
+ perPage: pagination.perPage,
2093
+ hasMore: result.rows?.length > pagination.perPage
2094
+ }
2095
+ };
2096
+ } catch (error) {
2097
+ throw new MastraError(
2098
+ {
2099
+ id: "LIBSQL_STORE_GET_SCORES_BY_SCORER_ID_FAILED",
2100
+ domain: ErrorDomain.STORAGE,
2101
+ category: ErrorCategory.THIRD_PARTY
2102
+ },
2103
+ error
2104
+ );
1703
2105
  }
1704
- await this.client.batch(batchStatements, "write");
1705
- const updatedResult = await this.client.execute({ sql: selectSql, args: messageIds });
1706
- return updatedResult.rows.map((row) => this.parseRow(row));
1707
2106
  }
1708
- transformEvalRow(row) {
1709
- const resultValue = JSON.parse(row.result);
1710
- const testInfoValue = row.test_info ? JSON.parse(row.test_info) : void 0;
1711
- if (!resultValue || typeof resultValue !== "object" || !("score" in resultValue)) {
1712
- throw new Error(`Invalid MetricResult format: ${JSON.stringify(resultValue)}`);
1713
- }
2107
+ transformScoreRow(row) {
2108
+ const scorerValue = safelyParseJSON(row.scorer);
2109
+ const inputValue = safelyParseJSON(row.input ?? "{}");
2110
+ const outputValue = safelyParseJSON(row.output ?? "{}");
2111
+ const additionalLLMContextValue = row.additionalLLMContext ? safelyParseJSON(row.additionalLLMContext) : null;
2112
+ const runtimeContextValue = row.runtimeContext ? safelyParseJSON(row.runtimeContext) : null;
2113
+ const metadataValue = row.metadata ? safelyParseJSON(row.metadata) : null;
2114
+ const entityValue = row.entity ? safelyParseJSON(row.entity) : null;
2115
+ const preprocessStepResultValue = row.preprocessStepResult ? safelyParseJSON(row.preprocessStepResult) : null;
2116
+ const analyzeStepResultValue = row.analyzeStepResult ? safelyParseJSON(row.analyzeStepResult) : null;
1714
2117
  return {
1715
- input: row.input,
1716
- output: row.output,
1717
- result: resultValue,
1718
- agentName: row.agent_name,
1719
- metricName: row.metric_name,
1720
- instructions: row.instructions,
1721
- testInfo: testInfoValue,
1722
- globalRunId: row.global_run_id,
1723
- runId: row.run_id,
1724
- createdAt: row.created_at
2118
+ id: row.id,
2119
+ traceId: row.traceId,
2120
+ runId: row.runId,
2121
+ scorer: scorerValue,
2122
+ score: row.score,
2123
+ reason: row.reason,
2124
+ preprocessStepResult: preprocessStepResultValue,
2125
+ analyzeStepResult: analyzeStepResultValue,
2126
+ analyzePrompt: row.analyzePrompt,
2127
+ preprocessPrompt: row.preprocessPrompt,
2128
+ generateScorePrompt: row.generateScorePrompt,
2129
+ generateReasonPrompt: row.generateReasonPrompt,
2130
+ metadata: metadataValue,
2131
+ input: inputValue,
2132
+ output: outputValue,
2133
+ additionalContext: additionalLLMContextValue,
2134
+ runtimeContext: runtimeContextValue,
2135
+ entityType: row.entityType,
2136
+ entity: entityValue,
2137
+ entityId: row.entityId,
2138
+ scorerId: row.scorerId,
2139
+ source: row.source,
2140
+ resourceId: row.resourceId,
2141
+ threadId: row.threadId,
2142
+ createdAt: row.createdAt,
2143
+ updatedAt: row.updatedAt
1725
2144
  };
1726
2145
  }
1727
- /** @deprecated use getEvals instead */
1728
- async getEvalsByAgentName(agentName, type) {
2146
+ async getScoreById({ id }) {
2147
+ const result = await this.client.execute({
2148
+ sql: `SELECT * FROM ${TABLE_SCORERS} WHERE id = ?`,
2149
+ args: [id]
2150
+ });
2151
+ return result.rows?.[0] ? this.transformScoreRow(result.rows[0]) : null;
2152
+ }
2153
+ async saveScore(score) {
1729
2154
  try {
1730
- const baseQuery = `SELECT * FROM ${TABLE_EVALS} WHERE agent_name = ?`;
1731
- const typeCondition = type === "test" ? " AND test_info IS NOT NULL AND test_info->>'testPath' IS NOT NULL" : type === "live" ? " AND (test_info IS NULL OR test_info->>'testPath' IS NULL)" : "";
1732
- const result = await this.client.execute({
1733
- sql: `${baseQuery}${typeCondition} ORDER BY created_at DESC`,
1734
- args: [agentName]
2155
+ const id = crypto.randomUUID();
2156
+ await this.operations.insert({
2157
+ tableName: TABLE_SCORERS,
2158
+ record: {
2159
+ id,
2160
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2161
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2162
+ ...score
2163
+ }
1735
2164
  });
1736
- return result.rows?.map((row) => this.transformEvalRow(row)) ?? [];
2165
+ const scoreFromDb = await this.getScoreById({ id });
2166
+ return { score: scoreFromDb };
1737
2167
  } catch (error) {
1738
- if (error instanceof Error && error.message.includes("no such table")) {
1739
- return [];
1740
- }
1741
2168
  throw new MastraError(
1742
2169
  {
1743
- id: "LIBSQL_STORE_GET_EVALS_BY_AGENT_NAME_FAILED",
2170
+ id: "LIBSQL_STORE_SAVE_SCORE_FAILED",
1744
2171
  domain: ErrorDomain.STORAGE,
1745
- category: ErrorCategory.THIRD_PARTY,
1746
- details: { agentName }
2172
+ category: ErrorCategory.THIRD_PARTY
1747
2173
  },
1748
2174
  error
1749
2175
  );
1750
2176
  }
1751
2177
  }
1752
- async getEvals(options = {}) {
1753
- const { agentName, type, page = 0, perPage = 100, dateRange } = options;
1754
- const fromDate = dateRange?.start;
1755
- const toDate = dateRange?.end;
1756
- const conditions = [];
1757
- const queryParams = [];
1758
- if (agentName) {
1759
- conditions.push(`agent_name = ?`);
1760
- queryParams.push(agentName);
1761
- }
1762
- if (type === "test") {
1763
- conditions.push(`(test_info IS NOT NULL AND json_extract(test_info, '$.testPath') IS NOT NULL)`);
1764
- } else if (type === "live") {
1765
- conditions.push(`(test_info IS NULL OR json_extract(test_info, '$.testPath') IS NULL)`);
1766
- }
1767
- if (fromDate) {
1768
- conditions.push(`created_at >= ?`);
1769
- queryParams.push(fromDate.toISOString());
1770
- }
1771
- if (toDate) {
1772
- conditions.push(`created_at <= ?`);
1773
- queryParams.push(toDate.toISOString());
1774
- }
1775
- const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2178
+ async getScoresByEntityId({
2179
+ entityId,
2180
+ entityType,
2181
+ pagination
2182
+ }) {
1776
2183
  try {
1777
- const countResult = await this.client.execute({
1778
- sql: `SELECT COUNT(*) as count FROM ${TABLE_EVALS} ${whereClause}`,
1779
- args: queryParams
1780
- });
1781
- const total = Number(countResult.rows?.[0]?.count ?? 0);
1782
- const currentOffset = page * perPage;
1783
- const hasMore = currentOffset + perPage < total;
1784
- if (total === 0) {
1785
- return {
1786
- evals: [],
1787
- total: 0,
1788
- page,
1789
- perPage,
1790
- hasMore: false
1791
- };
1792
- }
1793
- const dataResult = await this.client.execute({
1794
- sql: `SELECT * FROM ${TABLE_EVALS} ${whereClause} ORDER BY created_at DESC LIMIT ? OFFSET ?`,
1795
- args: [...queryParams, perPage, currentOffset]
2184
+ const result = await this.client.execute({
2185
+ sql: `SELECT * FROM ${TABLE_SCORERS} WHERE entityId = ? AND entityType = ? ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
2186
+ args: [entityId, entityType, pagination.perPage + 1, pagination.page * pagination.perPage]
1796
2187
  });
1797
2188
  return {
1798
- evals: dataResult.rows?.map((row) => this.transformEvalRow(row)) ?? [],
1799
- total,
1800
- page,
1801
- perPage,
1802
- hasMore
2189
+ scores: result.rows?.slice(0, pagination.perPage).map((row) => this.transformScoreRow(row)) ?? [],
2190
+ pagination: {
2191
+ total: result.rows?.length ?? 0,
2192
+ page: pagination.page,
2193
+ perPage: pagination.perPage,
2194
+ hasMore: result.rows?.length > pagination.perPage
2195
+ }
1803
2196
  };
1804
2197
  } catch (error) {
1805
2198
  throw new MastraError(
1806
2199
  {
1807
- id: "LIBSQL_STORE_GET_EVALS_FAILED",
2200
+ id: "LIBSQL_STORE_GET_SCORES_BY_ENTITY_ID_FAILED",
1808
2201
  domain: ErrorDomain.STORAGE,
1809
2202
  category: ErrorCategory.THIRD_PARTY
1810
2203
  },
@@ -1812,9 +2205,15 @@ var LibSQLStore = class extends MastraStorage {
1812
2205
  );
1813
2206
  }
1814
2207
  }
1815
- /**
1816
- * @deprecated use getTracesPaginated instead.
1817
- */
2208
+ };
2209
+ var TracesLibSQL = class extends TracesStorage {
2210
+ client;
2211
+ operations;
2212
+ constructor({ client, operations }) {
2213
+ super();
2214
+ this.client = client;
2215
+ this.operations = operations;
2216
+ }
1818
2217
  async getTraces(args) {
1819
2218
  if (args.fromDate || args.toDate) {
1820
2219
  args.dateRange = {
@@ -1891,35 +2290,133 @@ var LibSQLStore = class extends MastraStorage {
1891
2290
  sql: `SELECT * FROM ${TABLE_TRACES} ${whereClause} ORDER BY "startTime" DESC LIMIT ? OFFSET ?`,
1892
2291
  args: [...queryArgs, perPage, currentOffset]
1893
2292
  });
1894
- const traces = dataResult.rows?.map(
1895
- (row) => ({
1896
- id: row.id,
1897
- parentSpanId: row.parentSpanId,
1898
- traceId: row.traceId,
1899
- name: row.name,
1900
- scope: row.scope,
1901
- kind: row.kind,
1902
- status: safelyParseJSON(row.status),
1903
- events: safelyParseJSON(row.events),
1904
- links: safelyParseJSON(row.links),
1905
- attributes: safelyParseJSON(row.attributes),
1906
- startTime: row.startTime,
1907
- endTime: row.endTime,
1908
- other: safelyParseJSON(row.other),
1909
- createdAt: row.createdAt
1910
- })
1911
- ) ?? [];
1912
- return {
1913
- traces,
1914
- total,
1915
- page,
1916
- perPage,
1917
- hasMore: currentOffset + traces.length < total
1918
- };
2293
+ const traces = dataResult.rows?.map(
2294
+ (row) => ({
2295
+ id: row.id,
2296
+ parentSpanId: row.parentSpanId,
2297
+ traceId: row.traceId,
2298
+ name: row.name,
2299
+ scope: row.scope,
2300
+ kind: row.kind,
2301
+ status: safelyParseJSON(row.status),
2302
+ events: safelyParseJSON(row.events),
2303
+ links: safelyParseJSON(row.links),
2304
+ attributes: safelyParseJSON(row.attributes),
2305
+ startTime: row.startTime,
2306
+ endTime: row.endTime,
2307
+ other: safelyParseJSON(row.other),
2308
+ createdAt: row.createdAt
2309
+ })
2310
+ ) ?? [];
2311
+ return {
2312
+ traces,
2313
+ total,
2314
+ page,
2315
+ perPage,
2316
+ hasMore: currentOffset + traces.length < total
2317
+ };
2318
+ } catch (error) {
2319
+ throw new MastraError(
2320
+ {
2321
+ id: "LIBSQL_STORE_GET_TRACES_PAGINATED_FAILED",
2322
+ domain: ErrorDomain.STORAGE,
2323
+ category: ErrorCategory.THIRD_PARTY
2324
+ },
2325
+ error
2326
+ );
2327
+ }
2328
+ }
2329
+ async batchTraceInsert({ records }) {
2330
+ this.logger.debug("Batch inserting traces", { count: records.length });
2331
+ await this.operations.batchInsert({
2332
+ tableName: TABLE_TRACES,
2333
+ records
2334
+ });
2335
+ }
2336
+ };
2337
+ function parseWorkflowRun(row) {
2338
+ let parsedSnapshot = row.snapshot;
2339
+ if (typeof parsedSnapshot === "string") {
2340
+ try {
2341
+ parsedSnapshot = JSON.parse(row.snapshot);
2342
+ } catch (e) {
2343
+ console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
2344
+ }
2345
+ }
2346
+ return {
2347
+ workflowName: row.workflow_name,
2348
+ runId: row.run_id,
2349
+ snapshot: parsedSnapshot,
2350
+ resourceId: row.resourceId,
2351
+ createdAt: new Date(row.createdAt),
2352
+ updatedAt: new Date(row.updatedAt)
2353
+ };
2354
+ }
2355
+ var WorkflowsLibSQL = class extends WorkflowsStorage {
2356
+ operations;
2357
+ client;
2358
+ constructor({ operations, client }) {
2359
+ super();
2360
+ this.operations = operations;
2361
+ this.client = client;
2362
+ }
2363
+ async persistWorkflowSnapshot({
2364
+ workflowName,
2365
+ runId,
2366
+ snapshot
2367
+ }) {
2368
+ const data = {
2369
+ workflow_name: workflowName,
2370
+ run_id: runId,
2371
+ snapshot,
2372
+ createdAt: /* @__PURE__ */ new Date(),
2373
+ updatedAt: /* @__PURE__ */ new Date()
2374
+ };
2375
+ this.logger.debug("Persisting workflow snapshot", { workflowName, runId, data });
2376
+ await this.operations.insert({
2377
+ tableName: TABLE_WORKFLOW_SNAPSHOT,
2378
+ record: data
2379
+ });
2380
+ }
2381
+ async loadWorkflowSnapshot({
2382
+ workflowName,
2383
+ runId
2384
+ }) {
2385
+ this.logger.debug("Loading workflow snapshot", { workflowName, runId });
2386
+ const d = await this.operations.load({
2387
+ tableName: TABLE_WORKFLOW_SNAPSHOT,
2388
+ keys: { workflow_name: workflowName, run_id: runId }
2389
+ });
2390
+ return d ? d.snapshot : null;
2391
+ }
2392
+ async getWorkflowRunById({
2393
+ runId,
2394
+ workflowName
2395
+ }) {
2396
+ const conditions = [];
2397
+ const args = [];
2398
+ if (runId) {
2399
+ conditions.push("run_id = ?");
2400
+ args.push(runId);
2401
+ }
2402
+ if (workflowName) {
2403
+ conditions.push("workflow_name = ?");
2404
+ args.push(workflowName);
2405
+ }
2406
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2407
+ try {
2408
+ const result = await this.client.execute({
2409
+ sql: `SELECT * FROM ${TABLE_WORKFLOW_SNAPSHOT} ${whereClause}`,
2410
+ args
2411
+ });
2412
+ if (!result.rows?.[0]) {
2413
+ return null;
2414
+ }
2415
+ return parseWorkflowRun(result.rows[0]);
1919
2416
  } catch (error) {
1920
2417
  throw new MastraError(
1921
2418
  {
1922
- id: "LIBSQL_STORE_GET_TRACES_PAGINATED_FAILED",
2419
+ id: "LIBSQL_STORE_GET_WORKFLOW_RUN_BY_ID_FAILED",
1923
2420
  domain: ErrorDomain.STORAGE,
1924
2421
  category: ErrorCategory.THIRD_PARTY
1925
2422
  },
@@ -1951,7 +2448,7 @@ var LibSQLStore = class extends MastraStorage {
1951
2448
  args.push(toDate.toISOString());
1952
2449
  }
1953
2450
  if (resourceId) {
1954
- const hasResourceId = await this.hasColumn(TABLE_WORKFLOW_SNAPSHOT, "resourceId");
2451
+ const hasResourceId = await this.operations.hasColumn(TABLE_WORKFLOW_SNAPSHOT, "resourceId");
1955
2452
  if (hasResourceId) {
1956
2453
  conditions.push("resourceId = ?");
1957
2454
  args.push(resourceId);
@@ -1972,7 +2469,7 @@ var LibSQLStore = class extends MastraStorage {
1972
2469
  sql: `SELECT * FROM ${TABLE_WORKFLOW_SNAPSHOT} ${whereClause} ORDER BY createdAt DESC${limit !== void 0 && offset !== void 0 ? ` LIMIT ? OFFSET ?` : ""}`,
1973
2470
  args: limit !== void 0 && offset !== void 0 ? [...args, limit, offset] : args
1974
2471
  });
1975
- const runs = (result.rows || []).map((row) => this.parseWorkflowRun(row));
2472
+ const runs = (result.rows || []).map((row) => parseWorkflowRun(row));
1976
2473
  return { runs, total: total || runs.length };
1977
2474
  } catch (error) {
1978
2475
  throw new MastraError(
@@ -1985,133 +2482,235 @@ var LibSQLStore = class extends MastraStorage {
1985
2482
  );
1986
2483
  }
1987
2484
  }
1988
- async getWorkflowRunById({
1989
- runId,
1990
- workflowName
1991
- }) {
1992
- const conditions = [];
1993
- const args = [];
1994
- if (runId) {
1995
- conditions.push("run_id = ?");
1996
- args.push(runId);
1997
- }
1998
- if (workflowName) {
1999
- conditions.push("workflow_name = ?");
2000
- args.push(workflowName);
2001
- }
2002
- const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2003
- try {
2004
- const result = await this.client.execute({
2005
- sql: `SELECT * FROM ${TABLE_WORKFLOW_SNAPSHOT} ${whereClause}`,
2006
- args
2485
+ };
2486
+
2487
+ // src/storage/index.ts
2488
+ var LibSQLStore = class extends MastraStorage {
2489
+ client;
2490
+ maxRetries;
2491
+ initialBackoffMs;
2492
+ stores;
2493
+ constructor(config) {
2494
+ super({ name: `LibSQLStore` });
2495
+ this.maxRetries = config.maxRetries ?? 5;
2496
+ this.initialBackoffMs = config.initialBackoffMs ?? 100;
2497
+ if ("url" in config) {
2498
+ if (config.url.endsWith(":memory:")) {
2499
+ this.shouldCacheInit = false;
2500
+ }
2501
+ this.client = createClient({
2502
+ url: config.url,
2503
+ ...config.authToken ? { authToken: config.authToken } : {}
2007
2504
  });
2008
- if (!result.rows?.[0]) {
2009
- return null;
2505
+ if (config.url.startsWith("file:") || config.url.includes(":memory:")) {
2506
+ this.client.execute("PRAGMA journal_mode=WAL;").then(() => this.logger.debug("LibSQLStore: PRAGMA journal_mode=WAL set.")).catch((err) => this.logger.warn("LibSQLStore: Failed to set PRAGMA journal_mode=WAL.", err));
2507
+ this.client.execute("PRAGMA busy_timeout = 5000;").then(() => this.logger.debug("LibSQLStore: PRAGMA busy_timeout=5000 set.")).catch((err) => this.logger.warn("LibSQLStore: Failed to set PRAGMA busy_timeout.", err));
2010
2508
  }
2011
- return this.parseWorkflowRun(result.rows[0]);
2012
- } catch (error) {
2013
- throw new MastraError(
2014
- {
2015
- id: "LIBSQL_STORE_GET_WORKFLOW_RUN_BY_ID_FAILED",
2016
- domain: ErrorDomain.STORAGE,
2017
- category: ErrorCategory.THIRD_PARTY
2018
- },
2019
- error
2020
- );
2509
+ } else {
2510
+ this.client = config.client;
2021
2511
  }
2022
- }
2023
- async getResourceById({ resourceId }) {
2024
- const result = await this.load({
2025
- tableName: TABLE_RESOURCES,
2026
- keys: { id: resourceId }
2512
+ const operations = new StoreOperationsLibSQL({
2513
+ client: this.client,
2514
+ maxRetries: this.maxRetries,
2515
+ initialBackoffMs: this.initialBackoffMs
2027
2516
  });
2028
- if (!result) {
2029
- return null;
2030
- }
2517
+ const scores = new ScoresLibSQL({ client: this.client, operations });
2518
+ const traces = new TracesLibSQL({ client: this.client, operations });
2519
+ const workflows = new WorkflowsLibSQL({ client: this.client, operations });
2520
+ const memory = new MemoryLibSQL({ client: this.client, operations });
2521
+ const legacyEvals = new LegacyEvalsLibSQL({ client: this.client });
2522
+ this.stores = {
2523
+ operations,
2524
+ scores,
2525
+ traces,
2526
+ workflows,
2527
+ memory,
2528
+ legacyEvals
2529
+ };
2530
+ }
2531
+ get supports() {
2031
2532
  return {
2032
- ...result,
2033
- // Ensure workingMemory is always returned as a string, even if auto-parsed as JSON
2034
- workingMemory: typeof result.workingMemory === "object" ? JSON.stringify(result.workingMemory) : result.workingMemory,
2035
- metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata
2533
+ selectByIncludeResourceScope: true,
2534
+ resourceWorkingMemory: true,
2535
+ hasColumn: true,
2536
+ createTable: true,
2537
+ deleteMessages: true
2036
2538
  };
2037
2539
  }
2540
+ async createTable({
2541
+ tableName,
2542
+ schema
2543
+ }) {
2544
+ await this.stores.operations.createTable({ tableName, schema });
2545
+ }
2546
+ /**
2547
+ * Alters table schema to add columns if they don't exist
2548
+ * @param tableName Name of the table
2549
+ * @param schema Schema of the table
2550
+ * @param ifNotExists Array of column names to add if they don't exist
2551
+ */
2552
+ async alterTable({
2553
+ tableName,
2554
+ schema,
2555
+ ifNotExists
2556
+ }) {
2557
+ await this.stores.operations.alterTable({ tableName, schema, ifNotExists });
2558
+ }
2559
+ async clearTable({ tableName }) {
2560
+ await this.stores.operations.clearTable({ tableName });
2561
+ }
2562
+ async dropTable({ tableName }) {
2563
+ await this.stores.operations.dropTable({ tableName });
2564
+ }
2565
+ insert(args) {
2566
+ return this.stores.operations.insert(args);
2567
+ }
2568
+ batchInsert(args) {
2569
+ return this.stores.operations.batchInsert(args);
2570
+ }
2571
+ async load({ tableName, keys }) {
2572
+ return this.stores.operations.load({ tableName, keys });
2573
+ }
2574
+ async getThreadById({ threadId }) {
2575
+ return this.stores.memory.getThreadById({ threadId });
2576
+ }
2577
+ /**
2578
+ * @deprecated use getThreadsByResourceIdPaginated instead for paginated results.
2579
+ */
2580
+ async getThreadsByResourceId(args) {
2581
+ return this.stores.memory.getThreadsByResourceId(args);
2582
+ }
2583
+ async getThreadsByResourceIdPaginated(args) {
2584
+ return this.stores.memory.getThreadsByResourceIdPaginated(args);
2585
+ }
2586
+ async saveThread({ thread }) {
2587
+ return this.stores.memory.saveThread({ thread });
2588
+ }
2589
+ async updateThread({
2590
+ id,
2591
+ title,
2592
+ metadata
2593
+ }) {
2594
+ return this.stores.memory.updateThread({ id, title, metadata });
2595
+ }
2596
+ async deleteThread({ threadId }) {
2597
+ return this.stores.memory.deleteThread({ threadId });
2598
+ }
2599
+ async getMessages({
2600
+ threadId,
2601
+ selectBy,
2602
+ format
2603
+ }) {
2604
+ return this.stores.memory.getMessages({ threadId, selectBy, format });
2605
+ }
2606
+ async getMessagesPaginated(args) {
2607
+ return this.stores.memory.getMessagesPaginated(args);
2608
+ }
2609
+ async saveMessages(args) {
2610
+ return this.stores.memory.saveMessages(args);
2611
+ }
2612
+ async updateMessages({
2613
+ messages
2614
+ }) {
2615
+ return this.stores.memory.updateMessages({ messages });
2616
+ }
2617
+ async deleteMessages(messageIds) {
2618
+ return this.stores.memory.deleteMessages(messageIds);
2619
+ }
2620
+ /** @deprecated use getEvals instead */
2621
+ async getEvalsByAgentName(agentName, type) {
2622
+ return this.stores.legacyEvals.getEvalsByAgentName(agentName, type);
2623
+ }
2624
+ async getEvals(options = {}) {
2625
+ return this.stores.legacyEvals.getEvals(options);
2626
+ }
2627
+ async getScoreById({ id }) {
2628
+ return this.stores.scores.getScoreById({ id });
2629
+ }
2630
+ async saveScore(score) {
2631
+ return this.stores.scores.saveScore(score);
2632
+ }
2633
+ async getScoresByScorerId({
2634
+ scorerId,
2635
+ entityId,
2636
+ entityType,
2637
+ source,
2638
+ pagination
2639
+ }) {
2640
+ return this.stores.scores.getScoresByScorerId({ scorerId, entityId, entityType, source, pagination });
2641
+ }
2642
+ async getScoresByRunId({
2643
+ runId,
2644
+ pagination
2645
+ }) {
2646
+ return this.stores.scores.getScoresByRunId({ runId, pagination });
2647
+ }
2648
+ async getScoresByEntityId({
2649
+ entityId,
2650
+ entityType,
2651
+ pagination
2652
+ }) {
2653
+ return this.stores.scores.getScoresByEntityId({ entityId, entityType, pagination });
2654
+ }
2655
+ /**
2656
+ * TRACES
2657
+ */
2658
+ /**
2659
+ * @deprecated use getTracesPaginated instead.
2660
+ */
2661
+ async getTraces(args) {
2662
+ return this.stores.traces.getTraces(args);
2663
+ }
2664
+ async getTracesPaginated(args) {
2665
+ return this.stores.traces.getTracesPaginated(args);
2666
+ }
2667
+ async batchTraceInsert(args) {
2668
+ return this.stores.traces.batchTraceInsert(args);
2669
+ }
2670
+ /**
2671
+ * WORKFLOWS
2672
+ */
2673
+ async persistWorkflowSnapshot({
2674
+ workflowName,
2675
+ runId,
2676
+ snapshot
2677
+ }) {
2678
+ return this.stores.workflows.persistWorkflowSnapshot({ workflowName, runId, snapshot });
2679
+ }
2680
+ async loadWorkflowSnapshot({
2681
+ workflowName,
2682
+ runId
2683
+ }) {
2684
+ return this.stores.workflows.loadWorkflowSnapshot({ workflowName, runId });
2685
+ }
2686
+ async getWorkflowRuns({
2687
+ workflowName,
2688
+ fromDate,
2689
+ toDate,
2690
+ limit,
2691
+ offset,
2692
+ resourceId
2693
+ } = {}) {
2694
+ return this.stores.workflows.getWorkflowRuns({ workflowName, fromDate, toDate, limit, offset, resourceId });
2695
+ }
2696
+ async getWorkflowRunById({
2697
+ runId,
2698
+ workflowName
2699
+ }) {
2700
+ return this.stores.workflows.getWorkflowRunById({ runId, workflowName });
2701
+ }
2702
+ async getResourceById({ resourceId }) {
2703
+ return this.stores.memory.getResourceById({ resourceId });
2704
+ }
2038
2705
  async saveResource({ resource }) {
2039
- await this.insert({
2040
- tableName: TABLE_RESOURCES,
2041
- record: {
2042
- ...resource,
2043
- metadata: JSON.stringify(resource.metadata)
2044
- }
2045
- });
2046
- return resource;
2706
+ return this.stores.memory.saveResource({ resource });
2047
2707
  }
2048
2708
  async updateResource({
2049
2709
  resourceId,
2050
2710
  workingMemory,
2051
2711
  metadata
2052
2712
  }) {
2053
- const existingResource = await this.getResourceById({ resourceId });
2054
- if (!existingResource) {
2055
- const newResource = {
2056
- id: resourceId,
2057
- workingMemory,
2058
- metadata: metadata || {},
2059
- createdAt: /* @__PURE__ */ new Date(),
2060
- updatedAt: /* @__PURE__ */ new Date()
2061
- };
2062
- return this.saveResource({ resource: newResource });
2063
- }
2064
- const updatedResource = {
2065
- ...existingResource,
2066
- workingMemory: workingMemory !== void 0 ? workingMemory : existingResource.workingMemory,
2067
- metadata: {
2068
- ...existingResource.metadata,
2069
- ...metadata
2070
- },
2071
- updatedAt: /* @__PURE__ */ new Date()
2072
- };
2073
- const updates = [];
2074
- const values = [];
2075
- if (workingMemory !== void 0) {
2076
- updates.push("workingMemory = ?");
2077
- values.push(workingMemory);
2078
- }
2079
- if (metadata) {
2080
- updates.push("metadata = ?");
2081
- values.push(JSON.stringify(updatedResource.metadata));
2082
- }
2083
- updates.push("updatedAt = ?");
2084
- values.push(updatedResource.updatedAt.toISOString());
2085
- values.push(resourceId);
2086
- await this.client.execute({
2087
- sql: `UPDATE ${TABLE_RESOURCES} SET ${updates.join(", ")} WHERE id = ?`,
2088
- args: values
2089
- });
2090
- return updatedResource;
2091
- }
2092
- async hasColumn(table, column) {
2093
- const result = await this.client.execute({
2094
- sql: `PRAGMA table_info(${table})`
2095
- });
2096
- return (await result.rows)?.some((row) => row.name === column);
2097
- }
2098
- parseWorkflowRun(row) {
2099
- let parsedSnapshot = row.snapshot;
2100
- if (typeof parsedSnapshot === "string") {
2101
- try {
2102
- parsedSnapshot = JSON.parse(row.snapshot);
2103
- } catch (e) {
2104
- console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
2105
- }
2106
- }
2107
- return {
2108
- workflowName: row.workflow_name,
2109
- runId: row.run_id,
2110
- snapshot: parsedSnapshot,
2111
- resourceId: row.resourceId,
2112
- createdAt: new Date(row.createdAt),
2113
- updatedAt: new Date(row.updatedAt)
2114
- };
2713
+ return this.stores.memory.updateResource({ resourceId, workingMemory, metadata });
2115
2714
  }
2116
2715
  };
2117
2716
 
@@ -2215,3 +2814,5 @@ Example Complex Query:
2215
2814
  }`;
2216
2815
 
2217
2816
  export { LibSQLStore as DefaultStorage, LIBSQL_PROMPT, LibSQLStore, LibSQLVector };
2817
+ //# sourceMappingURL=index.js.map
2818
+ //# sourceMappingURL=index.js.map