@mastra/libsql 0.0.0-bundle-recursion-20251030002519 → 0.0.0-bundle-studio-cloud-20251222034739

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 (31) hide show
  1. package/CHANGELOG.md +861 -3
  2. package/README.md +30 -20
  3. package/dist/index.cjs +2098 -1668
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.js +2032 -1602
  6. package/dist/index.js.map +1 -1
  7. package/dist/storage/db/index.d.ts +259 -0
  8. package/dist/storage/db/index.d.ts.map +1 -0
  9. package/dist/storage/{domains → db}/utils.d.ts +4 -0
  10. package/dist/storage/db/utils.d.ts.map +1 -0
  11. package/dist/storage/domains/agents/index.d.ts +23 -0
  12. package/dist/storage/domains/agents/index.d.ts.map +1 -0
  13. package/dist/storage/domains/memory/index.d.ts +19 -53
  14. package/dist/storage/domains/memory/index.d.ts.map +1 -1
  15. package/dist/storage/domains/observability/index.d.ts +17 -17
  16. package/dist/storage/domains/observability/index.d.ts.map +1 -1
  17. package/dist/storage/domains/scores/index.d.ts +15 -14
  18. package/dist/storage/domains/scores/index.d.ts.map +1 -1
  19. package/dist/storage/domains/workflows/index.d.ts +18 -32
  20. package/dist/storage/domains/workflows/index.d.ts.map +1 -1
  21. package/dist/storage/index.d.ts +70 -128
  22. package/dist/storage/index.d.ts.map +1 -1
  23. package/dist/vector/index.d.ts +7 -3
  24. package/dist/vector/index.d.ts.map +1 -1
  25. package/dist/vector/sql-builder.d.ts.map +1 -1
  26. package/package.json +12 -8
  27. package/dist/storage/domains/legacy-evals/index.d.ts +0 -18
  28. package/dist/storage/domains/legacy-evals/index.d.ts.map +0 -1
  29. package/dist/storage/domains/operations/index.d.ts +0 -110
  30. package/dist/storage/domains/operations/index.d.ts.map +0 -1
  31. package/dist/storage/domains/utils.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -1,11 +1,12 @@
1
1
  import { createClient } from '@libsql/client';
2
2
  import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
3
+ import { createVectorErrorId, MastraStorage, ScoresStorage, SCORERS_SCHEMA, TABLE_SCORERS, normalizePerPage, calculatePagination, createStorageErrorId, transformScoreRow, WorkflowsStorage, TABLE_SCHEMAS, TABLE_WORKFLOW_SNAPSHOT, MemoryStorage, TABLE_THREADS, TABLE_MESSAGES, TABLE_RESOURCES, ObservabilityStorage, SPAN_SCHEMA, TABLE_SPANS, AgentsStorage, AGENTS_SCHEMA, TABLE_AGENTS, getSqlType, safelyParseJSON } from '@mastra/core/storage';
3
4
  import { parseSqlIdentifier, parseFieldKey } from '@mastra/core/utils';
4
5
  import { MastraVector } from '@mastra/core/vector';
5
6
  import { BaseFilterTranslator } from '@mastra/core/vector/filter';
6
- import { MastraStorage, StoreOperations, TABLE_WORKFLOW_SNAPSHOT, TABLE_AI_SPANS, ScoresStorage, TABLE_SCORERS, safelyParseJSON, WorkflowsStorage, MemoryStorage, resolveMessageLimit, TABLE_MESSAGES, TABLE_THREADS, TABLE_RESOURCES, LegacyEvalsStorage, TABLE_EVALS, ObservabilityStorage, TABLE_SCHEMAS, AI_SPAN_SCHEMA } from '@mastra/core/storage';
7
+ import { MastraBase } from '@mastra/core/base';
7
8
  import { MessageList } from '@mastra/core/agent';
8
- import { saveScorePayloadSchema } from '@mastra/core/scores';
9
+ import { saveScorePayloadSchema } from '@mastra/core/evals';
9
10
 
10
11
  // src/vector/index.ts
11
12
  var LibSQLFilterTranslator = class extends BaseFilterTranslator {
@@ -90,12 +91,20 @@ var createBasicOperator = (symbol) => {
90
91
  };
91
92
  };
92
93
  var createNumericOperator = (symbol) => {
93
- return (key) => {
94
+ return (key, value) => {
94
95
  const jsonPath = getJsonPath(key);
95
- return {
96
- sql: `CAST(json_extract(metadata, ${jsonPath}) AS NUMERIC) ${symbol} ?`,
97
- needsValue: true
98
- };
96
+ const isNumeric = typeof value === "number" || typeof value === "string" && !isNaN(Number(value)) && value.trim() !== "";
97
+ if (isNumeric) {
98
+ return {
99
+ sql: `CAST(json_extract(metadata, ${jsonPath}) AS NUMERIC) ${symbol} ?`,
100
+ needsValue: true
101
+ };
102
+ } else {
103
+ return {
104
+ sql: `CAST(json_extract(metadata, ${jsonPath}) AS TEXT) ${symbol} ?`,
105
+ needsValue: true
106
+ };
107
+ }
99
108
  };
100
109
  };
101
110
  var validateJsonArray = (key) => {
@@ -505,9 +514,10 @@ var LibSQLVector = class extends MastraVector {
505
514
  syncUrl,
506
515
  syncInterval,
507
516
  maxRetries = 5,
508
- initialBackoffMs = 100
517
+ initialBackoffMs = 100,
518
+ id
509
519
  }) {
510
- super();
520
+ super({ id });
511
521
  this.turso = createClient({
512
522
  url: connectionUrl,
513
523
  syncUrl,
@@ -572,7 +582,7 @@ var LibSQLVector = class extends MastraVector {
572
582
  } catch (error) {
573
583
  throw new MastraError(
574
584
  {
575
- id: "LIBSQL_VECTOR_QUERY_INVALID_ARGS",
585
+ id: createVectorErrorId("LIBSQL", "QUERY", "INVALID_ARGS"),
576
586
  domain: ErrorDomain.STORAGE,
577
587
  category: ErrorCategory.USER
578
588
  },
@@ -614,7 +624,7 @@ var LibSQLVector = class extends MastraVector {
614
624
  } catch (error) {
615
625
  throw new MastraError(
616
626
  {
617
- id: "LIBSQL_VECTOR_QUERY_FAILED",
627
+ id: createVectorErrorId("LIBSQL", "QUERY", "FAILED"),
618
628
  domain: ErrorDomain.STORAGE,
619
629
  category: ErrorCategory.THIRD_PARTY
620
630
  },
@@ -628,7 +638,7 @@ var LibSQLVector = class extends MastraVector {
628
638
  } catch (error) {
629
639
  throw new MastraError(
630
640
  {
631
- id: "LIBSQL_VECTOR_UPSERT_FAILED",
641
+ id: createVectorErrorId("LIBSQL", "UPSERT", "FAILED"),
632
642
  domain: ErrorDomain.STORAGE,
633
643
  category: ErrorCategory.THIRD_PARTY
634
644
  },
@@ -682,7 +692,7 @@ var LibSQLVector = class extends MastraVector {
682
692
  } catch (error) {
683
693
  throw new MastraError(
684
694
  {
685
- id: "LIBSQL_VECTOR_CREATE_INDEX_FAILED",
695
+ id: createVectorErrorId("LIBSQL", "CREATE_INDEX", "FAILED"),
686
696
  domain: ErrorDomain.STORAGE,
687
697
  category: ErrorCategory.THIRD_PARTY,
688
698
  details: { indexName: args.indexName, dimension: args.dimension }
@@ -721,7 +731,7 @@ var LibSQLVector = class extends MastraVector {
721
731
  } catch (error) {
722
732
  throw new MastraError(
723
733
  {
724
- id: "LIBSQL_VECTOR_DELETE_INDEX_FAILED",
734
+ id: createVectorErrorId("LIBSQL", "DELETE_INDEX", "FAILED"),
725
735
  domain: ErrorDomain.STORAGE,
726
736
  category: ErrorCategory.THIRD_PARTY,
727
737
  details: { indexName: args.indexName }
@@ -752,7 +762,7 @@ var LibSQLVector = class extends MastraVector {
752
762
  } catch (error) {
753
763
  throw new MastraError(
754
764
  {
755
- id: "LIBSQL_VECTOR_LIST_INDEXES_FAILED",
765
+ id: createVectorErrorId("LIBSQL", "LIST_INDEXES", "FAILED"),
756
766
  domain: ErrorDomain.STORAGE,
757
767
  category: ErrorCategory.THIRD_PARTY
758
768
  },
@@ -800,7 +810,7 @@ var LibSQLVector = class extends MastraVector {
800
810
  } catch (e) {
801
811
  throw new MastraError(
802
812
  {
803
- id: "LIBSQL_VECTOR_DESCRIBE_INDEX_FAILED",
813
+ id: createVectorErrorId("LIBSQL", "DESCRIBE_INDEX", "FAILED"),
804
814
  domain: ErrorDomain.STORAGE,
805
815
  category: ErrorCategory.THIRD_PARTY,
806
816
  details: { indexName }
@@ -823,8 +833,27 @@ var LibSQLVector = class extends MastraVector {
823
833
  updateVector(args) {
824
834
  return this.executeWriteOperationWithRetry(() => this.doUpdateVector(args));
825
835
  }
826
- async doUpdateVector({ indexName, id, update }) {
836
+ async doUpdateVector(params) {
837
+ const { indexName, update } = params;
827
838
  const parsedIndexName = parseSqlIdentifier(indexName, "index name");
839
+ if ("id" in params && params.id && "filter" in params && params.filter) {
840
+ throw new MastraError({
841
+ id: createVectorErrorId("LIBSQL", "UPDATE_VECTOR", "MUTUALLY_EXCLUSIVE"),
842
+ domain: ErrorDomain.STORAGE,
843
+ category: ErrorCategory.USER,
844
+ details: { indexName },
845
+ text: "id and filter are mutually exclusive - provide only one"
846
+ });
847
+ }
848
+ if (!update.vector && !update.metadata) {
849
+ throw new MastraError({
850
+ id: createVectorErrorId("LIBSQL", "UPDATE_VECTOR", "NO_PAYLOAD"),
851
+ domain: ErrorDomain.STORAGE,
852
+ category: ErrorCategory.USER,
853
+ details: { indexName },
854
+ text: "No updates provided"
855
+ });
856
+ }
828
857
  const updates = [];
829
858
  const args = [];
830
859
  if (update.vector) {
@@ -836,32 +865,81 @@ var LibSQLVector = class extends MastraVector {
836
865
  args.push(JSON.stringify(update.metadata));
837
866
  }
838
867
  if (updates.length === 0) {
868
+ return;
869
+ }
870
+ let whereClause;
871
+ let whereValues;
872
+ if ("id" in params && params.id) {
873
+ whereClause = "vector_id = ?";
874
+ whereValues = [params.id];
875
+ } else if ("filter" in params && params.filter) {
876
+ const filter = params.filter;
877
+ if (!filter || Object.keys(filter).length === 0) {
878
+ throw new MastraError({
879
+ id: createVectorErrorId("LIBSQL", "UPDATE_VECTOR", "EMPTY_FILTER"),
880
+ domain: ErrorDomain.STORAGE,
881
+ category: ErrorCategory.USER,
882
+ details: { indexName },
883
+ text: "Cannot update with empty filter"
884
+ });
885
+ }
886
+ const translatedFilter = this.transformFilter(filter);
887
+ const { sql: filterSql, values: filterValues } = buildFilterQuery(translatedFilter);
888
+ if (!filterSql || filterSql.trim() === "") {
889
+ throw new MastraError({
890
+ id: createVectorErrorId("LIBSQL", "UPDATE_VECTOR", "INVALID_FILTER"),
891
+ domain: ErrorDomain.STORAGE,
892
+ category: ErrorCategory.USER,
893
+ details: { indexName },
894
+ text: "Filter produced empty WHERE clause"
895
+ });
896
+ }
897
+ const normalizedCondition = filterSql.replace(/^\s*WHERE\s+/i, "").trim().toLowerCase();
898
+ const matchAllPatterns = ["true", "1 = 1", "1=1"];
899
+ if (matchAllPatterns.includes(normalizedCondition)) {
900
+ throw new MastraError({
901
+ id: createVectorErrorId("LIBSQL", "UPDATE_VECTOR", "MATCH_ALL_FILTER"),
902
+ domain: ErrorDomain.STORAGE,
903
+ category: ErrorCategory.USER,
904
+ details: { indexName, filterSql: normalizedCondition },
905
+ text: "Filter matches all vectors. Provide a specific filter to update targeted vectors."
906
+ });
907
+ }
908
+ whereClause = filterSql.replace(/^WHERE\s+/i, "");
909
+ whereValues = filterValues;
910
+ } else {
839
911
  throw new MastraError({
840
- id: "LIBSQL_VECTOR_UPDATE_VECTOR_INVALID_ARGS",
912
+ id: createVectorErrorId("LIBSQL", "UPDATE_VECTOR", "NO_TARGET"),
841
913
  domain: ErrorDomain.STORAGE,
842
914
  category: ErrorCategory.USER,
843
- details: { indexName, id },
844
- text: "No updates provided"
915
+ details: { indexName },
916
+ text: "Either id or filter must be provided"
845
917
  });
846
918
  }
847
- args.push(id);
848
919
  const query = `
849
- UPDATE ${parsedIndexName}
850
- SET ${updates.join(", ")}
851
- WHERE vector_id = ?;
852
- `;
920
+ UPDATE ${parsedIndexName}
921
+ SET ${updates.join(", ")}
922
+ WHERE ${whereClause};
923
+ `;
853
924
  try {
854
925
  await this.turso.execute({
855
926
  sql: query,
856
- args
927
+ args: [...args, ...whereValues]
857
928
  });
858
929
  } catch (error) {
930
+ const errorDetails = { indexName };
931
+ if ("id" in params && params.id) {
932
+ errorDetails.id = params.id;
933
+ }
934
+ if ("filter" in params && params.filter) {
935
+ errorDetails.filter = JSON.stringify(params.filter);
936
+ }
859
937
  throw new MastraError(
860
938
  {
861
- id: "LIBSQL_VECTOR_UPDATE_VECTOR_FAILED",
939
+ id: createVectorErrorId("LIBSQL", "UPDATE_VECTOR", "FAILED"),
862
940
  domain: ErrorDomain.STORAGE,
863
941
  category: ErrorCategory.THIRD_PARTY,
864
- details: { indexName, id }
942
+ details: errorDetails
865
943
  },
866
944
  error
867
945
  );
@@ -880,10 +958,13 @@ var LibSQLVector = class extends MastraVector {
880
958
  } catch (error) {
881
959
  throw new MastraError(
882
960
  {
883
- id: "LIBSQL_VECTOR_DELETE_VECTOR_FAILED",
961
+ id: createVectorErrorId("LIBSQL", "DELETE_VECTOR", "FAILED"),
884
962
  domain: ErrorDomain.STORAGE,
885
963
  category: ErrorCategory.THIRD_PARTY,
886
- details: { indexName: args.indexName, id: args.id }
964
+ details: {
965
+ indexName: args.indexName,
966
+ ...args.id && { id: args.id }
967
+ }
887
968
  },
888
969
  error
889
970
  );
@@ -896,392 +977,1322 @@ var LibSQLVector = class extends MastraVector {
896
977
  args: [id]
897
978
  });
898
979
  }
899
- truncateIndex(args) {
900
- try {
901
- return this.executeWriteOperationWithRetry(() => this._doTruncateIndex(args));
902
- } catch (error) {
903
- throw new MastraError(
904
- {
905
- id: "LIBSQL_VECTOR_TRUNCATE_INDEX_FAILED",
906
- domain: ErrorDomain.STORAGE,
907
- category: ErrorCategory.THIRD_PARTY,
908
- details: { indexName: args.indexName }
909
- },
910
- error
911
- );
912
- }
913
- }
914
- async _doTruncateIndex({ indexName }) {
915
- await this.turso.execute({
916
- sql: `DELETE FROM ${parseSqlIdentifier(indexName, "index name")}`,
917
- args: []
918
- });
919
- }
920
- };
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)}`);
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
- };
939
- }
940
- var LegacyEvalsLibSQL = class extends LegacyEvalsStorage {
941
- client;
942
- constructor({ client }) {
943
- super();
944
- this.client = client;
980
+ deleteVectors(args) {
981
+ return this.executeWriteOperationWithRetry(() => this.doDeleteVectors(args));
945
982
  }
946
- /** @deprecated use getEvals instead */
947
- async getEvalsByAgentName(agentName, type) {
948
- try {
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]
983
+ async doDeleteVectors({ indexName, filter, ids }) {
984
+ const parsedIndexName = parseSqlIdentifier(indexName, "index name");
985
+ if (!filter && !ids) {
986
+ throw new MastraError({
987
+ id: createVectorErrorId("LIBSQL", "DELETE_VECTORS", "NO_TARGET"),
988
+ domain: ErrorDomain.STORAGE,
989
+ category: ErrorCategory.USER,
990
+ details: { indexName },
991
+ text: "Either filter or ids must be provided"
954
992
  });
955
- return result.rows?.map((row) => transformEvalRow(row)) ?? [];
956
- } catch (error) {
957
- if (error instanceof Error && error.message.includes("no such table")) {
958
- return [];
959
- }
960
- throw new MastraError(
961
- {
962
- id: "LIBSQL_STORE_GET_EVALS_BY_AGENT_NAME_FAILED",
963
- domain: ErrorDomain.STORAGE,
964
- category: ErrorCategory.THIRD_PARTY,
965
- details: { agentName }
966
- },
967
- error
968
- );
969
993
  }
970
- }
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);
980
- }
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 ")}` : "";
995
- try {
996
- const countResult = await this.client.execute({
997
- sql: `SELECT COUNT(*) as count FROM ${TABLE_EVALS} ${whereClause}`,
998
- args: queryParams
994
+ if (filter && ids) {
995
+ throw new MastraError({
996
+ id: createVectorErrorId("LIBSQL", "DELETE_VECTORS", "MUTUALLY_EXCLUSIVE"),
997
+ domain: ErrorDomain.STORAGE,
998
+ category: ErrorCategory.USER,
999
+ details: { indexName },
1000
+ text: "Cannot provide both filter and ids - they are mutually exclusive"
999
1001
  });
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
- };
1002
+ }
1003
+ let query;
1004
+ let values;
1005
+ if (ids) {
1006
+ if (ids.length === 0) {
1007
+ throw new MastraError({
1008
+ id: createVectorErrorId("LIBSQL", "DELETE_VECTORS", "EMPTY_IDS"),
1009
+ domain: ErrorDomain.STORAGE,
1010
+ category: ErrorCategory.USER,
1011
+ details: { indexName },
1012
+ text: "Cannot delete with empty ids array"
1013
+ });
1011
1014
  }
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
- };
1023
- } catch (error) {
1024
- throw new MastraError(
1025
- {
1026
- id: "LIBSQL_STORE_GET_EVALS_FAILED",
1015
+ const placeholders = ids.map(() => "?").join(", ");
1016
+ query = `DELETE FROM ${parsedIndexName} WHERE vector_id IN (${placeholders})`;
1017
+ values = ids;
1018
+ } else {
1019
+ if (!filter || Object.keys(filter).length === 0) {
1020
+ throw new MastraError({
1021
+ id: createVectorErrorId("LIBSQL", "DELETE_VECTORS", "EMPTY_FILTER"),
1027
1022
  domain: ErrorDomain.STORAGE,
1028
- category: ErrorCategory.THIRD_PARTY
1029
- },
1030
- error
1031
- );
1032
- }
1033
- }
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;
1045
- try {
1046
- content = JSON.parse(row.content);
1047
- } catch {
1048
- }
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
1056
- };
1057
- if (row.type && row.type !== `v2`) result.type = row.type;
1058
- return result;
1059
- }
1060
- async _getIncludedMessages({
1061
- threadId,
1062
- selectBy
1063
- }) {
1064
- if (!threadId.trim()) throw new Error("threadId must be a non-empty string");
1065
- const include = selectBy?.include;
1066
- if (!include) return null;
1067
- const unionQueries = [];
1068
- const params = [];
1069
- for (const inc of include) {
1070
- const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
1071
- const searchId = inc.threadId || threadId;
1072
- unionQueries.push(
1073
- `
1074
- SELECT * FROM (
1075
- WITH numbered_messages AS (
1076
- SELECT
1077
- id, content, role, type, "createdAt", thread_id, "resourceId",
1078
- ROW_NUMBER() OVER (ORDER BY "createdAt" ASC) as row_num
1079
- FROM "${TABLE_MESSAGES}"
1080
- WHERE thread_id = ?
1081
- ),
1082
- target_positions AS (
1083
- SELECT row_num as target_pos
1084
- FROM numbered_messages
1085
- WHERE id = ?
1086
- )
1087
- SELECT DISTINCT m.*
1088
- FROM numbered_messages m
1089
- CROSS JOIN target_positions t
1090
- WHERE m.row_num BETWEEN (t.target_pos - ?) AND (t.target_pos + ?)
1091
- )
1092
- `
1093
- // Keep ASC for final sorting after fetching context
1094
- );
1095
- params.push(searchId, id, withPreviousMessages, withNextMessages);
1096
- }
1097
- const finalQuery = unionQueries.join(" UNION ALL ") + ' ORDER BY "createdAt" ASC';
1098
- const includedResult = await this.client.execute({ sql: finalQuery, args: params });
1099
- const includedRows = includedResult.rows?.map((row) => this.parseRow(row));
1100
- const seen = /* @__PURE__ */ new Set();
1101
- const dedupedRows = includedRows.filter((row) => {
1102
- if (seen.has(row.id)) return false;
1103
- seen.add(row.id);
1104
- return true;
1105
- });
1106
- return dedupedRows;
1107
- }
1108
- async getMessages({
1109
- threadId,
1110
- resourceId,
1111
- selectBy,
1112
- format
1113
- }) {
1114
- try {
1115
- if (!threadId.trim()) throw new Error("threadId must be a non-empty string");
1116
- const messages = [];
1117
- const limit = resolveMessageLimit({ last: selectBy?.last, defaultLimit: 40 });
1118
- if (selectBy?.include?.length) {
1119
- const includeMessages = await this._getIncludedMessages({ threadId, selectBy });
1120
- if (includeMessages) {
1121
- messages.push(...includeMessages);
1122
- }
1023
+ category: ErrorCategory.USER,
1024
+ details: { indexName },
1025
+ text: "Cannot delete with empty filter. Use deleteIndex to delete all vectors."
1026
+ });
1123
1027
  }
1124
- const excludeIds = messages.map((m) => m.id);
1125
- const remainingSql = `
1126
- SELECT
1127
- id,
1128
- content,
1129
- role,
1130
- type,
1131
- "createdAt",
1132
- thread_id,
1133
- "resourceId"
1134
- FROM "${TABLE_MESSAGES}"
1135
- WHERE thread_id = ?
1136
- ${excludeIds.length ? `AND id NOT IN (${excludeIds.map(() => "?").join(", ")})` : ""}
1137
- ORDER BY "createdAt" DESC
1138
- LIMIT ?
1139
- `;
1140
- const remainingArgs = [threadId, ...excludeIds.length ? excludeIds : [], limit];
1141
- const remainingResult = await this.client.execute({ sql: remainingSql, args: remainingArgs });
1142
- if (remainingResult.rows) {
1143
- messages.push(...remainingResult.rows.map((row) => this.parseRow(row)));
1028
+ const translatedFilter = this.transformFilter(filter);
1029
+ const { sql: filterSql, values: filterValues } = buildFilterQuery(translatedFilter);
1030
+ if (!filterSql || filterSql.trim() === "") {
1031
+ throw new MastraError({
1032
+ id: createVectorErrorId("LIBSQL", "DELETE_VECTORS", "INVALID_FILTER"),
1033
+ domain: ErrorDomain.STORAGE,
1034
+ category: ErrorCategory.USER,
1035
+ details: { indexName },
1036
+ text: "Filter produced empty WHERE clause"
1037
+ });
1144
1038
  }
1145
- messages.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
1146
- const list = new MessageList().add(messages, "memory");
1147
- if (format === `v2`) return list.get.all.v2();
1148
- return list.get.all.v1();
1039
+ const normalizedCondition = filterSql.replace(/^\s*WHERE\s+/i, "").trim().toLowerCase();
1040
+ const matchAllPatterns = ["true", "1 = 1", "1=1"];
1041
+ if (matchAllPatterns.includes(normalizedCondition)) {
1042
+ throw new MastraError({
1043
+ id: createVectorErrorId("LIBSQL", "DELETE_VECTORS", "MATCH_ALL_FILTER"),
1044
+ domain: ErrorDomain.STORAGE,
1045
+ category: ErrorCategory.USER,
1046
+ details: { indexName, filterSql: normalizedCondition },
1047
+ text: "Filter matches all vectors. Use deleteIndex to delete all vectors from an index."
1048
+ });
1049
+ }
1050
+ query = `DELETE FROM ${parsedIndexName} ${filterSql}`;
1051
+ values = filterValues;
1052
+ }
1053
+ try {
1054
+ await this.turso.execute({
1055
+ sql: query,
1056
+ args: values
1057
+ });
1149
1058
  } catch (error) {
1150
1059
  throw new MastraError(
1151
1060
  {
1152
- id: "LIBSQL_STORE_GET_MESSAGES_FAILED",
1061
+ id: createVectorErrorId("LIBSQL", "DELETE_VECTORS", "FAILED"),
1153
1062
  domain: ErrorDomain.STORAGE,
1154
1063
  category: ErrorCategory.THIRD_PARTY,
1155
- details: { threadId, resourceId: resourceId ?? "" }
1064
+ details: {
1065
+ indexName,
1066
+ ...filter && { filter: JSON.stringify(filter) },
1067
+ ...ids && { idsCount: ids.length }
1068
+ }
1156
1069
  },
1157
1070
  error
1158
1071
  );
1159
1072
  }
1160
1073
  }
1161
- async getMessagesById({
1162
- messageIds,
1163
- format
1164
- }) {
1165
- if (messageIds.length === 0) return [];
1074
+ truncateIndex(args) {
1166
1075
  try {
1167
- const sql = `
1168
- SELECT
1169
- id,
1170
- content,
1171
- role,
1172
- type,
1173
- "createdAt",
1174
- thread_id,
1175
- "resourceId"
1176
- FROM "${TABLE_MESSAGES}"
1177
- WHERE id IN (${messageIds.map(() => "?").join(", ")})
1178
- ORDER BY "createdAt" DESC
1179
- `;
1180
- const result = await this.client.execute({ sql, args: messageIds });
1181
- if (!result.rows) return [];
1182
- const list = new MessageList().add(result.rows.map(this.parseRow), "memory");
1183
- if (format === `v1`) return list.get.all.v1();
1184
- return list.get.all.v2();
1076
+ return this.executeWriteOperationWithRetry(() => this._doTruncateIndex(args));
1185
1077
  } catch (error) {
1186
1078
  throw new MastraError(
1187
1079
  {
1188
- id: "LIBSQL_STORE_GET_MESSAGES_BY_ID_FAILED",
1080
+ id: createVectorErrorId("LIBSQL", "TRUNCATE_INDEX", "FAILED"),
1189
1081
  domain: ErrorDomain.STORAGE,
1190
1082
  category: ErrorCategory.THIRD_PARTY,
1191
- details: { messageIds: JSON.stringify(messageIds) }
1083
+ details: { indexName: args.indexName }
1192
1084
  },
1193
1085
  error
1194
1086
  );
1195
1087
  }
1196
1088
  }
1197
- async getMessagesPaginated(args) {
1198
- const { threadId, format, selectBy } = args;
1199
- const { page = 0, perPage: perPageInput, dateRange } = selectBy?.pagination || {};
1200
- const perPage = perPageInput !== void 0 ? perPageInput : resolveMessageLimit({ last: selectBy?.last, defaultLimit: 40 });
1201
- const fromDate = dateRange?.start;
1202
- const toDate = dateRange?.end;
1203
- const messages = [];
1204
- if (selectBy?.include?.length) {
1089
+ async _doTruncateIndex({ indexName }) {
1090
+ await this.turso.execute({
1091
+ sql: `DELETE FROM ${parseSqlIdentifier(indexName, "index name")}`,
1092
+ args: []
1093
+ });
1094
+ }
1095
+ };
1096
+ function isLockError(error) {
1097
+ return error.code === "SQLITE_BUSY" || error.code === "SQLITE_LOCKED" || error.message?.toLowerCase().includes("database is locked") || error.message?.toLowerCase().includes("database table is locked") || error.message?.toLowerCase().includes("table is locked") || error.constructor.name === "SqliteError" && error.message?.toLowerCase().includes("locked");
1098
+ }
1099
+ function createExecuteWriteOperationWithRetry({
1100
+ logger,
1101
+ maxRetries,
1102
+ initialBackoffMs
1103
+ }) {
1104
+ return async function executeWriteOperationWithRetry(operationFn, operationDescription) {
1105
+ let attempts = 0;
1106
+ let backoff = initialBackoffMs;
1107
+ while (attempts < maxRetries) {
1205
1108
  try {
1206
- const includeMessages = await this._getIncludedMessages({ threadId, selectBy });
1207
- if (includeMessages) {
1208
- messages.push(...includeMessages);
1109
+ return await operationFn();
1110
+ } catch (error) {
1111
+ logger.debug(`LibSQLStore: Error caught in retry loop for ${operationDescription}`, {
1112
+ errorType: error.constructor.name,
1113
+ errorCode: error.code,
1114
+ errorMessage: error.message,
1115
+ attempts,
1116
+ maxRetries
1117
+ });
1118
+ if (isLockError(error)) {
1119
+ attempts++;
1120
+ if (attempts >= maxRetries) {
1121
+ logger.error(
1122
+ `LibSQLStore: Operation failed after ${maxRetries} attempts due to database lock: ${error.message}`,
1123
+ { error, attempts, maxRetries }
1124
+ );
1125
+ throw error;
1126
+ }
1127
+ logger.warn(
1128
+ `LibSQLStore: Attempt ${attempts} failed due to database lock during ${operationDescription}. Retrying in ${backoff}ms...`,
1129
+ { errorMessage: error.message, attempts, backoff, maxRetries }
1130
+ );
1131
+ await new Promise((resolve) => setTimeout(resolve, backoff));
1132
+ backoff *= 2;
1133
+ } else {
1134
+ logger.error(`LibSQLStore: Non-lock error during ${operationDescription}, not retrying`, { error });
1135
+ throw error;
1136
+ }
1137
+ }
1138
+ }
1139
+ throw new Error(`LibSQLStore: Unexpected exit from retry loop for ${operationDescription}`);
1140
+ };
1141
+ }
1142
+ function prepareStatement({ tableName, record }) {
1143
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
1144
+ const columns = Object.keys(record).map((col) => parseSqlIdentifier(col, "column name"));
1145
+ const values = Object.values(record).map((v) => {
1146
+ if (typeof v === `undefined` || v === null) {
1147
+ return null;
1148
+ }
1149
+ if (v instanceof Date) {
1150
+ return v.toISOString();
1151
+ }
1152
+ return typeof v === "object" ? JSON.stringify(v) : v;
1153
+ });
1154
+ const placeholders = values.map(() => "?").join(", ");
1155
+ return {
1156
+ sql: `INSERT OR REPLACE INTO ${parsedTableName} (${columns.join(", ")}) VALUES (${placeholders})`,
1157
+ args: values
1158
+ };
1159
+ }
1160
+ function prepareUpdateStatement({
1161
+ tableName,
1162
+ updates,
1163
+ keys
1164
+ }) {
1165
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
1166
+ const schema = TABLE_SCHEMAS[tableName];
1167
+ const updateColumns = Object.keys(updates).map((col) => parseSqlIdentifier(col, "column name"));
1168
+ const updateValues = Object.values(updates).map(transformToSqlValue);
1169
+ const setClause = updateColumns.map((col) => `${col} = ?`).join(", ");
1170
+ const whereClause = prepareWhereClause(keys, schema);
1171
+ return {
1172
+ sql: `UPDATE ${parsedTableName} SET ${setClause}${whereClause.sql}`,
1173
+ args: [...updateValues, ...whereClause.args]
1174
+ };
1175
+ }
1176
+ function transformToSqlValue(value) {
1177
+ if (typeof value === "undefined" || value === null) {
1178
+ return null;
1179
+ }
1180
+ if (value instanceof Date) {
1181
+ return value.toISOString();
1182
+ }
1183
+ return typeof value === "object" ? JSON.stringify(value) : value;
1184
+ }
1185
+ function prepareDeleteStatement({ tableName, keys }) {
1186
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
1187
+ const whereClause = prepareWhereClause(keys, TABLE_SCHEMAS[tableName]);
1188
+ return {
1189
+ sql: `DELETE FROM ${parsedTableName}${whereClause.sql}`,
1190
+ args: whereClause.args
1191
+ };
1192
+ }
1193
+ function prepareWhereClause(filters, schema) {
1194
+ const conditions = [];
1195
+ const args = [];
1196
+ for (const [columnName, filterValue] of Object.entries(filters)) {
1197
+ const column = schema[columnName];
1198
+ if (!column) {
1199
+ throw new Error(`Unknown column: ${columnName}`);
1200
+ }
1201
+ const parsedColumn = parseSqlIdentifier(columnName, "column name");
1202
+ const result = buildCondition2(parsedColumn, filterValue);
1203
+ conditions.push(result.condition);
1204
+ args.push(...result.args);
1205
+ }
1206
+ return {
1207
+ sql: conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "",
1208
+ args
1209
+ };
1210
+ }
1211
+ function buildCondition2(columnName, filterValue) {
1212
+ if (filterValue === null) {
1213
+ return { condition: `${columnName} IS NULL`, args: [] };
1214
+ }
1215
+ if (typeof filterValue === "object" && filterValue !== null && ("startAt" in filterValue || "endAt" in filterValue)) {
1216
+ return buildDateRangeCondition(columnName, filterValue);
1217
+ }
1218
+ return {
1219
+ condition: `${columnName} = ?`,
1220
+ args: [transformToSqlValue(filterValue)]
1221
+ };
1222
+ }
1223
+ function buildDateRangeCondition(columnName, range) {
1224
+ const conditions = [];
1225
+ const args = [];
1226
+ if (range.startAt !== void 0) {
1227
+ conditions.push(`${columnName} >= ?`);
1228
+ args.push(transformToSqlValue(range.startAt));
1229
+ }
1230
+ if (range.endAt !== void 0) {
1231
+ conditions.push(`${columnName} <= ?`);
1232
+ args.push(transformToSqlValue(range.endAt));
1233
+ }
1234
+ if (conditions.length === 0) {
1235
+ throw new Error("Date range must specify at least startAt or endAt");
1236
+ }
1237
+ return {
1238
+ condition: conditions.join(" AND "),
1239
+ args
1240
+ };
1241
+ }
1242
+ function buildDateRangeFilter(dateRange, columnName = "createdAt") {
1243
+ if (!dateRange?.start && !dateRange?.end) {
1244
+ return {};
1245
+ }
1246
+ const filter = {};
1247
+ if (dateRange.start) {
1248
+ filter.startAt = new Date(dateRange.start).toISOString();
1249
+ }
1250
+ if (dateRange.end) {
1251
+ filter.endAt = new Date(dateRange.end).toISOString();
1252
+ }
1253
+ return { [columnName]: filter };
1254
+ }
1255
+ function transformFromSqlRow({
1256
+ tableName,
1257
+ sqlRow
1258
+ }) {
1259
+ const result = {};
1260
+ const jsonColumns = new Set(
1261
+ Object.keys(TABLE_SCHEMAS[tableName]).filter((key) => TABLE_SCHEMAS[tableName][key].type === "jsonb").map((key) => key)
1262
+ );
1263
+ const dateColumns = new Set(
1264
+ Object.keys(TABLE_SCHEMAS[tableName]).filter((key) => TABLE_SCHEMAS[tableName][key].type === "timestamp").map((key) => key)
1265
+ );
1266
+ for (const [key, value] of Object.entries(sqlRow)) {
1267
+ if (value === null || value === void 0) {
1268
+ result[key] = value;
1269
+ continue;
1270
+ }
1271
+ if (dateColumns.has(key) && typeof value === "string") {
1272
+ result[key] = new Date(value);
1273
+ continue;
1274
+ }
1275
+ if (jsonColumns.has(key) && typeof value === "string") {
1276
+ result[key] = safelyParseJSON(value);
1277
+ continue;
1278
+ }
1279
+ result[key] = value;
1280
+ }
1281
+ return result;
1282
+ }
1283
+
1284
+ // src/storage/db/index.ts
1285
+ function resolveClient(config) {
1286
+ if ("client" in config) {
1287
+ return config.client;
1288
+ }
1289
+ return createClient({
1290
+ url: config.url,
1291
+ ...config.authToken ? { authToken: config.authToken } : {}
1292
+ });
1293
+ }
1294
+ var LibSQLDB = class extends MastraBase {
1295
+ client;
1296
+ maxRetries;
1297
+ initialBackoffMs;
1298
+ executeWriteOperationWithRetry;
1299
+ constructor({
1300
+ client,
1301
+ maxRetries,
1302
+ initialBackoffMs
1303
+ }) {
1304
+ super({
1305
+ component: "STORAGE",
1306
+ name: "LIBSQL_DB_LAYER"
1307
+ });
1308
+ this.client = client;
1309
+ this.maxRetries = maxRetries ?? 5;
1310
+ this.initialBackoffMs = initialBackoffMs ?? 100;
1311
+ this.executeWriteOperationWithRetry = createExecuteWriteOperationWithRetry({
1312
+ logger: this.logger,
1313
+ maxRetries: this.maxRetries,
1314
+ initialBackoffMs: this.initialBackoffMs
1315
+ });
1316
+ }
1317
+ /**
1318
+ * Checks if a column exists in the specified table.
1319
+ *
1320
+ * @param table - The name of the table to check
1321
+ * @param column - The name of the column to look for
1322
+ * @returns `true` if the column exists in the table, `false` otherwise
1323
+ */
1324
+ async hasColumn(table, column) {
1325
+ const sanitizedTable = parseSqlIdentifier(table, "table name");
1326
+ const result = await this.client.execute({
1327
+ sql: `PRAGMA table_info("${sanitizedTable}")`
1328
+ });
1329
+ return result.rows?.some((row) => row.name === column);
1330
+ }
1331
+ /**
1332
+ * Internal insert implementation without retry logic.
1333
+ */
1334
+ async doInsert({
1335
+ tableName,
1336
+ record
1337
+ }) {
1338
+ await this.client.execute(
1339
+ prepareStatement({
1340
+ tableName,
1341
+ record
1342
+ })
1343
+ );
1344
+ }
1345
+ /**
1346
+ * Inserts or replaces a record in the specified table with automatic retry on lock errors.
1347
+ *
1348
+ * @param args - The insert arguments
1349
+ * @param args.tableName - The name of the table to insert into
1350
+ * @param args.record - The record to insert (key-value pairs)
1351
+ */
1352
+ insert(args) {
1353
+ return this.executeWriteOperationWithRetry(() => this.doInsert(args), `insert into table ${args.tableName}`);
1354
+ }
1355
+ /**
1356
+ * Internal update implementation without retry logic.
1357
+ */
1358
+ async doUpdate({
1359
+ tableName,
1360
+ keys,
1361
+ data
1362
+ }) {
1363
+ await this.client.execute(prepareUpdateStatement({ tableName, updates: data, keys }));
1364
+ }
1365
+ /**
1366
+ * Updates a record in the specified table with automatic retry on lock errors.
1367
+ *
1368
+ * @param args - The update arguments
1369
+ * @param args.tableName - The name of the table to update
1370
+ * @param args.keys - The key(s) identifying the record to update
1371
+ * @param args.data - The fields to update (key-value pairs)
1372
+ */
1373
+ update(args) {
1374
+ return this.executeWriteOperationWithRetry(() => this.doUpdate(args), `update table ${args.tableName}`);
1375
+ }
1376
+ /**
1377
+ * Internal batch insert implementation without retry logic.
1378
+ */
1379
+ async doBatchInsert({
1380
+ tableName,
1381
+ records
1382
+ }) {
1383
+ if (records.length === 0) return;
1384
+ const batchStatements = records.map((r) => prepareStatement({ tableName, record: r }));
1385
+ await this.client.batch(batchStatements, "write");
1386
+ }
1387
+ /**
1388
+ * Inserts multiple records in a single batch transaction with automatic retry on lock errors.
1389
+ *
1390
+ * @param args - The batch insert arguments
1391
+ * @param args.tableName - The name of the table to insert into
1392
+ * @param args.records - Array of records to insert
1393
+ * @throws {MastraError} When the batch insert fails after retries
1394
+ */
1395
+ async batchInsert(args) {
1396
+ return this.executeWriteOperationWithRetry(
1397
+ () => this.doBatchInsert(args),
1398
+ `batch insert into table ${args.tableName}`
1399
+ ).catch((error) => {
1400
+ throw new MastraError(
1401
+ {
1402
+ id: createStorageErrorId("LIBSQL", "BATCH_INSERT", "FAILED"),
1403
+ domain: ErrorDomain.STORAGE,
1404
+ category: ErrorCategory.THIRD_PARTY,
1405
+ details: {
1406
+ tableName: args.tableName
1407
+ }
1408
+ },
1409
+ error
1410
+ );
1411
+ });
1412
+ }
1413
+ /**
1414
+ * Internal batch update implementation without retry logic.
1415
+ * Each record can be updated based on single or composite keys.
1416
+ */
1417
+ async doBatchUpdate({
1418
+ tableName,
1419
+ updates
1420
+ }) {
1421
+ if (updates.length === 0) return;
1422
+ const batchStatements = updates.map(
1423
+ ({ keys, data }) => prepareUpdateStatement({
1424
+ tableName,
1425
+ updates: data,
1426
+ keys
1427
+ })
1428
+ );
1429
+ await this.client.batch(batchStatements, "write");
1430
+ }
1431
+ /**
1432
+ * Updates multiple records in a single batch transaction with automatic retry on lock errors.
1433
+ * Each record can be updated based on single or composite keys.
1434
+ *
1435
+ * @param args - The batch update arguments
1436
+ * @param args.tableName - The name of the table to update
1437
+ * @param args.updates - Array of update operations, each containing keys and data
1438
+ * @throws {MastraError} When the batch update fails after retries
1439
+ */
1440
+ async batchUpdate(args) {
1441
+ return this.executeWriteOperationWithRetry(
1442
+ () => this.doBatchUpdate(args),
1443
+ `batch update in table ${args.tableName}`
1444
+ ).catch((error) => {
1445
+ throw new MastraError(
1446
+ {
1447
+ id: createStorageErrorId("LIBSQL", "BATCH_UPDATE", "FAILED"),
1448
+ domain: ErrorDomain.STORAGE,
1449
+ category: ErrorCategory.THIRD_PARTY,
1450
+ details: {
1451
+ tableName: args.tableName
1452
+ }
1453
+ },
1454
+ error
1455
+ );
1456
+ });
1457
+ }
1458
+ /**
1459
+ * Internal batch delete implementation without retry logic.
1460
+ * Each record can be deleted based on single or composite keys.
1461
+ */
1462
+ async doBatchDelete({
1463
+ tableName,
1464
+ keys
1465
+ }) {
1466
+ if (keys.length === 0) return;
1467
+ const batchStatements = keys.map(
1468
+ (keyObj) => prepareDeleteStatement({
1469
+ tableName,
1470
+ keys: keyObj
1471
+ })
1472
+ );
1473
+ await this.client.batch(batchStatements, "write");
1474
+ }
1475
+ /**
1476
+ * Deletes multiple records in a single batch transaction with automatic retry on lock errors.
1477
+ * Each record can be deleted based on single or composite keys.
1478
+ *
1479
+ * @param args - The batch delete arguments
1480
+ * @param args.tableName - The name of the table to delete from
1481
+ * @param args.keys - Array of key objects identifying records to delete
1482
+ * @throws {MastraError} When the batch delete fails after retries
1483
+ */
1484
+ async batchDelete({
1485
+ tableName,
1486
+ keys
1487
+ }) {
1488
+ return this.executeWriteOperationWithRetry(
1489
+ () => this.doBatchDelete({ tableName, keys }),
1490
+ `batch delete from table ${tableName}`
1491
+ ).catch((error) => {
1492
+ throw new MastraError(
1493
+ {
1494
+ id: createStorageErrorId("LIBSQL", "BATCH_DELETE", "FAILED"),
1495
+ domain: ErrorDomain.STORAGE,
1496
+ category: ErrorCategory.THIRD_PARTY,
1497
+ details: {
1498
+ tableName
1499
+ }
1500
+ },
1501
+ error
1502
+ );
1503
+ });
1504
+ }
1505
+ /**
1506
+ * Internal single-record delete implementation without retry logic.
1507
+ */
1508
+ async doDelete({ tableName, keys }) {
1509
+ await this.client.execute(prepareDeleteStatement({ tableName, keys }));
1510
+ }
1511
+ /**
1512
+ * Deletes a single record from the specified table with automatic retry on lock errors.
1513
+ *
1514
+ * @param args - The delete arguments
1515
+ * @param args.tableName - The name of the table to delete from
1516
+ * @param args.keys - The key(s) identifying the record to delete
1517
+ * @throws {MastraError} When the delete fails after retries
1518
+ */
1519
+ async delete(args) {
1520
+ return this.executeWriteOperationWithRetry(() => this.doDelete(args), `delete from table ${args.tableName}`).catch(
1521
+ (error) => {
1522
+ throw new MastraError(
1523
+ {
1524
+ id: createStorageErrorId("LIBSQL", "DELETE", "FAILED"),
1525
+ domain: ErrorDomain.STORAGE,
1526
+ category: ErrorCategory.THIRD_PARTY,
1527
+ details: {
1528
+ tableName: args.tableName
1529
+ }
1530
+ },
1531
+ error
1532
+ );
1533
+ }
1534
+ );
1535
+ }
1536
+ /**
1537
+ * Selects a single record from the specified table by key(s).
1538
+ * Returns the most recently created record if multiple matches exist.
1539
+ * Automatically parses JSON string values back to objects/arrays.
1540
+ *
1541
+ * @typeParam R - The expected return type of the record
1542
+ * @param args - The select arguments
1543
+ * @param args.tableName - The name of the table to select from
1544
+ * @param args.keys - The key(s) identifying the record to select
1545
+ * @returns The matching record or `null` if not found
1546
+ */
1547
+ async select({ tableName, keys }) {
1548
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
1549
+ const parsedKeys = Object.keys(keys).map((key) => parseSqlIdentifier(key, "column name"));
1550
+ const conditions = parsedKeys.map((key) => `${key} = ?`).join(" AND ");
1551
+ const values = Object.values(keys);
1552
+ const result = await this.client.execute({
1553
+ sql: `SELECT * FROM ${parsedTableName} WHERE ${conditions} ORDER BY createdAt DESC LIMIT 1`,
1554
+ args: values
1555
+ });
1556
+ if (!result.rows || result.rows.length === 0) {
1557
+ return null;
1558
+ }
1559
+ const row = result.rows[0];
1560
+ const parsed = Object.fromEntries(
1561
+ Object.entries(row || {}).map(([k, v]) => {
1562
+ try {
1563
+ return [k, typeof v === "string" ? v.startsWith("{") || v.startsWith("[") ? JSON.parse(v) : v : v];
1564
+ } catch {
1565
+ return [k, v];
1566
+ }
1567
+ })
1568
+ );
1569
+ return parsed;
1570
+ }
1571
+ /**
1572
+ * Selects multiple records from the specified table with optional filtering, ordering, and pagination.
1573
+ *
1574
+ * @typeParam R - The expected return type of each record
1575
+ * @param args - The select arguments
1576
+ * @param args.tableName - The name of the table to select from
1577
+ * @param args.whereClause - Optional WHERE clause with SQL string and arguments
1578
+ * @param args.orderBy - Optional ORDER BY clause (e.g., "createdAt DESC")
1579
+ * @param args.offset - Optional offset for pagination
1580
+ * @param args.limit - Optional limit for pagination
1581
+ * @param args.args - Optional additional query arguments
1582
+ * @returns Array of matching records
1583
+ */
1584
+ async selectMany({
1585
+ tableName,
1586
+ whereClause,
1587
+ orderBy,
1588
+ offset,
1589
+ limit,
1590
+ args
1591
+ }) {
1592
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
1593
+ let statement = `SELECT * FROM ${parsedTableName}`;
1594
+ if (whereClause?.sql) {
1595
+ statement += `${whereClause.sql}`;
1596
+ }
1597
+ if (orderBy) {
1598
+ statement += ` ORDER BY ${orderBy}`;
1599
+ }
1600
+ if (limit) {
1601
+ statement += ` LIMIT ${limit}`;
1602
+ }
1603
+ if (offset) {
1604
+ statement += ` OFFSET ${offset}`;
1605
+ }
1606
+ const result = await this.client.execute({
1607
+ sql: statement,
1608
+ args: [...whereClause?.args ?? [], ...args ?? []]
1609
+ });
1610
+ return result.rows;
1611
+ }
1612
+ /**
1613
+ * Returns the total count of records matching the optional WHERE clause.
1614
+ *
1615
+ * @param args - The count arguments
1616
+ * @param args.tableName - The name of the table to count from
1617
+ * @param args.whereClause - Optional WHERE clause with SQL string and arguments
1618
+ * @returns The total count of matching records
1619
+ */
1620
+ async selectTotalCount({
1621
+ tableName,
1622
+ whereClause
1623
+ }) {
1624
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
1625
+ const statement = `SELECT COUNT(*) as count FROM ${parsedTableName} ${whereClause ? `${whereClause.sql}` : ""}`;
1626
+ const result = await this.client.execute({
1627
+ sql: statement,
1628
+ args: whereClause?.args ?? []
1629
+ });
1630
+ if (!result.rows || result.rows.length === 0) {
1631
+ return 0;
1632
+ }
1633
+ return result.rows[0]?.count ?? 0;
1634
+ }
1635
+ /**
1636
+ * Maps a storage column type to its SQLite equivalent.
1637
+ */
1638
+ getSqlType(type) {
1639
+ switch (type) {
1640
+ case "bigint":
1641
+ return "INTEGER";
1642
+ // SQLite uses INTEGER for all integer sizes
1643
+ case "timestamp":
1644
+ return "TEXT";
1645
+ // Store timestamps as ISO strings in SQLite
1646
+ case "float":
1647
+ return "REAL";
1648
+ // SQLite's floating point type
1649
+ case "boolean":
1650
+ return "INTEGER";
1651
+ // SQLite uses 0/1 for booleans
1652
+ case "jsonb":
1653
+ return "TEXT";
1654
+ // Store JSON as TEXT in SQLite
1655
+ default:
1656
+ return getSqlType(type);
1657
+ }
1658
+ }
1659
+ /**
1660
+ * Creates a table if it doesn't exist based on the provided schema.
1661
+ *
1662
+ * @param args - The create table arguments
1663
+ * @param args.tableName - The name of the table to create
1664
+ * @param args.schema - The schema definition for the table columns
1665
+ */
1666
+ async createTable({
1667
+ tableName,
1668
+ schema
1669
+ }) {
1670
+ try {
1671
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
1672
+ const columnDefinitions = Object.entries(schema).map(([colName, colDef]) => {
1673
+ const type = this.getSqlType(colDef.type);
1674
+ const nullable = colDef.nullable === false ? "NOT NULL" : "";
1675
+ const primaryKey = colDef.primaryKey ? "PRIMARY KEY" : "";
1676
+ return `"${colName}" ${type} ${nullable} ${primaryKey}`.trim();
1677
+ });
1678
+ const tableConstraints = [];
1679
+ if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
1680
+ tableConstraints.push("UNIQUE (workflow_name, run_id)");
1681
+ }
1682
+ const allDefinitions = [...columnDefinitions, ...tableConstraints].join(",\n ");
1683
+ const sql = `CREATE TABLE IF NOT EXISTS ${parsedTableName} (
1684
+ ${allDefinitions}
1685
+ )`;
1686
+ await this.client.execute(sql);
1687
+ this.logger.debug(`LibSQLDB: Created table ${tableName}`);
1688
+ } catch (error) {
1689
+ throw new MastraError(
1690
+ {
1691
+ id: createStorageErrorId("LIBSQL", "CREATE_TABLE", "FAILED"),
1692
+ domain: ErrorDomain.STORAGE,
1693
+ category: ErrorCategory.THIRD_PARTY,
1694
+ details: { tableName }
1695
+ },
1696
+ error
1697
+ );
1698
+ }
1699
+ }
1700
+ /**
1701
+ * Gets a default value for a column type (used when adding NOT NULL columns).
1702
+ */
1703
+ getDefaultValue(type) {
1704
+ switch (type) {
1705
+ case "text":
1706
+ case "uuid":
1707
+ return "DEFAULT ''";
1708
+ case "integer":
1709
+ case "bigint":
1710
+ case "float":
1711
+ return "DEFAULT 0";
1712
+ case "boolean":
1713
+ return "DEFAULT 0";
1714
+ case "jsonb":
1715
+ return "DEFAULT '{}'";
1716
+ case "timestamp":
1717
+ return "DEFAULT CURRENT_TIMESTAMP";
1718
+ default:
1719
+ return "DEFAULT ''";
1720
+ }
1721
+ }
1722
+ /**
1723
+ * Alters an existing table to add missing columns.
1724
+ * Used for schema migrations when new columns are added.
1725
+ *
1726
+ * @param args - The alter table arguments
1727
+ * @param args.tableName - The name of the table to alter
1728
+ * @param args.schema - The full schema definition for the table
1729
+ * @param args.ifNotExists - Array of column names to add if they don't exist
1730
+ */
1731
+ async alterTable({
1732
+ tableName,
1733
+ schema,
1734
+ ifNotExists
1735
+ }) {
1736
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
1737
+ try {
1738
+ const tableInfo = await this.client.execute({
1739
+ sql: `PRAGMA table_info("${parsedTableName}")`
1740
+ });
1741
+ const existingColumns = new Set((tableInfo.rows || []).map((row) => row.name?.toLowerCase()));
1742
+ for (const columnName of ifNotExists) {
1743
+ if (!existingColumns.has(columnName.toLowerCase()) && schema[columnName]) {
1744
+ const columnDef = schema[columnName];
1745
+ const sqlType = this.getSqlType(columnDef.type);
1746
+ const defaultValue = this.getDefaultValue(columnDef.type);
1747
+ const alterSql = `ALTER TABLE ${parsedTableName} ADD COLUMN "${columnName}" ${sqlType} ${defaultValue}`;
1748
+ await this.client.execute(alterSql);
1749
+ this.logger.debug(`LibSQLDB: Added column ${columnName} to table ${tableName}`);
1750
+ }
1751
+ }
1752
+ } catch (error) {
1753
+ throw new MastraError(
1754
+ {
1755
+ id: createStorageErrorId("LIBSQL", "ALTER_TABLE", "FAILED"),
1756
+ domain: ErrorDomain.STORAGE,
1757
+ category: ErrorCategory.THIRD_PARTY,
1758
+ details: { tableName }
1759
+ },
1760
+ error
1761
+ );
1762
+ }
1763
+ }
1764
+ /**
1765
+ * Deletes all records from the specified table.
1766
+ * Errors are logged but not thrown.
1767
+ *
1768
+ * @param args - The delete arguments
1769
+ * @param args.tableName - The name of the table to clear
1770
+ */
1771
+ async deleteData({ tableName }) {
1772
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
1773
+ try {
1774
+ await this.client.execute(`DELETE FROM ${parsedTableName}`);
1775
+ } catch (e) {
1776
+ const mastraError = new MastraError(
1777
+ {
1778
+ id: createStorageErrorId("LIBSQL", "CLEAR_TABLE", "FAILED"),
1779
+ domain: ErrorDomain.STORAGE,
1780
+ category: ErrorCategory.THIRD_PARTY,
1781
+ details: {
1782
+ tableName
1783
+ }
1784
+ },
1785
+ e
1786
+ );
1787
+ this.logger?.trackException?.(mastraError);
1788
+ this.logger?.error?.(mastraError.toString());
1789
+ }
1790
+ }
1791
+ };
1792
+
1793
+ // src/storage/domains/agents/index.ts
1794
+ var AgentsLibSQL = class extends AgentsStorage {
1795
+ #db;
1796
+ constructor(config) {
1797
+ super();
1798
+ const client = resolveClient(config);
1799
+ this.#db = new LibSQLDB({ client, maxRetries: config.maxRetries, initialBackoffMs: config.initialBackoffMs });
1800
+ }
1801
+ async init() {
1802
+ await this.#db.createTable({ tableName: TABLE_AGENTS, schema: AGENTS_SCHEMA });
1803
+ }
1804
+ async dangerouslyClearAll() {
1805
+ await this.#db.deleteData({ tableName: TABLE_AGENTS });
1806
+ }
1807
+ parseJson(value, fieldName) {
1808
+ if (!value) return void 0;
1809
+ if (typeof value !== "string") return value;
1810
+ try {
1811
+ return JSON.parse(value);
1812
+ } catch (error) {
1813
+ const details = {
1814
+ value: value.length > 100 ? value.substring(0, 100) + "..." : value
1815
+ };
1816
+ if (fieldName) {
1817
+ details.field = fieldName;
1818
+ }
1819
+ throw new MastraError(
1820
+ {
1821
+ id: createStorageErrorId("LIBSQL", "PARSE_JSON", "INVALID_JSON"),
1822
+ domain: ErrorDomain.STORAGE,
1823
+ category: ErrorCategory.SYSTEM,
1824
+ text: `Failed to parse JSON${fieldName ? ` for field "${fieldName}"` : ""}: ${error instanceof Error ? error.message : "Unknown error"}`,
1825
+ details
1826
+ },
1827
+ error
1828
+ );
1829
+ }
1830
+ }
1831
+ parseRow(row) {
1832
+ return {
1833
+ id: row.id,
1834
+ name: row.name,
1835
+ description: row.description,
1836
+ instructions: row.instructions,
1837
+ model: this.parseJson(row.model, "model"),
1838
+ tools: this.parseJson(row.tools, "tools"),
1839
+ defaultOptions: this.parseJson(row.defaultOptions, "defaultOptions"),
1840
+ workflows: this.parseJson(row.workflows, "workflows"),
1841
+ agents: this.parseJson(row.agents, "agents"),
1842
+ inputProcessors: this.parseJson(row.inputProcessors, "inputProcessors"),
1843
+ outputProcessors: this.parseJson(row.outputProcessors, "outputProcessors"),
1844
+ memory: this.parseJson(row.memory, "memory"),
1845
+ scorers: this.parseJson(row.scorers, "scorers"),
1846
+ metadata: this.parseJson(row.metadata, "metadata"),
1847
+ createdAt: new Date(row.createdAt),
1848
+ updatedAt: new Date(row.updatedAt)
1849
+ };
1850
+ }
1851
+ async getAgentById({ id }) {
1852
+ try {
1853
+ const result = await this.#db.select({
1854
+ tableName: TABLE_AGENTS,
1855
+ keys: { id }
1856
+ });
1857
+ return result ? this.parseRow(result) : null;
1858
+ } catch (error) {
1859
+ throw new MastraError(
1860
+ {
1861
+ id: createStorageErrorId("LIBSQL", "GET_AGENT_BY_ID", "FAILED"),
1862
+ domain: ErrorDomain.STORAGE,
1863
+ category: ErrorCategory.THIRD_PARTY,
1864
+ details: { agentId: id }
1865
+ },
1866
+ error
1867
+ );
1868
+ }
1869
+ }
1870
+ async createAgent({ agent }) {
1871
+ try {
1872
+ const now = /* @__PURE__ */ new Date();
1873
+ await this.#db.insert({
1874
+ tableName: TABLE_AGENTS,
1875
+ record: {
1876
+ id: agent.id,
1877
+ name: agent.name,
1878
+ description: agent.description ?? null,
1879
+ instructions: agent.instructions,
1880
+ model: agent.model,
1881
+ tools: agent.tools ?? null,
1882
+ defaultOptions: agent.defaultOptions ?? null,
1883
+ workflows: agent.workflows ?? null,
1884
+ agents: agent.agents ?? null,
1885
+ inputProcessors: agent.inputProcessors ?? null,
1886
+ outputProcessors: agent.outputProcessors ?? null,
1887
+ memory: agent.memory ?? null,
1888
+ scorers: agent.scorers ?? null,
1889
+ metadata: agent.metadata ?? null,
1890
+ createdAt: now,
1891
+ updatedAt: now
1209
1892
  }
1210
- } catch (error) {
1211
- throw new MastraError(
1212
- {
1213
- id: "LIBSQL_STORE_GET_MESSAGES_PAGINATED_GET_INCLUDE_MESSAGES_FAILED",
1214
- domain: ErrorDomain.STORAGE,
1215
- category: ErrorCategory.THIRD_PARTY,
1216
- details: { threadId }
1217
- },
1218
- error
1219
- );
1893
+ });
1894
+ return {
1895
+ ...agent,
1896
+ createdAt: now,
1897
+ updatedAt: now
1898
+ };
1899
+ } catch (error) {
1900
+ throw new MastraError(
1901
+ {
1902
+ id: createStorageErrorId("LIBSQL", "CREATE_AGENT", "FAILED"),
1903
+ domain: ErrorDomain.STORAGE,
1904
+ category: ErrorCategory.THIRD_PARTY,
1905
+ details: { agentId: agent.id }
1906
+ },
1907
+ error
1908
+ );
1909
+ }
1910
+ }
1911
+ async updateAgent({ id, ...updates }) {
1912
+ try {
1913
+ const existingAgent = await this.getAgentById({ id });
1914
+ if (!existingAgent) {
1915
+ throw new MastraError({
1916
+ id: createStorageErrorId("LIBSQL", "UPDATE_AGENT", "NOT_FOUND"),
1917
+ domain: ErrorDomain.STORAGE,
1918
+ category: ErrorCategory.USER,
1919
+ text: `Agent ${id} not found`,
1920
+ details: { agentId: id }
1921
+ });
1922
+ }
1923
+ const data = {
1924
+ updatedAt: /* @__PURE__ */ new Date()
1925
+ };
1926
+ if (updates.name !== void 0) data.name = updates.name;
1927
+ if (updates.description !== void 0) data.description = updates.description;
1928
+ if (updates.instructions !== void 0) data.instructions = updates.instructions;
1929
+ if (updates.model !== void 0) data.model = updates.model;
1930
+ if (updates.tools !== void 0) data.tools = updates.tools;
1931
+ if (updates.defaultOptions !== void 0) data.defaultOptions = updates.defaultOptions;
1932
+ if (updates.workflows !== void 0) data.workflows = updates.workflows;
1933
+ if (updates.agents !== void 0) data.agents = updates.agents;
1934
+ if (updates.inputProcessors !== void 0) data.inputProcessors = updates.inputProcessors;
1935
+ if (updates.outputProcessors !== void 0) data.outputProcessors = updates.outputProcessors;
1936
+ if (updates.memory !== void 0) data.memory = updates.memory;
1937
+ if (updates.scorers !== void 0) data.scorers = updates.scorers;
1938
+ if (updates.metadata !== void 0) {
1939
+ data.metadata = { ...existingAgent.metadata, ...updates.metadata };
1940
+ }
1941
+ if (Object.keys(data).length > 1) {
1942
+ await this.#db.update({
1943
+ tableName: TABLE_AGENTS,
1944
+ keys: { id },
1945
+ data
1946
+ });
1947
+ }
1948
+ const updatedAgent = await this.getAgentById({ id });
1949
+ if (!updatedAgent) {
1950
+ throw new MastraError({
1951
+ id: createStorageErrorId("LIBSQL", "UPDATE_AGENT", "NOT_FOUND_AFTER_UPDATE"),
1952
+ domain: ErrorDomain.STORAGE,
1953
+ category: ErrorCategory.SYSTEM,
1954
+ text: `Agent ${id} not found after update`,
1955
+ details: { agentId: id }
1956
+ });
1957
+ }
1958
+ return updatedAgent;
1959
+ } catch (error) {
1960
+ if (error instanceof MastraError) {
1961
+ throw error;
1962
+ }
1963
+ throw new MastraError(
1964
+ {
1965
+ id: createStorageErrorId("LIBSQL", "UPDATE_AGENT", "FAILED"),
1966
+ domain: ErrorDomain.STORAGE,
1967
+ category: ErrorCategory.THIRD_PARTY,
1968
+ details: { agentId: id }
1969
+ },
1970
+ error
1971
+ );
1972
+ }
1973
+ }
1974
+ async deleteAgent({ id }) {
1975
+ try {
1976
+ await this.#db.delete({
1977
+ tableName: TABLE_AGENTS,
1978
+ keys: { id }
1979
+ });
1980
+ } catch (error) {
1981
+ throw new MastraError(
1982
+ {
1983
+ id: createStorageErrorId("LIBSQL", "DELETE_AGENT", "FAILED"),
1984
+ domain: ErrorDomain.STORAGE,
1985
+ category: ErrorCategory.THIRD_PARTY,
1986
+ details: { agentId: id }
1987
+ },
1988
+ error
1989
+ );
1990
+ }
1991
+ }
1992
+ async listAgents(args) {
1993
+ const { page = 0, perPage: perPageInput, orderBy } = args || {};
1994
+ const { field, direction } = this.parseOrderBy(orderBy);
1995
+ if (page < 0) {
1996
+ throw new MastraError(
1997
+ {
1998
+ id: createStorageErrorId("LIBSQL", "LIST_AGENTS", "INVALID_PAGE"),
1999
+ domain: ErrorDomain.STORAGE,
2000
+ category: ErrorCategory.USER,
2001
+ details: { page }
2002
+ },
2003
+ new Error("page must be >= 0")
2004
+ );
2005
+ }
2006
+ const perPage = normalizePerPage(perPageInput, 100);
2007
+ const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
2008
+ try {
2009
+ const total = await this.#db.selectTotalCount({ tableName: TABLE_AGENTS });
2010
+ if (total === 0) {
2011
+ return {
2012
+ agents: [],
2013
+ total: 0,
2014
+ page,
2015
+ perPage: perPageForResponse,
2016
+ hasMore: false
2017
+ };
1220
2018
  }
2019
+ const limitValue = perPageInput === false ? total : perPage;
2020
+ const rows = await this.#db.selectMany({
2021
+ tableName: TABLE_AGENTS,
2022
+ orderBy: `"${field}" ${direction}`,
2023
+ limit: limitValue,
2024
+ offset
2025
+ });
2026
+ const agents = rows.map((row) => this.parseRow(row));
2027
+ return {
2028
+ agents,
2029
+ total,
2030
+ page,
2031
+ perPage: perPageForResponse,
2032
+ hasMore: perPageInput === false ? false : offset + perPage < total
2033
+ };
2034
+ } catch (error) {
2035
+ throw new MastraError(
2036
+ {
2037
+ id: createStorageErrorId("LIBSQL", "LIST_AGENTS", "FAILED"),
2038
+ domain: ErrorDomain.STORAGE,
2039
+ category: ErrorCategory.THIRD_PARTY
2040
+ },
2041
+ error
2042
+ );
2043
+ }
2044
+ }
2045
+ };
2046
+ var MemoryLibSQL = class extends MemoryStorage {
2047
+ #client;
2048
+ #db;
2049
+ constructor(config) {
2050
+ super();
2051
+ const client = resolveClient(config);
2052
+ this.#client = client;
2053
+ this.#db = new LibSQLDB({ client, maxRetries: config.maxRetries, initialBackoffMs: config.initialBackoffMs });
2054
+ }
2055
+ async init() {
2056
+ await this.#db.createTable({ tableName: TABLE_THREADS, schema: TABLE_SCHEMAS[TABLE_THREADS] });
2057
+ await this.#db.createTable({ tableName: TABLE_MESSAGES, schema: TABLE_SCHEMAS[TABLE_MESSAGES] });
2058
+ await this.#db.createTable({ tableName: TABLE_RESOURCES, schema: TABLE_SCHEMAS[TABLE_RESOURCES] });
2059
+ await this.#db.alterTable({
2060
+ tableName: TABLE_MESSAGES,
2061
+ schema: TABLE_SCHEMAS[TABLE_MESSAGES],
2062
+ ifNotExists: ["resourceId"]
2063
+ });
2064
+ }
2065
+ async dangerouslyClearAll() {
2066
+ await this.#db.deleteData({ tableName: TABLE_MESSAGES });
2067
+ await this.#db.deleteData({ tableName: TABLE_THREADS });
2068
+ await this.#db.deleteData({ tableName: TABLE_RESOURCES });
2069
+ }
2070
+ parseRow(row) {
2071
+ let content = row.content;
2072
+ try {
2073
+ content = JSON.parse(row.content);
2074
+ } catch {
2075
+ }
2076
+ const result = {
2077
+ id: row.id,
2078
+ content,
2079
+ role: row.role,
2080
+ createdAt: new Date(row.createdAt),
2081
+ threadId: row.thread_id,
2082
+ resourceId: row.resourceId
2083
+ };
2084
+ if (row.type && row.type !== `v2`) result.type = row.type;
2085
+ return result;
2086
+ }
2087
+ async _getIncludedMessages({ include }) {
2088
+ if (!include || include.length === 0) return null;
2089
+ const unionQueries = [];
2090
+ const params = [];
2091
+ for (const inc of include) {
2092
+ const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
2093
+ unionQueries.push(
2094
+ `
2095
+ SELECT * FROM (
2096
+ WITH target_thread AS (
2097
+ SELECT thread_id FROM "${TABLE_MESSAGES}" WHERE id = ?
2098
+ ),
2099
+ numbered_messages AS (
2100
+ SELECT
2101
+ id, content, role, type, "createdAt", thread_id, "resourceId",
2102
+ ROW_NUMBER() OVER (ORDER BY "createdAt" ASC) as row_num
2103
+ FROM "${TABLE_MESSAGES}"
2104
+ WHERE thread_id = (SELECT thread_id FROM target_thread)
2105
+ ),
2106
+ target_positions AS (
2107
+ SELECT row_num as target_pos
2108
+ FROM numbered_messages
2109
+ WHERE id = ?
2110
+ )
2111
+ SELECT DISTINCT m.*
2112
+ FROM numbered_messages m
2113
+ CROSS JOIN target_positions t
2114
+ WHERE m.row_num BETWEEN (t.target_pos - ?) AND (t.target_pos + ?)
2115
+ )
2116
+ `
2117
+ // Keep ASC for final sorting after fetching context
2118
+ );
2119
+ params.push(id, id, withPreviousMessages, withNextMessages);
2120
+ }
2121
+ const finalQuery = unionQueries.join(" UNION ALL ") + ' ORDER BY "createdAt" ASC';
2122
+ const includedResult = await this.#client.execute({ sql: finalQuery, args: params });
2123
+ const includedRows = includedResult.rows?.map((row) => this.parseRow(row));
2124
+ const seen = /* @__PURE__ */ new Set();
2125
+ const dedupedRows = includedRows.filter((row) => {
2126
+ if (seen.has(row.id)) return false;
2127
+ seen.add(row.id);
2128
+ return true;
2129
+ });
2130
+ return dedupedRows;
2131
+ }
2132
+ async listMessagesById({ messageIds }) {
2133
+ if (messageIds.length === 0) return { messages: [] };
2134
+ try {
2135
+ const sql = `
2136
+ SELECT
2137
+ id,
2138
+ content,
2139
+ role,
2140
+ type,
2141
+ "createdAt",
2142
+ thread_id,
2143
+ "resourceId"
2144
+ FROM "${TABLE_MESSAGES}"
2145
+ WHERE id IN (${messageIds.map(() => "?").join(", ")})
2146
+ ORDER BY "createdAt" DESC
2147
+ `;
2148
+ const result = await this.#client.execute({ sql, args: messageIds });
2149
+ if (!result.rows) return { messages: [] };
2150
+ const list = new MessageList().add(result.rows.map(this.parseRow), "memory");
2151
+ return { messages: list.get.all.db() };
2152
+ } catch (error) {
2153
+ throw new MastraError(
2154
+ {
2155
+ id: createStorageErrorId("LIBSQL", "LIST_MESSAGES_BY_ID", "FAILED"),
2156
+ domain: ErrorDomain.STORAGE,
2157
+ category: ErrorCategory.THIRD_PARTY,
2158
+ details: { messageIds: JSON.stringify(messageIds) }
2159
+ },
2160
+ error
2161
+ );
2162
+ }
2163
+ }
2164
+ async listMessages(args) {
2165
+ const { threadId, resourceId, include, filter, perPage: perPageInput, page = 0, orderBy } = args;
2166
+ const threadIds = Array.isArray(threadId) ? threadId : [threadId];
2167
+ if (threadIds.length === 0 || threadIds.some((id) => !id.trim())) {
2168
+ throw new MastraError(
2169
+ {
2170
+ id: createStorageErrorId("LIBSQL", "LIST_MESSAGES", "INVALID_THREAD_ID"),
2171
+ domain: ErrorDomain.STORAGE,
2172
+ category: ErrorCategory.THIRD_PARTY,
2173
+ details: { threadId: Array.isArray(threadId) ? threadId.join(",") : threadId }
2174
+ },
2175
+ new Error("threadId must be a non-empty string or array of non-empty strings")
2176
+ );
2177
+ }
2178
+ if (page < 0) {
2179
+ throw new MastraError(
2180
+ {
2181
+ id: createStorageErrorId("LIBSQL", "LIST_MESSAGES", "INVALID_PAGE"),
2182
+ domain: ErrorDomain.STORAGE,
2183
+ category: ErrorCategory.USER,
2184
+ details: { page }
2185
+ },
2186
+ new Error("page must be >= 0")
2187
+ );
1221
2188
  }
2189
+ const perPage = normalizePerPage(perPageInput, 40);
2190
+ const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
1222
2191
  try {
1223
- if (!threadId.trim()) throw new Error("threadId must be a non-empty string");
1224
- const currentOffset = page * perPage;
1225
- const conditions = [`thread_id = ?`];
1226
- const queryParams = [threadId];
1227
- if (fromDate) {
2192
+ const { field, direction } = this.parseOrderBy(orderBy, "ASC");
2193
+ const orderByStatement = `ORDER BY "${field}" ${direction}`;
2194
+ const threadPlaceholders = threadIds.map(() => "?").join(", ");
2195
+ const conditions = [`thread_id IN (${threadPlaceholders})`];
2196
+ const queryParams = [...threadIds];
2197
+ if (resourceId) {
2198
+ conditions.push(`"resourceId" = ?`);
2199
+ queryParams.push(resourceId);
2200
+ }
2201
+ if (filter?.dateRange?.start) {
1228
2202
  conditions.push(`"createdAt" >= ?`);
1229
- queryParams.push(fromDate.toISOString());
2203
+ queryParams.push(
2204
+ filter.dateRange.start instanceof Date ? filter.dateRange.start.toISOString() : filter.dateRange.start
2205
+ );
1230
2206
  }
1231
- if (toDate) {
2207
+ if (filter?.dateRange?.end) {
1232
2208
  conditions.push(`"createdAt" <= ?`);
1233
- queryParams.push(toDate.toISOString());
2209
+ queryParams.push(
2210
+ filter.dateRange.end instanceof Date ? filter.dateRange.end.toISOString() : filter.dateRange.end
2211
+ );
1234
2212
  }
1235
2213
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1236
- const countResult = await this.client.execute({
2214
+ const countResult = await this.#client.execute({
1237
2215
  sql: `SELECT COUNT(*) as count FROM ${TABLE_MESSAGES} ${whereClause}`,
1238
2216
  args: queryParams
1239
2217
  });
1240
2218
  const total = Number(countResult.rows?.[0]?.count ?? 0);
1241
- if (total === 0 && messages.length === 0) {
2219
+ const limitValue = perPageInput === false ? total : perPage;
2220
+ const dataResult = await this.#client.execute({
2221
+ sql: `SELECT id, content, role, type, "createdAt", "resourceId", "thread_id" FROM ${TABLE_MESSAGES} ${whereClause} ${orderByStatement} LIMIT ? OFFSET ?`,
2222
+ args: [...queryParams, limitValue, offset]
2223
+ });
2224
+ const messages = (dataResult.rows || []).map((row) => this.parseRow(row));
2225
+ if (total === 0 && messages.length === 0 && (!include || include.length === 0)) {
1242
2226
  return {
1243
2227
  messages: [],
1244
2228
  total: 0,
1245
2229
  page,
1246
- perPage,
2230
+ perPage: perPageForResponse,
1247
2231
  hasMore: false
1248
2232
  };
1249
2233
  }
1250
- const excludeIds = messages.map((m) => m.id);
1251
- const excludeIdsParam = excludeIds.map((_, idx) => `$${idx + queryParams.length + 1}`).join(", ");
1252
- const dataResult = await this.client.execute({
1253
- 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 ?`,
1254
- args: [...queryParams, ...excludeIds, perPage, currentOffset]
2234
+ const messageIds = new Set(messages.map((m) => m.id));
2235
+ if (include && include.length > 0) {
2236
+ const includeMessages = await this._getIncludedMessages({ include });
2237
+ if (includeMessages) {
2238
+ for (const includeMsg of includeMessages) {
2239
+ if (!messageIds.has(includeMsg.id)) {
2240
+ messages.push(includeMsg);
2241
+ messageIds.add(includeMsg.id);
2242
+ }
2243
+ }
2244
+ }
2245
+ }
2246
+ const list = new MessageList().add(messages, "memory");
2247
+ let finalMessages = list.get.all.db();
2248
+ finalMessages = finalMessages.sort((a, b) => {
2249
+ const isDateField = field === "createdAt" || field === "updatedAt";
2250
+ const aValue = isDateField ? new Date(a[field]).getTime() : a[field];
2251
+ const bValue = isDateField ? new Date(b[field]).getTime() : b[field];
2252
+ if (typeof aValue === "number" && typeof bValue === "number") {
2253
+ return direction === "ASC" ? aValue - bValue : bValue - aValue;
2254
+ }
2255
+ return direction === "ASC" ? String(aValue).localeCompare(String(bValue)) : String(bValue).localeCompare(String(aValue));
1255
2256
  });
1256
- messages.push(...(dataResult.rows || []).map((row) => this.parseRow(row)));
1257
- const messagesToReturn = format === "v1" ? new MessageList().add(messages, "memory").get.all.v1() : new MessageList().add(messages, "memory").get.all.v2();
2257
+ const threadIdSet = new Set(threadIds);
2258
+ const returnedThreadMessageIds = new Set(
2259
+ finalMessages.filter((m) => m.threadId && threadIdSet.has(m.threadId)).map((m) => m.id)
2260
+ );
2261
+ const allThreadMessagesReturned = returnedThreadMessageIds.size >= total;
2262
+ const hasMore = perPageInput !== false && !allThreadMessagesReturned && offset + perPage < total;
1258
2263
  return {
1259
- messages: messagesToReturn,
2264
+ messages: finalMessages,
1260
2265
  total,
1261
2266
  page,
1262
- perPage,
1263
- hasMore: currentOffset + messages.length < total
2267
+ perPage: perPageForResponse,
2268
+ hasMore
1264
2269
  };
1265
2270
  } catch (error) {
1266
2271
  const mastraError = new MastraError(
1267
2272
  {
1268
- id: "LIBSQL_STORE_GET_MESSAGES_PAGINATED_FAILED",
2273
+ id: createStorageErrorId("LIBSQL", "LIST_MESSAGES", "FAILED"),
1269
2274
  domain: ErrorDomain.STORAGE,
1270
2275
  category: ErrorCategory.THIRD_PARTY,
1271
- details: { threadId }
2276
+ details: {
2277
+ threadId: Array.isArray(threadId) ? threadId.join(",") : threadId,
2278
+ resourceId: resourceId ?? ""
2279
+ }
1272
2280
  },
1273
2281
  error
1274
2282
  );
1275
- this.logger?.trackException?.(mastraError);
1276
2283
  this.logger?.error?.(mastraError.toString());
1277
- return { messages: [], total: 0, page, perPage, hasMore: false };
2284
+ this.logger?.trackException?.(mastraError);
2285
+ return {
2286
+ messages: [],
2287
+ total: 0,
2288
+ page,
2289
+ perPage: perPageForResponse,
2290
+ hasMore: false
2291
+ };
1278
2292
  }
1279
2293
  }
1280
- async saveMessages({
1281
- messages,
1282
- format
1283
- }) {
1284
- if (messages.length === 0) return messages;
2294
+ async saveMessages({ messages }) {
2295
+ if (messages.length === 0) return { messages };
1285
2296
  try {
1286
2297
  const threadId = messages[0]?.threadId;
1287
2298
  if (!threadId) {
@@ -1331,19 +2342,18 @@ var MemoryLibSQL = class extends MemoryStorage {
1331
2342
  for (let i = 0; i < messageStatements.length; i += BATCH_SIZE) {
1332
2343
  const batch = messageStatements.slice(i, i + BATCH_SIZE);
1333
2344
  if (batch.length > 0) {
1334
- await this.client.batch(batch, "write");
2345
+ await this.#client.batch(batch, "write");
1335
2346
  }
1336
2347
  }
1337
2348
  if (threadUpdateStatement) {
1338
- await this.client.execute(threadUpdateStatement);
2349
+ await this.#client.execute(threadUpdateStatement);
1339
2350
  }
1340
2351
  const list = new MessageList().add(messages, "memory");
1341
- if (format === `v2`) return list.get.all.v2();
1342
- return list.get.all.v1();
2352
+ return { messages: list.get.all.db() };
1343
2353
  } catch (error) {
1344
2354
  throw new MastraError(
1345
2355
  {
1346
- id: "LIBSQL_STORE_SAVE_MESSAGES_FAILED",
2356
+ id: createStorageErrorId("LIBSQL", "SAVE_MESSAGES", "FAILED"),
1347
2357
  domain: ErrorDomain.STORAGE,
1348
2358
  category: ErrorCategory.THIRD_PARTY
1349
2359
  },
@@ -1360,7 +2370,7 @@ var MemoryLibSQL = class extends MemoryStorage {
1360
2370
  const messageIds = messages.map((m) => m.id);
1361
2371
  const placeholders = messageIds.map(() => "?").join(",");
1362
2372
  const selectSql = `SELECT * FROM ${TABLE_MESSAGES} WHERE id IN (${placeholders})`;
1363
- const existingResult = await this.client.execute({ sql: selectSql, args: messageIds });
2373
+ const existingResult = await this.#client.execute({ sql: selectSql, args: messageIds });
1364
2374
  const existingMessages = existingResult.rows.map((row) => this.parseRow(row));
1365
2375
  if (existingMessages.length === 0) {
1366
2376
  return [];
@@ -1426,8 +2436,8 @@ var MemoryLibSQL = class extends MemoryStorage {
1426
2436
  });
1427
2437
  }
1428
2438
  }
1429
- await this.client.batch(batchStatements, "write");
1430
- const updatedResult = await this.client.execute({ sql: selectSql, args: messageIds });
2439
+ await this.#client.batch(batchStatements, "write");
2440
+ const updatedResult = await this.#client.execute({ sql: selectSql, args: messageIds });
1431
2441
  return updatedResult.rows.map((row) => this.parseRow(row));
1432
2442
  }
1433
2443
  async deleteMessages(messageIds) {
@@ -1437,7 +2447,7 @@ var MemoryLibSQL = class extends MemoryStorage {
1437
2447
  try {
1438
2448
  const BATCH_SIZE = 100;
1439
2449
  const threadIds = /* @__PURE__ */ new Set();
1440
- const tx = await this.client.transaction("write");
2450
+ const tx = await this.#client.transaction("write");
1441
2451
  try {
1442
2452
  for (let i = 0; i < messageIds.length; i += BATCH_SIZE) {
1443
2453
  const batch = messageIds.slice(i, i + BATCH_SIZE);
@@ -1461,1151 +2471,611 @@ var MemoryLibSQL = class extends MemoryStorage {
1461
2471
  sql: `UPDATE "${TABLE_THREADS}" SET "updatedAt" = ? WHERE id = ?`,
1462
2472
  args: [now, threadId]
1463
2473
  });
1464
- }
1465
- }
1466
- await tx.commit();
1467
- } catch (error) {
1468
- await tx.rollback();
1469
- throw error;
1470
- }
1471
- } catch (error) {
1472
- throw new MastraError(
1473
- {
1474
- id: "LIBSQL_STORE_DELETE_MESSAGES_FAILED",
1475
- domain: ErrorDomain.STORAGE,
1476
- category: ErrorCategory.THIRD_PARTY,
1477
- details: { messageIds: messageIds.join(", ") }
1478
- },
1479
- error
1480
- );
1481
- }
1482
- }
1483
- async getResourceById({ resourceId }) {
1484
- const result = await this.operations.load({
1485
- tableName: TABLE_RESOURCES,
1486
- keys: { id: resourceId }
1487
- });
1488
- if (!result) {
1489
- return null;
1490
- }
1491
- return {
1492
- ...result,
1493
- // Ensure workingMemory is always returned as a string, even if auto-parsed as JSON
1494
- workingMemory: result.workingMemory && typeof result.workingMemory === "object" ? JSON.stringify(result.workingMemory) : result.workingMemory,
1495
- metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata,
1496
- createdAt: new Date(result.createdAt),
1497
- updatedAt: new Date(result.updatedAt)
1498
- };
1499
- }
1500
- async saveResource({ resource }) {
1501
- await this.operations.insert({
1502
- tableName: TABLE_RESOURCES,
1503
- record: {
1504
- ...resource,
1505
- metadata: JSON.stringify(resource.metadata)
1506
- }
1507
- });
1508
- return resource;
1509
- }
1510
- async updateResource({
1511
- resourceId,
1512
- workingMemory,
1513
- metadata
1514
- }) {
1515
- const existingResource = await this.getResourceById({ resourceId });
1516
- if (!existingResource) {
1517
- const newResource = {
1518
- id: resourceId,
1519
- workingMemory,
1520
- metadata: metadata || {},
1521
- createdAt: /* @__PURE__ */ new Date(),
1522
- updatedAt: /* @__PURE__ */ new Date()
1523
- };
1524
- return this.saveResource({ resource: newResource });
1525
- }
1526
- const updatedResource = {
1527
- ...existingResource,
1528
- workingMemory: workingMemory !== void 0 ? workingMemory : existingResource.workingMemory,
1529
- metadata: {
1530
- ...existingResource.metadata,
1531
- ...metadata
1532
- },
1533
- updatedAt: /* @__PURE__ */ new Date()
1534
- };
1535
- const updates = [];
1536
- const values = [];
1537
- if (workingMemory !== void 0) {
1538
- updates.push("workingMemory = ?");
1539
- values.push(workingMemory);
1540
- }
1541
- if (metadata) {
1542
- updates.push("metadata = ?");
1543
- values.push(JSON.stringify(updatedResource.metadata));
1544
- }
1545
- updates.push("updatedAt = ?");
1546
- values.push(updatedResource.updatedAt.toISOString());
1547
- values.push(resourceId);
1548
- await this.client.execute({
1549
- sql: `UPDATE ${TABLE_RESOURCES} SET ${updates.join(", ")} WHERE id = ?`,
1550
- args: values
1551
- });
1552
- return updatedResource;
1553
- }
1554
- async getThreadById({ threadId }) {
1555
- try {
1556
- const result = await this.operations.load({
1557
- tableName: TABLE_THREADS,
1558
- keys: { id: threadId }
1559
- });
1560
- if (!result) {
1561
- return null;
1562
- }
1563
- return {
1564
- ...result,
1565
- metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata,
1566
- createdAt: new Date(result.createdAt),
1567
- updatedAt: new Date(result.updatedAt)
1568
- };
1569
- } catch (error) {
1570
- throw new MastraError(
1571
- {
1572
- id: "LIBSQL_STORE_GET_THREAD_BY_ID_FAILED",
1573
- domain: ErrorDomain.STORAGE,
1574
- category: ErrorCategory.THIRD_PARTY,
1575
- details: { threadId }
1576
- },
1577
- error
1578
- );
1579
- }
1580
- }
1581
- /**
1582
- * @deprecated use getThreadsByResourceIdPaginated instead for paginated results.
1583
- */
1584
- async getThreadsByResourceId(args) {
1585
- const resourceId = args.resourceId;
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 result = await this.client.execute({
1602
- sql: `SELECT * ${baseQuery} ORDER BY ${orderBy} ${sortDirection}`,
1603
- args: queryParams
1604
- });
1605
- if (!result.rows) {
1606
- return [];
1607
- }
1608
- return result.rows.map(mapRowToStorageThreadType);
1609
- } catch (error) {
1610
- const mastraError = new MastraError(
1611
- {
1612
- id: "LIBSQL_STORE_GET_THREADS_BY_RESOURCE_ID_FAILED",
1613
- domain: ErrorDomain.STORAGE,
1614
- category: ErrorCategory.THIRD_PARTY,
1615
- details: { resourceId }
1616
- },
1617
- error
1618
- );
1619
- this.logger?.trackException?.(mastraError);
1620
- this.logger?.error?.(mastraError.toString());
1621
- return [];
1622
- }
1623
- }
1624
- async getThreadsByResourceIdPaginated(args) {
1625
- const { resourceId, page = 0, perPage = 100 } = args;
1626
- const orderBy = this.castThreadOrderBy(args.orderBy);
1627
- const sortDirection = this.castThreadSortDirection(args.sortDirection);
1628
- try {
1629
- const baseQuery = `FROM ${TABLE_THREADS} WHERE resourceId = ?`;
1630
- const queryParams = [resourceId];
1631
- const mapRowToStorageThreadType = (row) => ({
1632
- id: row.id,
1633
- resourceId: row.resourceId,
1634
- title: row.title,
1635
- createdAt: new Date(row.createdAt),
1636
- // Convert string to Date
1637
- updatedAt: new Date(row.updatedAt),
1638
- // Convert string to Date
1639
- metadata: typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata
1640
- });
1641
- const currentOffset = page * perPage;
1642
- const countResult = await this.client.execute({
1643
- sql: `SELECT COUNT(*) as count ${baseQuery}`,
1644
- args: queryParams
1645
- });
1646
- const total = Number(countResult.rows?.[0]?.count ?? 0);
1647
- if (total === 0) {
1648
- return {
1649
- threads: [],
1650
- total: 0,
1651
- page,
1652
- perPage,
1653
- hasMore: false
1654
- };
1655
- }
1656
- const dataResult = await this.client.execute({
1657
- sql: `SELECT * ${baseQuery} ORDER BY ${orderBy} ${sortDirection} LIMIT ? OFFSET ?`,
1658
- args: [...queryParams, perPage, currentOffset]
1659
- });
1660
- const threads = (dataResult.rows || []).map(mapRowToStorageThreadType);
1661
- return {
1662
- threads,
1663
- total,
1664
- page,
1665
- perPage,
1666
- hasMore: currentOffset + threads.length < total
1667
- };
1668
- } catch (error) {
1669
- const mastraError = new MastraError(
1670
- {
1671
- id: "LIBSQL_STORE_GET_THREADS_BY_RESOURCE_ID_FAILED",
1672
- domain: ErrorDomain.STORAGE,
1673
- category: ErrorCategory.THIRD_PARTY,
1674
- details: { resourceId }
1675
- },
1676
- error
1677
- );
1678
- this.logger?.trackException?.(mastraError);
1679
- this.logger?.error?.(mastraError.toString());
1680
- return { threads: [], total: 0, page, perPage, hasMore: false };
1681
- }
1682
- }
1683
- async saveThread({ thread }) {
1684
- try {
1685
- await this.operations.insert({
1686
- tableName: TABLE_THREADS,
1687
- record: {
1688
- ...thread,
1689
- metadata: JSON.stringify(thread.metadata)
1690
- }
1691
- });
1692
- return thread;
1693
- } catch (error) {
1694
- const mastraError = new MastraError(
1695
- {
1696
- id: "LIBSQL_STORE_SAVE_THREAD_FAILED",
1697
- domain: ErrorDomain.STORAGE,
1698
- category: ErrorCategory.THIRD_PARTY,
1699
- details: { threadId: thread.id }
1700
- },
1701
- error
1702
- );
1703
- this.logger?.trackException?.(mastraError);
1704
- this.logger?.error?.(mastraError.toString());
1705
- throw mastraError;
1706
- }
1707
- }
1708
- async updateThread({
1709
- id,
1710
- title,
1711
- metadata
1712
- }) {
1713
- const thread = await this.getThreadById({ threadId: id });
1714
- if (!thread) {
1715
- throw new MastraError({
1716
- id: "LIBSQL_STORE_UPDATE_THREAD_FAILED_THREAD_NOT_FOUND",
1717
- domain: ErrorDomain.STORAGE,
1718
- category: ErrorCategory.USER,
1719
- text: `Thread ${id} not found`,
1720
- details: {
1721
- status: 404,
1722
- threadId: id
2474
+ }
1723
2475
  }
1724
- });
1725
- }
1726
- const updatedThread = {
1727
- ...thread,
1728
- title,
1729
- metadata: {
1730
- ...thread.metadata,
1731
- ...metadata
2476
+ await tx.commit();
2477
+ } catch (error) {
2478
+ await tx.rollback();
2479
+ throw error;
1732
2480
  }
1733
- };
1734
- try {
1735
- await this.client.execute({
1736
- sql: `UPDATE ${TABLE_THREADS} SET title = ?, metadata = ? WHERE id = ?`,
1737
- args: [title, JSON.stringify(updatedThread.metadata), id]
1738
- });
1739
- return updatedThread;
1740
- } catch (error) {
1741
- throw new MastraError(
1742
- {
1743
- id: "LIBSQL_STORE_UPDATE_THREAD_FAILED",
1744
- domain: ErrorDomain.STORAGE,
1745
- category: ErrorCategory.THIRD_PARTY,
1746
- text: `Failed to update thread ${id}`,
1747
- details: { threadId: id }
1748
- },
1749
- error
1750
- );
1751
- }
1752
- }
1753
- async deleteThread({ threadId }) {
1754
- try {
1755
- await this.client.execute({
1756
- sql: `DELETE FROM ${TABLE_MESSAGES} WHERE thread_id = ?`,
1757
- args: [threadId]
1758
- });
1759
- await this.client.execute({
1760
- sql: `DELETE FROM ${TABLE_THREADS} WHERE id = ?`,
1761
- args: [threadId]
1762
- });
1763
2481
  } catch (error) {
1764
2482
  throw new MastraError(
1765
2483
  {
1766
- id: "LIBSQL_STORE_DELETE_THREAD_FAILED",
2484
+ id: createStorageErrorId("LIBSQL", "DELETE_MESSAGES", "FAILED"),
1767
2485
  domain: ErrorDomain.STORAGE,
1768
2486
  category: ErrorCategory.THIRD_PARTY,
1769
- details: { threadId }
2487
+ details: { messageIds: messageIds.join(", ") }
1770
2488
  },
1771
2489
  error
1772
2490
  );
1773
2491
  }
1774
2492
  }
1775
- };
1776
- function createExecuteWriteOperationWithRetry({
1777
- logger,
1778
- maxRetries,
1779
- initialBackoffMs
1780
- }) {
1781
- return async function executeWriteOperationWithRetry(operationFn, operationDescription) {
1782
- let retries = 0;
1783
- while (true) {
1784
- try {
1785
- return await operationFn();
1786
- } catch (error) {
1787
- if (error.message && (error.message.includes("SQLITE_BUSY") || error.message.includes("database is locked")) && retries < maxRetries) {
1788
- retries++;
1789
- const backoffTime = initialBackoffMs * Math.pow(2, retries - 1);
1790
- logger.warn(
1791
- `LibSQLStore: Encountered SQLITE_BUSY during ${operationDescription}. Retrying (${retries}/${maxRetries}) in ${backoffTime}ms...`
1792
- );
1793
- await new Promise((resolve) => setTimeout(resolve, backoffTime));
1794
- } else {
1795
- logger.error(`LibSQLStore: Error during ${operationDescription} after ${retries} retries: ${error}`);
1796
- throw error;
1797
- }
1798
- }
1799
- }
1800
- };
1801
- }
1802
- function prepareStatement({ tableName, record }) {
1803
- const parsedTableName = parseSqlIdentifier(tableName, "table name");
1804
- const columns = Object.keys(record).map((col) => parseSqlIdentifier(col, "column name"));
1805
- const values = Object.values(record).map((v) => {
1806
- if (typeof v === `undefined` || v === null) {
2493
+ async getResourceById({ resourceId }) {
2494
+ const result = await this.#db.select({
2495
+ tableName: TABLE_RESOURCES,
2496
+ keys: { id: resourceId }
2497
+ });
2498
+ if (!result) {
1807
2499
  return null;
1808
2500
  }
1809
- if (v instanceof Date) {
1810
- return v.toISOString();
1811
- }
1812
- return typeof v === "object" ? JSON.stringify(v) : v;
1813
- });
1814
- const placeholders = values.map(() => "?").join(", ");
1815
- return {
1816
- sql: `INSERT OR REPLACE INTO ${parsedTableName} (${columns.join(", ")}) VALUES (${placeholders})`,
1817
- args: values
1818
- };
1819
- }
1820
- function prepareUpdateStatement({
1821
- tableName,
1822
- updates,
1823
- keys
1824
- }) {
1825
- const parsedTableName = parseSqlIdentifier(tableName, "table name");
1826
- const schema = TABLE_SCHEMAS[tableName];
1827
- const updateColumns = Object.keys(updates).map((col) => parseSqlIdentifier(col, "column name"));
1828
- const updateValues = Object.values(updates).map(transformToSqlValue);
1829
- const setClause = updateColumns.map((col) => `${col} = ?`).join(", ");
1830
- const whereClause = prepareWhereClause(keys, schema);
1831
- return {
1832
- sql: `UPDATE ${parsedTableName} SET ${setClause}${whereClause.sql}`,
1833
- args: [...updateValues, ...whereClause.args]
1834
- };
1835
- }
1836
- function transformToSqlValue(value) {
1837
- if (typeof value === "undefined" || value === null) {
1838
- return null;
1839
- }
1840
- if (value instanceof Date) {
1841
- return value.toISOString();
1842
- }
1843
- return typeof value === "object" ? JSON.stringify(value) : value;
1844
- }
1845
- function prepareDeleteStatement({ tableName, keys }) {
1846
- const parsedTableName = parseSqlIdentifier(tableName, "table name");
1847
- const whereClause = prepareWhereClause(keys, TABLE_SCHEMAS[tableName]);
1848
- return {
1849
- sql: `DELETE FROM ${parsedTableName}${whereClause.sql}`,
1850
- args: whereClause.args
1851
- };
1852
- }
1853
- function prepareWhereClause(filters, schema) {
1854
- const conditions = [];
1855
- const args = [];
1856
- for (const [columnName, filterValue] of Object.entries(filters)) {
1857
- const column = schema[columnName];
1858
- if (!column) {
1859
- throw new Error(`Unknown column: ${columnName}`);
1860
- }
1861
- const parsedColumn = parseSqlIdentifier(columnName, "column name");
1862
- const result = buildCondition2(parsedColumn, filterValue);
1863
- conditions.push(result.condition);
1864
- args.push(...result.args);
1865
- }
1866
- return {
1867
- sql: conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "",
1868
- args
1869
- };
1870
- }
1871
- function buildCondition2(columnName, filterValue) {
1872
- if (filterValue === null) {
1873
- return { condition: `${columnName} IS NULL`, args: [] };
1874
- }
1875
- if (typeof filterValue === "object" && filterValue !== null && ("startAt" in filterValue || "endAt" in filterValue)) {
1876
- return buildDateRangeCondition(columnName, filterValue);
1877
- }
1878
- return {
1879
- condition: `${columnName} = ?`,
1880
- args: [transformToSqlValue(filterValue)]
1881
- };
1882
- }
1883
- function buildDateRangeCondition(columnName, range) {
1884
- const conditions = [];
1885
- const args = [];
1886
- if (range.startAt !== void 0) {
1887
- conditions.push(`${columnName} >= ?`);
1888
- args.push(transformToSqlValue(range.startAt));
1889
- }
1890
- if (range.endAt !== void 0) {
1891
- conditions.push(`${columnName} <= ?`);
1892
- args.push(transformToSqlValue(range.endAt));
1893
- }
1894
- if (conditions.length === 0) {
1895
- throw new Error("Date range must specify at least startAt or endAt");
1896
- }
1897
- return {
1898
- condition: conditions.join(" AND "),
1899
- args
1900
- };
1901
- }
1902
- function buildDateRangeFilter(dateRange, columnName = "createdAt") {
1903
- if (!dateRange?.start && !dateRange?.end) {
1904
- return {};
1905
- }
1906
- const filter = {};
1907
- if (dateRange.start) {
1908
- filter.startAt = new Date(dateRange.start).toISOString();
1909
- }
1910
- if (dateRange.end) {
1911
- filter.endAt = new Date(dateRange.end).toISOString();
1912
- }
1913
- return { [columnName]: filter };
1914
- }
1915
- function transformFromSqlRow({
1916
- tableName,
1917
- sqlRow
1918
- }) {
1919
- const result = {};
1920
- const jsonColumns = new Set(
1921
- Object.keys(TABLE_SCHEMAS[tableName]).filter((key) => TABLE_SCHEMAS[tableName][key].type === "jsonb").map((key) => key)
1922
- );
1923
- const dateColumns = new Set(
1924
- Object.keys(TABLE_SCHEMAS[tableName]).filter((key) => TABLE_SCHEMAS[tableName][key].type === "timestamp").map((key) => key)
1925
- );
1926
- for (const [key, value] of Object.entries(sqlRow)) {
1927
- if (value === null || value === void 0) {
1928
- result[key] = value;
1929
- continue;
1930
- }
1931
- if (dateColumns.has(key) && typeof value === "string") {
1932
- result[key] = new Date(value);
1933
- continue;
1934
- }
1935
- if (jsonColumns.has(key) && typeof value === "string") {
1936
- result[key] = safelyParseJSON(value);
1937
- continue;
1938
- }
1939
- result[key] = value;
2501
+ return {
2502
+ ...result,
2503
+ // Ensure workingMemory is always returned as a string, even if auto-parsed as JSON
2504
+ workingMemory: result.workingMemory && typeof result.workingMemory === "object" ? JSON.stringify(result.workingMemory) : result.workingMemory,
2505
+ metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata,
2506
+ createdAt: new Date(result.createdAt),
2507
+ updatedAt: new Date(result.updatedAt)
2508
+ };
1940
2509
  }
1941
- return result;
1942
- }
1943
-
1944
- // src/storage/domains/observability/index.ts
1945
- var ObservabilityLibSQL = class extends ObservabilityStorage {
1946
- operations;
1947
- constructor({ operations }) {
1948
- super();
1949
- this.operations = operations;
2510
+ async saveResource({ resource }) {
2511
+ await this.#db.insert({
2512
+ tableName: TABLE_RESOURCES,
2513
+ record: {
2514
+ ...resource,
2515
+ metadata: JSON.stringify(resource.metadata)
2516
+ }
2517
+ });
2518
+ return resource;
1950
2519
  }
1951
- async createAISpan(span) {
1952
- try {
1953
- const now = (/* @__PURE__ */ new Date()).toISOString();
1954
- const record = {
1955
- ...span,
1956
- createdAt: now,
1957
- updatedAt: now
2520
+ async updateResource({
2521
+ resourceId,
2522
+ workingMemory,
2523
+ metadata
2524
+ }) {
2525
+ const existingResource = await this.getResourceById({ resourceId });
2526
+ if (!existingResource) {
2527
+ const newResource = {
2528
+ id: resourceId,
2529
+ workingMemory,
2530
+ metadata: metadata || {},
2531
+ createdAt: /* @__PURE__ */ new Date(),
2532
+ updatedAt: /* @__PURE__ */ new Date()
1958
2533
  };
1959
- return this.operations.insert({ tableName: TABLE_AI_SPANS, record });
1960
- } catch (error) {
1961
- throw new MastraError(
1962
- {
1963
- id: "LIBSQL_STORE_CREATE_AI_SPAN_FAILED",
1964
- domain: ErrorDomain.STORAGE,
1965
- category: ErrorCategory.USER,
1966
- details: {
1967
- spanId: span.spanId,
1968
- traceId: span.traceId,
1969
- spanType: span.spanType,
1970
- spanName: span.name
1971
- }
1972
- },
1973
- error
1974
- );
2534
+ return this.saveResource({ resource: newResource });
1975
2535
  }
2536
+ const updatedResource = {
2537
+ ...existingResource,
2538
+ workingMemory: workingMemory !== void 0 ? workingMemory : existingResource.workingMemory,
2539
+ metadata: {
2540
+ ...existingResource.metadata,
2541
+ ...metadata
2542
+ },
2543
+ updatedAt: /* @__PURE__ */ new Date()
2544
+ };
2545
+ const updates = [];
2546
+ const values = [];
2547
+ if (workingMemory !== void 0) {
2548
+ updates.push("workingMemory = ?");
2549
+ values.push(workingMemory);
2550
+ }
2551
+ if (metadata) {
2552
+ updates.push("metadata = ?");
2553
+ values.push(JSON.stringify(updatedResource.metadata));
2554
+ }
2555
+ updates.push("updatedAt = ?");
2556
+ values.push(updatedResource.updatedAt.toISOString());
2557
+ values.push(resourceId);
2558
+ await this.#client.execute({
2559
+ sql: `UPDATE ${TABLE_RESOURCES} SET ${updates.join(", ")} WHERE id = ?`,
2560
+ args: values
2561
+ });
2562
+ return updatedResource;
1976
2563
  }
1977
- async getAITrace(traceId) {
2564
+ async getThreadById({ threadId }) {
1978
2565
  try {
1979
- const spans = await this.operations.loadMany({
1980
- tableName: TABLE_AI_SPANS,
1981
- whereClause: { sql: " WHERE traceId = ?", args: [traceId] },
1982
- orderBy: "startedAt DESC"
2566
+ const result = await this.#db.select({
2567
+ tableName: TABLE_THREADS,
2568
+ keys: { id: threadId }
1983
2569
  });
1984
- if (!spans || spans.length === 0) {
2570
+ if (!result) {
1985
2571
  return null;
1986
2572
  }
1987
2573
  return {
1988
- traceId,
1989
- spans: spans.map((span) => transformFromSqlRow({ tableName: TABLE_AI_SPANS, sqlRow: span }))
2574
+ ...result,
2575
+ metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata,
2576
+ createdAt: new Date(result.createdAt),
2577
+ updatedAt: new Date(result.updatedAt)
1990
2578
  };
1991
2579
  } catch (error) {
1992
2580
  throw new MastraError(
1993
2581
  {
1994
- id: "LIBSQL_STORE_GET_AI_TRACE_FAILED",
2582
+ id: createStorageErrorId("LIBSQL", "GET_THREAD_BY_ID", "FAILED"),
1995
2583
  domain: ErrorDomain.STORAGE,
1996
- category: ErrorCategory.USER,
1997
- details: {
1998
- traceId
1999
- }
2584
+ category: ErrorCategory.THIRD_PARTY,
2585
+ details: { threadId }
2000
2586
  },
2001
2587
  error
2002
2588
  );
2003
2589
  }
2004
2590
  }
2005
- async updateAISpan({
2006
- spanId,
2007
- traceId,
2008
- updates
2009
- }) {
2010
- try {
2011
- await this.operations.update({
2012
- tableName: TABLE_AI_SPANS,
2013
- keys: { spanId, traceId },
2014
- data: { ...updates, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }
2015
- });
2016
- } catch (error) {
2591
+ async listThreadsByResourceId(args) {
2592
+ const { resourceId, page = 0, perPage: perPageInput, orderBy } = args;
2593
+ if (page < 0) {
2017
2594
  throw new MastraError(
2018
2595
  {
2019
- id: "LIBSQL_STORE_UPDATE_AI_SPAN_FAILED",
2596
+ id: createStorageErrorId("LIBSQL", "LIST_THREADS_BY_RESOURCE_ID", "INVALID_PAGE"),
2020
2597
  domain: ErrorDomain.STORAGE,
2021
2598
  category: ErrorCategory.USER,
2022
- details: {
2023
- spanId,
2024
- traceId
2025
- }
2599
+ details: { page }
2026
2600
  },
2027
- error
2601
+ new Error("page must be >= 0")
2028
2602
  );
2029
2603
  }
2030
- }
2031
- async getAITracesPaginated({
2032
- filters,
2033
- pagination
2034
- }) {
2035
- const page = pagination?.page ?? 0;
2036
- const perPage = pagination?.perPage ?? 10;
2037
- const { entityId, entityType, ...actualFilters } = filters || {};
2038
- const filtersWithDateRange = {
2039
- ...actualFilters,
2040
- ...buildDateRangeFilter(pagination?.dateRange, "startedAt"),
2041
- parentSpanId: null
2042
- };
2043
- const whereClause = prepareWhereClause(filtersWithDateRange, AI_SPAN_SCHEMA);
2044
- let actualWhereClause = whereClause.sql || "";
2045
- if (entityId && entityType) {
2046
- const statement = `name = ?`;
2047
- let name = "";
2048
- if (entityType === "workflow") {
2049
- name = `workflow run: '${entityId}'`;
2050
- } else if (entityType === "agent") {
2051
- name = `agent run: '${entityId}'`;
2052
- } else {
2053
- const error = new MastraError({
2054
- id: "LIBSQL_STORE_GET_AI_TRACES_PAGINATED_FAILED",
2055
- domain: ErrorDomain.STORAGE,
2056
- category: ErrorCategory.USER,
2057
- details: {
2058
- entityType
2059
- },
2060
- text: `Cannot filter by entity type: ${entityType}`
2061
- });
2062
- this.logger?.trackException(error);
2063
- throw error;
2064
- }
2065
- whereClause.args.push(name);
2066
- if (actualWhereClause) {
2067
- actualWhereClause += ` AND ${statement}`;
2068
- } else {
2069
- actualWhereClause += `WHERE ${statement}`;
2070
- }
2071
- }
2072
- const orderBy = "startedAt DESC";
2073
- let count = 0;
2604
+ const perPage = normalizePerPage(perPageInput, 100);
2605
+ const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
2606
+ const { field, direction } = this.parseOrderBy(orderBy);
2074
2607
  try {
2075
- count = await this.operations.loadTotalCount({
2076
- tableName: TABLE_AI_SPANS,
2077
- whereClause: { sql: actualWhereClause, args: whereClause.args }
2608
+ const baseQuery = `FROM ${TABLE_THREADS} WHERE resourceId = ?`;
2609
+ const queryParams = [resourceId];
2610
+ const mapRowToStorageThreadType = (row) => ({
2611
+ id: row.id,
2612
+ resourceId: row.resourceId,
2613
+ title: row.title,
2614
+ createdAt: new Date(row.createdAt),
2615
+ // Convert string to Date
2616
+ updatedAt: new Date(row.updatedAt),
2617
+ // Convert string to Date
2618
+ metadata: typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata
2078
2619
  });
2079
- } catch (error) {
2080
- throw new MastraError(
2081
- {
2082
- id: "LIBSQL_STORE_GET_AI_TRACES_PAGINATED_COUNT_FAILED",
2083
- domain: ErrorDomain.STORAGE,
2084
- category: ErrorCategory.USER
2085
- },
2086
- error
2087
- );
2088
- }
2089
- if (count === 0) {
2090
- return {
2091
- pagination: {
2620
+ const countResult = await this.#client.execute({
2621
+ sql: `SELECT COUNT(*) as count ${baseQuery}`,
2622
+ args: queryParams
2623
+ });
2624
+ const total = Number(countResult.rows?.[0]?.count ?? 0);
2625
+ if (total === 0) {
2626
+ return {
2627
+ threads: [],
2092
2628
  total: 0,
2093
2629
  page,
2094
- perPage,
2630
+ perPage: perPageForResponse,
2095
2631
  hasMore: false
2096
- },
2097
- spans: []
2098
- };
2099
- }
2100
- try {
2101
- const spans = await this.operations.loadMany({
2102
- tableName: TABLE_AI_SPANS,
2103
- whereClause: {
2104
- sql: actualWhereClause,
2105
- args: whereClause.args
2106
- },
2107
- orderBy,
2108
- offset: page * perPage,
2109
- limit: perPage
2632
+ };
2633
+ }
2634
+ const limitValue = perPageInput === false ? total : perPage;
2635
+ const dataResult = await this.#client.execute({
2636
+ sql: `SELECT * ${baseQuery} ORDER BY "${field}" ${direction} LIMIT ? OFFSET ?`,
2637
+ args: [...queryParams, limitValue, offset]
2110
2638
  });
2639
+ const threads = (dataResult.rows || []).map(mapRowToStorageThreadType);
2111
2640
  return {
2112
- pagination: {
2113
- total: count,
2114
- page,
2115
- perPage,
2116
- hasMore: spans.length === perPage
2117
- },
2118
- spans: spans.map((span) => transformFromSqlRow({ tableName: TABLE_AI_SPANS, sqlRow: span }))
2641
+ threads,
2642
+ total,
2643
+ page,
2644
+ perPage: perPageForResponse,
2645
+ hasMore: perPageInput === false ? false : offset + perPage < total
2119
2646
  };
2120
2647
  } catch (error) {
2121
- throw new MastraError(
2648
+ const mastraError = new MastraError(
2122
2649
  {
2123
- id: "LIBSQL_STORE_GET_AI_TRACES_PAGINATED_FAILED",
2650
+ id: createStorageErrorId("LIBSQL", "LIST_THREADS_BY_RESOURCE_ID", "FAILED"),
2124
2651
  domain: ErrorDomain.STORAGE,
2125
- category: ErrorCategory.USER
2652
+ category: ErrorCategory.THIRD_PARTY,
2653
+ details: { resourceId }
2126
2654
  },
2127
2655
  error
2128
2656
  );
2657
+ this.logger?.trackException?.(mastraError);
2658
+ this.logger?.error?.(mastraError.toString());
2659
+ return {
2660
+ threads: [],
2661
+ total: 0,
2662
+ page,
2663
+ perPage: perPageForResponse,
2664
+ hasMore: false
2665
+ };
2129
2666
  }
2130
2667
  }
2131
- async batchCreateAISpans(args) {
2668
+ async saveThread({ thread }) {
2132
2669
  try {
2133
- const now = (/* @__PURE__ */ new Date()).toISOString();
2134
- return this.operations.batchInsert({
2135
- tableName: TABLE_AI_SPANS,
2136
- records: args.records.map((record) => ({
2137
- ...record,
2138
- createdAt: now,
2139
- updatedAt: now
2140
- }))
2670
+ await this.#db.insert({
2671
+ tableName: TABLE_THREADS,
2672
+ record: {
2673
+ ...thread,
2674
+ metadata: JSON.stringify(thread.metadata)
2675
+ }
2141
2676
  });
2677
+ return thread;
2142
2678
  } catch (error) {
2143
- throw new MastraError(
2679
+ const mastraError = new MastraError(
2144
2680
  {
2145
- id: "LIBSQL_STORE_BATCH_CREATE_AI_SPANS_FAILED",
2681
+ id: createStorageErrorId("LIBSQL", "SAVE_THREAD", "FAILED"),
2146
2682
  domain: ErrorDomain.STORAGE,
2147
- category: ErrorCategory.USER
2683
+ category: ErrorCategory.THIRD_PARTY,
2684
+ details: { threadId: thread.id }
2148
2685
  },
2149
2686
  error
2150
2687
  );
2688
+ this.logger?.trackException?.(mastraError);
2689
+ this.logger?.error?.(mastraError.toString());
2690
+ throw mastraError;
2151
2691
  }
2152
2692
  }
2153
- async batchUpdateAISpans(args) {
2693
+ async updateThread({
2694
+ id,
2695
+ title,
2696
+ metadata
2697
+ }) {
2698
+ const thread = await this.getThreadById({ threadId: id });
2699
+ if (!thread) {
2700
+ throw new MastraError({
2701
+ id: createStorageErrorId("LIBSQL", "UPDATE_THREAD", "NOT_FOUND"),
2702
+ domain: ErrorDomain.STORAGE,
2703
+ category: ErrorCategory.USER,
2704
+ text: `Thread ${id} not found`,
2705
+ details: {
2706
+ status: 404,
2707
+ threadId: id
2708
+ }
2709
+ });
2710
+ }
2711
+ const updatedThread = {
2712
+ ...thread,
2713
+ title,
2714
+ metadata: {
2715
+ ...thread.metadata,
2716
+ ...metadata
2717
+ }
2718
+ };
2154
2719
  try {
2155
- return this.operations.batchUpdate({
2156
- tableName: TABLE_AI_SPANS,
2157
- updates: args.records.map((record) => ({
2158
- keys: { spanId: record.spanId, traceId: record.traceId },
2159
- data: { ...record.updates, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }
2160
- }))
2720
+ await this.#client.execute({
2721
+ sql: `UPDATE ${TABLE_THREADS} SET title = ?, metadata = ? WHERE id = ?`,
2722
+ args: [title, JSON.stringify(updatedThread.metadata), id]
2161
2723
  });
2724
+ return updatedThread;
2162
2725
  } catch (error) {
2163
2726
  throw new MastraError(
2164
2727
  {
2165
- id: "LIBSQL_STORE_BATCH_UPDATE_AI_SPANS_FAILED",
2728
+ id: createStorageErrorId("LIBSQL", "UPDATE_THREAD", "FAILED"),
2166
2729
  domain: ErrorDomain.STORAGE,
2167
- category: ErrorCategory.USER
2730
+ category: ErrorCategory.THIRD_PARTY,
2731
+ text: `Failed to update thread ${id}`,
2732
+ details: { threadId: id }
2168
2733
  },
2169
2734
  error
2170
2735
  );
2171
2736
  }
2172
2737
  }
2173
- async batchDeleteAITraces(args) {
2738
+ async deleteThread({ threadId }) {
2174
2739
  try {
2175
- const keys = args.traceIds.map((traceId) => ({ traceId }));
2176
- return this.operations.batchDelete({
2177
- tableName: TABLE_AI_SPANS,
2178
- keys
2740
+ await this.#client.execute({
2741
+ sql: `DELETE FROM ${TABLE_MESSAGES} WHERE thread_id = ?`,
2742
+ args: [threadId]
2743
+ });
2744
+ await this.#client.execute({
2745
+ sql: `DELETE FROM ${TABLE_THREADS} WHERE id = ?`,
2746
+ args: [threadId]
2179
2747
  });
2180
2748
  } catch (error) {
2181
2749
  throw new MastraError(
2182
2750
  {
2183
- id: "LIBSQL_STORE_BATCH_DELETE_AI_TRACES_FAILED",
2751
+ id: createStorageErrorId("LIBSQL", "DELETE_THREAD", "FAILED"),
2184
2752
  domain: ErrorDomain.STORAGE,
2185
- category: ErrorCategory.USER
2753
+ category: ErrorCategory.THIRD_PARTY,
2754
+ details: { threadId }
2186
2755
  },
2187
2756
  error
2188
2757
  );
2189
2758
  }
2190
2759
  }
2191
2760
  };
2192
- var StoreOperationsLibSQL = class extends StoreOperations {
2193
- client;
2194
- /**
2195
- * Maximum number of retries for write operations if an SQLITE_BUSY error occurs.
2196
- * @default 5
2197
- */
2198
- maxRetries;
2199
- /**
2200
- * Initial backoff time in milliseconds for retrying write operations on SQLITE_BUSY.
2201
- * The backoff time will double with each retry (exponential backoff).
2202
- * @default 100
2203
- */
2204
- initialBackoffMs;
2205
- constructor({
2206
- client,
2207
- maxRetries,
2208
- initialBackoffMs
2209
- }) {
2761
+ var ObservabilityLibSQL = class extends ObservabilityStorage {
2762
+ #db;
2763
+ constructor(config) {
2210
2764
  super();
2211
- this.client = client;
2212
- this.maxRetries = maxRetries ?? 5;
2213
- this.initialBackoffMs = initialBackoffMs ?? 100;
2214
- }
2215
- async hasColumn(table, column) {
2216
- const result = await this.client.execute({
2217
- sql: `PRAGMA table_info(${table})`
2218
- });
2219
- return (await result.rows)?.some((row) => row.name === column);
2765
+ const client = resolveClient(config);
2766
+ this.#db = new LibSQLDB({ client, maxRetries: config.maxRetries, initialBackoffMs: config.initialBackoffMs });
2220
2767
  }
2221
- getCreateTableSQL(tableName, schema) {
2222
- const parsedTableName = parseSqlIdentifier(tableName, "table name");
2223
- const columns = Object.entries(schema).map(([name, col]) => {
2224
- const parsedColumnName = parseSqlIdentifier(name, "column name");
2225
- let type = col.type.toUpperCase();
2226
- if (type === "TEXT") type = "TEXT";
2227
- if (type === "TIMESTAMP") type = "TEXT";
2228
- const nullable = col.nullable ? "" : "NOT NULL";
2229
- const primaryKey = col.primaryKey ? "PRIMARY KEY" : "";
2230
- return `${parsedColumnName} ${type} ${nullable} ${primaryKey}`.trim();
2231
- });
2232
- if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
2233
- const stmnt = `CREATE TABLE IF NOT EXISTS ${parsedTableName} (
2234
- ${columns.join(",\n")},
2235
- PRIMARY KEY (workflow_name, run_id)
2236
- )`;
2237
- return stmnt;
2238
- }
2239
- if (tableName === TABLE_AI_SPANS) {
2240
- const stmnt = `CREATE TABLE IF NOT EXISTS ${parsedTableName} (
2241
- ${columns.join(",\n")},
2242
- PRIMARY KEY (traceId, spanId)
2243
- )`;
2244
- return stmnt;
2245
- }
2246
- return `CREATE TABLE IF NOT EXISTS ${parsedTableName} (${columns.join(", ")})`;
2768
+ async init() {
2769
+ await this.#db.createTable({ tableName: TABLE_SPANS, schema: SPAN_SCHEMA });
2247
2770
  }
2248
- async createTable({
2249
- tableName,
2250
- schema
2251
- }) {
2771
+ async dangerouslyClearAll() {
2772
+ await this.#db.deleteData({ tableName: TABLE_SPANS });
2773
+ }
2774
+ async createSpan(span) {
2252
2775
  try {
2253
- this.logger.debug(`Creating database table`, { tableName, operation: "schema init" });
2254
- const sql = this.getCreateTableSQL(tableName, schema);
2255
- await this.client.execute(sql);
2776
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2777
+ const record = {
2778
+ ...span,
2779
+ createdAt: now,
2780
+ updatedAt: now
2781
+ };
2782
+ return this.#db.insert({ tableName: TABLE_SPANS, record });
2256
2783
  } catch (error) {
2257
2784
  throw new MastraError(
2258
2785
  {
2259
- id: "LIBSQL_STORE_CREATE_TABLE_FAILED",
2786
+ id: createStorageErrorId("LIBSQL", "CREATE_SPAN", "FAILED"),
2260
2787
  domain: ErrorDomain.STORAGE,
2261
- category: ErrorCategory.THIRD_PARTY,
2788
+ category: ErrorCategory.USER,
2262
2789
  details: {
2263
- tableName
2790
+ spanId: span.spanId,
2791
+ traceId: span.traceId,
2792
+ spanType: span.spanType,
2793
+ spanName: span.name
2264
2794
  }
2265
2795
  },
2266
2796
  error
2267
2797
  );
2268
2798
  }
2269
2799
  }
2270
- getSqlType(type) {
2271
- switch (type) {
2272
- case "bigint":
2273
- return "INTEGER";
2274
- // SQLite uses INTEGER for all integer sizes
2275
- case "jsonb":
2276
- return "TEXT";
2277
- // Store JSON as TEXT in SQLite
2278
- default:
2279
- return super.getSqlType(type);
2280
- }
2281
- }
2282
- async doInsert({
2283
- tableName,
2284
- record
2285
- }) {
2286
- await this.client.execute(
2287
- prepareStatement({
2288
- tableName,
2289
- record
2290
- })
2291
- );
2292
- }
2293
- insert(args) {
2294
- const executeWriteOperationWithRetry = createExecuteWriteOperationWithRetry({
2295
- logger: this.logger,
2296
- maxRetries: this.maxRetries,
2297
- initialBackoffMs: this.initialBackoffMs
2298
- });
2299
- return executeWriteOperationWithRetry(() => this.doInsert(args), `insert into table ${args.tableName}`);
2300
- }
2301
- async load({ tableName, keys }) {
2302
- const parsedTableName = parseSqlIdentifier(tableName, "table name");
2303
- const parsedKeys = Object.keys(keys).map((key) => parseSqlIdentifier(key, "column name"));
2304
- const conditions = parsedKeys.map((key) => `${key} = ?`).join(" AND ");
2305
- const values = Object.values(keys);
2306
- const result = await this.client.execute({
2307
- sql: `SELECT * FROM ${parsedTableName} WHERE ${conditions} ORDER BY createdAt DESC LIMIT 1`,
2308
- args: values
2309
- });
2310
- if (!result.rows || result.rows.length === 0) {
2311
- return null;
2312
- }
2313
- const row = result.rows[0];
2314
- const parsed = Object.fromEntries(
2315
- Object.entries(row || {}).map(([k, v]) => {
2316
- try {
2317
- return [k, typeof v === "string" ? v.startsWith("{") || v.startsWith("[") ? JSON.parse(v) : v : v];
2318
- } catch {
2319
- return [k, v];
2320
- }
2321
- })
2322
- );
2323
- return parsed;
2324
- }
2325
- async loadMany({
2326
- tableName,
2327
- whereClause,
2328
- orderBy,
2329
- offset,
2330
- limit,
2331
- args
2332
- }) {
2333
- const parsedTableName = parseSqlIdentifier(tableName, "table name");
2334
- let statement = `SELECT * FROM ${parsedTableName}`;
2335
- if (whereClause?.sql) {
2336
- statement += `${whereClause.sql}`;
2337
- }
2338
- if (orderBy) {
2339
- statement += ` ORDER BY ${orderBy}`;
2340
- }
2341
- if (limit) {
2342
- statement += ` LIMIT ${limit}`;
2343
- }
2344
- if (offset) {
2345
- statement += ` OFFSET ${offset}`;
2346
- }
2347
- const result = await this.client.execute({
2348
- sql: statement,
2349
- args: [...whereClause?.args ?? [], ...args ?? []]
2350
- });
2351
- return result.rows;
2352
- }
2353
- async loadTotalCount({
2354
- tableName,
2355
- whereClause
2356
- }) {
2357
- const parsedTableName = parseSqlIdentifier(tableName, "table name");
2358
- const statement = `SELECT COUNT(*) as count FROM ${parsedTableName} ${whereClause ? `${whereClause.sql}` : ""}`;
2359
- const result = await this.client.execute({
2360
- sql: statement,
2361
- args: whereClause?.args ?? []
2362
- });
2363
- if (!result.rows || result.rows.length === 0) {
2364
- return 0;
2365
- }
2366
- return result.rows[0]?.count ?? 0;
2367
- }
2368
- update(args) {
2369
- const executeWriteOperationWithRetry = createExecuteWriteOperationWithRetry({
2370
- logger: this.logger,
2371
- maxRetries: this.maxRetries,
2372
- initialBackoffMs: this.initialBackoffMs
2373
- });
2374
- return executeWriteOperationWithRetry(() => this.executeUpdate(args), `update table ${args.tableName}`);
2375
- }
2376
- async executeUpdate({
2377
- tableName,
2378
- keys,
2379
- data
2380
- }) {
2381
- await this.client.execute(prepareUpdateStatement({ tableName, updates: data, keys }));
2382
- }
2383
- async doBatchInsert({
2384
- tableName,
2385
- records
2386
- }) {
2387
- if (records.length === 0) return;
2388
- const batchStatements = records.map((r) => prepareStatement({ tableName, record: r }));
2389
- await this.client.batch(batchStatements, "write");
2390
- }
2391
- batchInsert(args) {
2392
- const executeWriteOperationWithRetry = createExecuteWriteOperationWithRetry({
2393
- logger: this.logger,
2394
- maxRetries: this.maxRetries,
2395
- initialBackoffMs: this.initialBackoffMs
2396
- });
2397
- return executeWriteOperationWithRetry(
2398
- () => this.doBatchInsert(args),
2399
- `batch insert into table ${args.tableName}`
2400
- ).catch((error) => {
2800
+ async getTrace(traceId) {
2801
+ try {
2802
+ const spans = await this.#db.selectMany({
2803
+ tableName: TABLE_SPANS,
2804
+ whereClause: { sql: " WHERE traceId = ?", args: [traceId] },
2805
+ orderBy: "startedAt DESC"
2806
+ });
2807
+ if (!spans || spans.length === 0) {
2808
+ return null;
2809
+ }
2810
+ return {
2811
+ traceId,
2812
+ spans: spans.map((span) => transformFromSqlRow({ tableName: TABLE_SPANS, sqlRow: span }))
2813
+ };
2814
+ } catch (error) {
2401
2815
  throw new MastraError(
2402
2816
  {
2403
- id: "LIBSQL_STORE_BATCH_INSERT_FAILED",
2817
+ id: createStorageErrorId("LIBSQL", "GET_TRACE", "FAILED"),
2404
2818
  domain: ErrorDomain.STORAGE,
2405
- category: ErrorCategory.THIRD_PARTY,
2819
+ category: ErrorCategory.USER,
2406
2820
  details: {
2407
- tableName: args.tableName
2821
+ traceId
2408
2822
  }
2409
2823
  },
2410
2824
  error
2411
2825
  );
2412
- });
2826
+ }
2413
2827
  }
2414
- /**
2415
- * Public batch update method with retry logic
2416
- */
2417
- batchUpdate(args) {
2418
- const executeWriteOperationWithRetry = createExecuteWriteOperationWithRetry({
2419
- logger: this.logger,
2420
- maxRetries: this.maxRetries,
2421
- initialBackoffMs: this.initialBackoffMs
2422
- });
2423
- return executeWriteOperationWithRetry(
2424
- () => this.executeBatchUpdate(args),
2425
- `batch update in table ${args.tableName}`
2426
- ).catch((error) => {
2828
+ async updateSpan({
2829
+ spanId,
2830
+ traceId,
2831
+ updates
2832
+ }) {
2833
+ try {
2834
+ await this.#db.update({
2835
+ tableName: TABLE_SPANS,
2836
+ keys: { spanId, traceId },
2837
+ data: { ...updates, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }
2838
+ });
2839
+ } catch (error) {
2427
2840
  throw new MastraError(
2428
2841
  {
2429
- id: "LIBSQL_STORE_BATCH_UPDATE_FAILED",
2842
+ id: createStorageErrorId("LIBSQL", "UPDATE_SPAN", "FAILED"),
2430
2843
  domain: ErrorDomain.STORAGE,
2431
- category: ErrorCategory.THIRD_PARTY,
2844
+ category: ErrorCategory.USER,
2432
2845
  details: {
2433
- tableName: args.tableName
2846
+ spanId,
2847
+ traceId
2434
2848
  }
2435
2849
  },
2436
2850
  error
2437
2851
  );
2438
- });
2852
+ }
2439
2853
  }
2440
- /**
2441
- * Updates multiple records in batch. Each record can be updated based on single or composite keys.
2442
- */
2443
- async executeBatchUpdate({
2444
- tableName,
2445
- updates
2854
+ async getTracesPaginated({
2855
+ filters,
2856
+ pagination
2446
2857
  }) {
2447
- if (updates.length === 0) return;
2448
- const batchStatements = updates.map(
2449
- ({ keys, data }) => prepareUpdateStatement({
2450
- tableName,
2451
- updates: data,
2452
- keys
2453
- })
2454
- );
2455
- await this.client.batch(batchStatements, "write");
2456
- }
2457
- /**
2458
- * Public batch delete method with retry logic
2459
- */
2460
- batchDelete({ tableName, keys }) {
2461
- const executeWriteOperationWithRetry = createExecuteWriteOperationWithRetry({
2462
- logger: this.logger,
2463
- maxRetries: this.maxRetries,
2464
- initialBackoffMs: this.initialBackoffMs
2465
- });
2466
- return executeWriteOperationWithRetry(
2467
- () => this.executeBatchDelete({ tableName, keys }),
2468
- `batch delete from table ${tableName}`
2469
- ).catch((error) => {
2858
+ const page = pagination?.page ?? 0;
2859
+ const perPage = pagination?.perPage ?? 10;
2860
+ const { entityId, entityType, ...actualFilters } = filters || {};
2861
+ const filtersWithDateRange = {
2862
+ ...actualFilters,
2863
+ ...buildDateRangeFilter(pagination?.dateRange, "startedAt"),
2864
+ parentSpanId: null
2865
+ };
2866
+ const whereClause = prepareWhereClause(filtersWithDateRange, SPAN_SCHEMA);
2867
+ let actualWhereClause = whereClause.sql || "";
2868
+ if (entityId && entityType) {
2869
+ const statement = `name = ?`;
2870
+ let name = "";
2871
+ if (entityType === "workflow") {
2872
+ name = `workflow run: '${entityId}'`;
2873
+ } else if (entityType === "agent") {
2874
+ name = `agent run: '${entityId}'`;
2875
+ } else {
2876
+ const error = new MastraError({
2877
+ id: createStorageErrorId("LIBSQL", "GET_TRACES_PAGINATED", "INVALID_ENTITY_TYPE"),
2878
+ domain: ErrorDomain.STORAGE,
2879
+ category: ErrorCategory.USER,
2880
+ details: {
2881
+ entityType
2882
+ },
2883
+ text: `Cannot filter by entity type: ${entityType}`
2884
+ });
2885
+ this.logger?.trackException(error);
2886
+ throw error;
2887
+ }
2888
+ whereClause.args.push(name);
2889
+ if (actualWhereClause) {
2890
+ actualWhereClause += ` AND ${statement}`;
2891
+ } else {
2892
+ actualWhereClause += `WHERE ${statement}`;
2893
+ }
2894
+ }
2895
+ const orderBy = "startedAt DESC";
2896
+ let count = 0;
2897
+ try {
2898
+ count = await this.#db.selectTotalCount({
2899
+ tableName: TABLE_SPANS,
2900
+ whereClause: { sql: actualWhereClause, args: whereClause.args }
2901
+ });
2902
+ } catch (error) {
2903
+ throw new MastraError(
2904
+ {
2905
+ id: createStorageErrorId("LIBSQL", "GET_TRACES_PAGINATED", "COUNT_FAILED"),
2906
+ domain: ErrorDomain.STORAGE,
2907
+ category: ErrorCategory.USER
2908
+ },
2909
+ error
2910
+ );
2911
+ }
2912
+ if (count === 0) {
2913
+ return {
2914
+ pagination: {
2915
+ total: 0,
2916
+ page,
2917
+ perPage,
2918
+ hasMore: false
2919
+ },
2920
+ spans: []
2921
+ };
2922
+ }
2923
+ try {
2924
+ const spans = await this.#db.selectMany({
2925
+ tableName: TABLE_SPANS,
2926
+ whereClause: {
2927
+ sql: actualWhereClause,
2928
+ args: whereClause.args
2929
+ },
2930
+ orderBy,
2931
+ offset: page * perPage,
2932
+ limit: perPage
2933
+ });
2934
+ return {
2935
+ pagination: {
2936
+ total: count,
2937
+ page,
2938
+ perPage,
2939
+ hasMore: spans.length === perPage
2940
+ },
2941
+ spans: spans.map((span) => transformFromSqlRow({ tableName: TABLE_SPANS, sqlRow: span }))
2942
+ };
2943
+ } catch (error) {
2470
2944
  throw new MastraError(
2471
2945
  {
2472
- id: "LIBSQL_STORE_BATCH_DELETE_FAILED",
2946
+ id: createStorageErrorId("LIBSQL", "GET_TRACES_PAGINATED", "FAILED"),
2473
2947
  domain: ErrorDomain.STORAGE,
2474
- category: ErrorCategory.THIRD_PARTY,
2475
- details: {
2476
- tableName
2477
- }
2948
+ category: ErrorCategory.USER
2478
2949
  },
2479
2950
  error
2480
2951
  );
2481
- });
2482
- }
2483
- /**
2484
- * Deletes multiple records in batch. Each record can be deleted based on single or composite keys.
2485
- */
2486
- async executeBatchDelete({
2487
- tableName,
2488
- keys
2489
- }) {
2490
- if (keys.length === 0) return;
2491
- const batchStatements = keys.map(
2492
- (keyObj) => prepareDeleteStatement({
2493
- tableName,
2494
- keys: keyObj
2495
- })
2496
- );
2497
- await this.client.batch(batchStatements, "write");
2952
+ }
2498
2953
  }
2499
- /**
2500
- * Alters table schema to add columns if they don't exist
2501
- * @param tableName Name of the table
2502
- * @param schema Schema of the table
2503
- * @param ifNotExists Array of column names to add if they don't exist
2504
- */
2505
- async alterTable({
2506
- tableName,
2507
- schema,
2508
- ifNotExists
2509
- }) {
2510
- const parsedTableName = parseSqlIdentifier(tableName, "table name");
2954
+ async batchCreateSpans(args) {
2511
2955
  try {
2512
- const pragmaQuery = `PRAGMA table_info(${parsedTableName})`;
2513
- const result = await this.client.execute(pragmaQuery);
2514
- const existingColumnNames = new Set(result.rows.map((row) => row.name.toLowerCase()));
2515
- for (const columnName of ifNotExists) {
2516
- if (!existingColumnNames.has(columnName.toLowerCase()) && schema[columnName]) {
2517
- const columnDef = schema[columnName];
2518
- const sqlType = this.getSqlType(columnDef.type);
2519
- const nullable = columnDef.nullable === false ? "NOT NULL" : "";
2520
- const defaultValue = columnDef.nullable === false ? this.getDefaultValue(columnDef.type) : "";
2521
- const alterSql = `ALTER TABLE ${parsedTableName} ADD COLUMN "${columnName}" ${sqlType} ${nullable} ${defaultValue}`.trim();
2522
- await this.client.execute(alterSql);
2523
- this.logger?.debug?.(`Added column ${columnName} to table ${parsedTableName}`);
2524
- }
2525
- }
2956
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2957
+ return this.#db.batchInsert({
2958
+ tableName: TABLE_SPANS,
2959
+ records: args.records.map((record) => ({
2960
+ ...record,
2961
+ createdAt: now,
2962
+ updatedAt: now
2963
+ }))
2964
+ });
2526
2965
  } catch (error) {
2527
2966
  throw new MastraError(
2528
2967
  {
2529
- id: "LIBSQL_STORE_ALTER_TABLE_FAILED",
2968
+ id: createStorageErrorId("LIBSQL", "BATCH_CREATE_SPANS", "FAILED"),
2530
2969
  domain: ErrorDomain.STORAGE,
2531
- category: ErrorCategory.THIRD_PARTY,
2532
- details: {
2533
- tableName
2534
- }
2970
+ category: ErrorCategory.USER
2535
2971
  },
2536
2972
  error
2537
2973
  );
2538
2974
  }
2539
2975
  }
2540
- async clearTable({ tableName }) {
2541
- const parsedTableName = parseSqlIdentifier(tableName, "table name");
2976
+ async batchUpdateSpans(args) {
2542
2977
  try {
2543
- await this.client.execute(`DELETE FROM ${parsedTableName}`);
2544
- } catch (e) {
2545
- const mastraError = new MastraError(
2978
+ return this.#db.batchUpdate({
2979
+ tableName: TABLE_SPANS,
2980
+ updates: args.records.map((record) => ({
2981
+ keys: { spanId: record.spanId, traceId: record.traceId },
2982
+ data: { ...record.updates, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }
2983
+ }))
2984
+ });
2985
+ } catch (error) {
2986
+ throw new MastraError(
2546
2987
  {
2547
- id: "LIBSQL_STORE_CLEAR_TABLE_FAILED",
2988
+ id: createStorageErrorId("LIBSQL", "BATCH_UPDATE_SPANS", "FAILED"),
2548
2989
  domain: ErrorDomain.STORAGE,
2549
- category: ErrorCategory.THIRD_PARTY,
2550
- details: {
2551
- tableName
2552
- }
2990
+ category: ErrorCategory.USER
2553
2991
  },
2554
- e
2992
+ error
2555
2993
  );
2556
- this.logger?.trackException?.(mastraError);
2557
- this.logger?.error?.(mastraError.toString());
2558
2994
  }
2559
2995
  }
2560
- async dropTable({ tableName }) {
2561
- const parsedTableName = parseSqlIdentifier(tableName, "table name");
2996
+ async batchDeleteTraces(args) {
2562
2997
  try {
2563
- await this.client.execute(`DROP TABLE IF EXISTS ${parsedTableName}`);
2564
- } catch (e) {
2998
+ const keys = args.traceIds.map((traceId) => ({ traceId }));
2999
+ return this.#db.batchDelete({
3000
+ tableName: TABLE_SPANS,
3001
+ keys
3002
+ });
3003
+ } catch (error) {
2565
3004
  throw new MastraError(
2566
3005
  {
2567
- id: "LIBSQL_STORE_DROP_TABLE_FAILED",
3006
+ id: createStorageErrorId("LIBSQL", "BATCH_DELETE_TRACES", "FAILED"),
2568
3007
  domain: ErrorDomain.STORAGE,
2569
- category: ErrorCategory.THIRD_PARTY,
2570
- details: {
2571
- tableName
2572
- }
3008
+ category: ErrorCategory.USER
2573
3009
  },
2574
- e
3010
+ error
2575
3011
  );
2576
3012
  }
2577
3013
  }
2578
3014
  };
2579
3015
  var ScoresLibSQL = class extends ScoresStorage {
2580
- operations;
2581
- client;
2582
- constructor({ client, operations }) {
3016
+ #db;
3017
+ #client;
3018
+ constructor(config) {
2583
3019
  super();
2584
- this.operations = operations;
2585
- this.client = client;
3020
+ const client = resolveClient(config);
3021
+ this.#client = client;
3022
+ this.#db = new LibSQLDB({ client, maxRetries: config.maxRetries, initialBackoffMs: config.initialBackoffMs });
3023
+ }
3024
+ async init() {
3025
+ await this.#db.createTable({ tableName: TABLE_SCORERS, schema: SCORERS_SCHEMA });
3026
+ await this.#db.alterTable({
3027
+ tableName: TABLE_SCORERS,
3028
+ schema: SCORERS_SCHEMA,
3029
+ ifNotExists: ["spanId", "requestContext"]
3030
+ });
3031
+ }
3032
+ async dangerouslyClearAll() {
3033
+ await this.#db.deleteData({ tableName: TABLE_SCORERS });
2586
3034
  }
2587
- async getScoresByRunId({
3035
+ async listScoresByRunId({
2588
3036
  runId,
2589
3037
  pagination
2590
3038
  }) {
2591
3039
  try {
2592
- const result = await this.client.execute({
3040
+ const { page, perPage: perPageInput } = pagination;
3041
+ const countResult = await this.#client.execute({
3042
+ sql: `SELECT COUNT(*) as count FROM ${TABLE_SCORERS} WHERE runId = ?`,
3043
+ args: [runId]
3044
+ });
3045
+ const total = Number(countResult.rows?.[0]?.count ?? 0);
3046
+ if (total === 0) {
3047
+ return {
3048
+ pagination: {
3049
+ total: 0,
3050
+ page,
3051
+ perPage: perPageInput,
3052
+ hasMore: false
3053
+ },
3054
+ scores: []
3055
+ };
3056
+ }
3057
+ const perPage = normalizePerPage(perPageInput, 100);
3058
+ const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
3059
+ const limitValue = perPageInput === false ? total : perPage;
3060
+ const end = perPageInput === false ? total : start + perPage;
3061
+ const result = await this.#client.execute({
2593
3062
  sql: `SELECT * FROM ${TABLE_SCORERS} WHERE runId = ? ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
2594
- args: [runId, pagination.perPage + 1, pagination.page * pagination.perPage]
3063
+ args: [runId, limitValue, start]
2595
3064
  });
3065
+ const scores = result.rows?.map((row) => this.transformScoreRow(row)) ?? [];
2596
3066
  return {
2597
- scores: result.rows?.slice(0, pagination.perPage).map((row) => this.transformScoreRow(row)) ?? [],
3067
+ scores,
2598
3068
  pagination: {
2599
- total: result.rows?.length ?? 0,
2600
- page: pagination.page,
2601
- perPage: pagination.perPage,
2602
- hasMore: result.rows?.length > pagination.perPage
3069
+ total,
3070
+ page,
3071
+ perPage: perPageForResponse,
3072
+ hasMore: end < total
2603
3073
  }
2604
3074
  };
2605
3075
  } catch (error) {
2606
3076
  throw new MastraError(
2607
3077
  {
2608
- id: "LIBSQL_STORE_GET_SCORES_BY_RUN_ID_FAILED",
3078
+ id: createStorageErrorId("LIBSQL", "LIST_SCORES_BY_RUN_ID", "FAILED"),
2609
3079
  domain: ErrorDomain.STORAGE,
2610
3080
  category: ErrorCategory.THIRD_PARTY
2611
3081
  },
@@ -2613,7 +3083,7 @@ var ScoresLibSQL = class extends ScoresStorage {
2613
3083
  );
2614
3084
  }
2615
3085
  }
2616
- async getScoresByScorerId({
3086
+ async listScoresByScorerId({
2617
3087
  scorerId,
2618
3088
  entityId,
2619
3089
  entityType,
@@ -2621,6 +3091,7 @@ var ScoresLibSQL = class extends ScoresStorage {
2621
3091
  pagination
2622
3092
  }) {
2623
3093
  try {
3094
+ const { page, perPage: perPageInput } = pagination;
2624
3095
  const conditions = [];
2625
3096
  const queryParams = [];
2626
3097
  if (scorerId) {
@@ -2640,23 +3111,44 @@ var ScoresLibSQL = class extends ScoresStorage {
2640
3111
  queryParams.push(source);
2641
3112
  }
2642
3113
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2643
- const result = await this.client.execute({
3114
+ const countResult = await this.#client.execute({
3115
+ sql: `SELECT COUNT(*) as count FROM ${TABLE_SCORERS} ${whereClause}`,
3116
+ args: queryParams
3117
+ });
3118
+ const total = Number(countResult.rows?.[0]?.count ?? 0);
3119
+ if (total === 0) {
3120
+ return {
3121
+ pagination: {
3122
+ total: 0,
3123
+ page,
3124
+ perPage: perPageInput,
3125
+ hasMore: false
3126
+ },
3127
+ scores: []
3128
+ };
3129
+ }
3130
+ const perPage = normalizePerPage(perPageInput, 100);
3131
+ const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
3132
+ const limitValue = perPageInput === false ? total : perPage;
3133
+ const end = perPageInput === false ? total : start + perPage;
3134
+ const result = await this.#client.execute({
2644
3135
  sql: `SELECT * FROM ${TABLE_SCORERS} ${whereClause} ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
2645
- args: [...queryParams, pagination.perPage + 1, pagination.page * pagination.perPage]
3136
+ args: [...queryParams, limitValue, start]
2646
3137
  });
3138
+ const scores = result.rows?.map((row) => this.transformScoreRow(row)) ?? [];
2647
3139
  return {
2648
- scores: result.rows?.slice(0, pagination.perPage).map((row) => this.transformScoreRow(row)) ?? [],
3140
+ scores,
2649
3141
  pagination: {
2650
- total: result.rows?.length ?? 0,
2651
- page: pagination.page,
2652
- perPage: pagination.perPage,
2653
- hasMore: result.rows?.length > pagination.perPage
3142
+ total,
3143
+ page,
3144
+ perPage: perPageForResponse,
3145
+ hasMore: end < total
2654
3146
  }
2655
3147
  };
2656
3148
  } catch (error) {
2657
3149
  throw new MastraError(
2658
3150
  {
2659
- id: "LIBSQL_STORE_GET_SCORES_BY_SCORER_ID_FAILED",
3151
+ id: createStorageErrorId("LIBSQL", "LIST_SCORES_BY_SCORER_ID", "FAILED"),
2660
3152
  domain: ErrorDomain.STORAGE,
2661
3153
  category: ErrorCategory.THIRD_PARTY
2662
3154
  },
@@ -2664,48 +3156,17 @@ var ScoresLibSQL = class extends ScoresStorage {
2664
3156
  );
2665
3157
  }
2666
3158
  }
3159
+ /**
3160
+ * LibSQL-specific score row transformation.
3161
+ * Maps additionalLLMContext column to additionalContext field.
3162
+ */
2667
3163
  transformScoreRow(row) {
2668
- const scorerValue = safelyParseJSON(row.scorer);
2669
- const inputValue = safelyParseJSON(row.input ?? "{}");
2670
- const outputValue = safelyParseJSON(row.output ?? "{}");
2671
- const additionalLLMContextValue = row.additionalLLMContext ? safelyParseJSON(row.additionalLLMContext) : null;
2672
- const runtimeContextValue = row.runtimeContext ? safelyParseJSON(row.runtimeContext) : null;
2673
- const metadataValue = row.metadata ? safelyParseJSON(row.metadata) : null;
2674
- const entityValue = row.entity ? safelyParseJSON(row.entity) : null;
2675
- const preprocessStepResultValue = row.preprocessStepResult ? safelyParseJSON(row.preprocessStepResult) : null;
2676
- const analyzeStepResultValue = row.analyzeStepResult ? safelyParseJSON(row.analyzeStepResult) : null;
2677
- return {
2678
- id: row.id,
2679
- traceId: row.traceId,
2680
- spanId: row.spanId,
2681
- runId: row.runId,
2682
- scorer: scorerValue,
2683
- score: row.score,
2684
- reason: row.reason,
2685
- preprocessStepResult: preprocessStepResultValue,
2686
- analyzeStepResult: analyzeStepResultValue,
2687
- analyzePrompt: row.analyzePrompt,
2688
- preprocessPrompt: row.preprocessPrompt,
2689
- generateScorePrompt: row.generateScorePrompt,
2690
- generateReasonPrompt: row.generateReasonPrompt,
2691
- metadata: metadataValue,
2692
- input: inputValue,
2693
- output: outputValue,
2694
- additionalContext: additionalLLMContextValue,
2695
- runtimeContext: runtimeContextValue,
2696
- entityType: row.entityType,
2697
- entity: entityValue,
2698
- entityId: row.entityId,
2699
- scorerId: row.scorerId,
2700
- source: row.source,
2701
- resourceId: row.resourceId,
2702
- threadId: row.threadId,
2703
- createdAt: row.createdAt,
2704
- updatedAt: row.updatedAt
2705
- };
3164
+ return transformScoreRow(row, {
3165
+ fieldMappings: { additionalContext: "additionalLLMContext" }
3166
+ });
2706
3167
  }
2707
3168
  async getScoreById({ id }) {
2708
- const result = await this.client.execute({
3169
+ const result = await this.#client.execute({
2709
3170
  sql: `SELECT * FROM ${TABLE_SCORERS} WHERE id = ?`,
2710
3171
  args: [id]
2711
3172
  });
@@ -2718,15 +3179,15 @@ var ScoresLibSQL = class extends ScoresStorage {
2718
3179
  } catch (error) {
2719
3180
  throw new MastraError(
2720
3181
  {
2721
- id: "LIBSQL_STORE_SAVE_SCORE_FAILED_INVALID_SCORE_PAYLOAD",
3182
+ id: createStorageErrorId("LIBSQL", "SAVE_SCORE", "VALIDATION_FAILED"),
2722
3183
  domain: ErrorDomain.STORAGE,
2723
3184
  category: ErrorCategory.USER,
2724
3185
  details: {
2725
- scorer: score.scorer.name,
2726
- entityId: score.entityId,
2727
- entityType: score.entityType,
2728
- traceId: score.traceId || "",
2729
- spanId: score.spanId || ""
3186
+ scorer: score.scorer?.id ?? "unknown",
3187
+ entityId: score.entityId ?? "unknown",
3188
+ entityType: score.entityType ?? "unknown",
3189
+ traceId: score.traceId ?? "",
3190
+ spanId: score.spanId ?? ""
2730
3191
  }
2731
3192
  },
2732
3193
  error
@@ -2734,21 +3195,21 @@ var ScoresLibSQL = class extends ScoresStorage {
2734
3195
  }
2735
3196
  try {
2736
3197
  const id = crypto.randomUUID();
2737
- await this.operations.insert({
3198
+ const now = /* @__PURE__ */ new Date();
3199
+ await this.#db.insert({
2738
3200
  tableName: TABLE_SCORERS,
2739
3201
  record: {
3202
+ ...parsedScore,
2740
3203
  id,
2741
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2742
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2743
- ...parsedScore
3204
+ createdAt: now.toISOString(),
3205
+ updatedAt: now.toISOString()
2744
3206
  }
2745
3207
  });
2746
- const scoreFromDb = await this.getScoreById({ id });
2747
- return { score: scoreFromDb };
3208
+ return { score: { ...parsedScore, id, createdAt: now, updatedAt: now } };
2748
3209
  } catch (error) {
2749
3210
  throw new MastraError(
2750
3211
  {
2751
- id: "LIBSQL_STORE_SAVE_SCORE_FAILED",
3212
+ id: createStorageErrorId("LIBSQL", "SAVE_SCORE", "FAILED"),
2752
3213
  domain: ErrorDomain.STORAGE,
2753
3214
  category: ErrorCategory.THIRD_PARTY
2754
3215
  },
@@ -2756,29 +3217,51 @@ var ScoresLibSQL = class extends ScoresStorage {
2756
3217
  );
2757
3218
  }
2758
3219
  }
2759
- async getScoresByEntityId({
3220
+ async listScoresByEntityId({
2760
3221
  entityId,
2761
3222
  entityType,
2762
3223
  pagination
2763
3224
  }) {
2764
3225
  try {
2765
- const result = await this.client.execute({
3226
+ const { page, perPage: perPageInput } = pagination;
3227
+ const countResult = await this.#client.execute({
3228
+ sql: `SELECT COUNT(*) as count FROM ${TABLE_SCORERS} WHERE entityId = ? AND entityType = ?`,
3229
+ args: [entityId, entityType]
3230
+ });
3231
+ const total = Number(countResult.rows?.[0]?.count ?? 0);
3232
+ if (total === 0) {
3233
+ return {
3234
+ pagination: {
3235
+ total: 0,
3236
+ page,
3237
+ perPage: perPageInput,
3238
+ hasMore: false
3239
+ },
3240
+ scores: []
3241
+ };
3242
+ }
3243
+ const perPage = normalizePerPage(perPageInput, 100);
3244
+ const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
3245
+ const limitValue = perPageInput === false ? total : perPage;
3246
+ const end = perPageInput === false ? total : start + perPage;
3247
+ const result = await this.#client.execute({
2766
3248
  sql: `SELECT * FROM ${TABLE_SCORERS} WHERE entityId = ? AND entityType = ? ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
2767
- args: [entityId, entityType, pagination.perPage + 1, pagination.page * pagination.perPage]
3249
+ args: [entityId, entityType, limitValue, start]
2768
3250
  });
3251
+ const scores = result.rows?.map((row) => this.transformScoreRow(row)) ?? [];
2769
3252
  return {
2770
- scores: result.rows?.slice(0, pagination.perPage).map((row) => this.transformScoreRow(row)) ?? [],
3253
+ scores,
2771
3254
  pagination: {
2772
- total: result.rows?.length ?? 0,
2773
- page: pagination.page,
2774
- perPage: pagination.perPage,
2775
- hasMore: result.rows?.length > pagination.perPage
3255
+ total,
3256
+ page,
3257
+ perPage: perPageForResponse,
3258
+ hasMore: end < total
2776
3259
  }
2777
3260
  };
2778
3261
  } catch (error) {
2779
3262
  throw new MastraError(
2780
3263
  {
2781
- id: "LIBSQL_STORE_GET_SCORES_BY_ENTITY_ID_FAILED",
3264
+ id: createStorageErrorId("LIBSQL", "LIST_SCORES_BY_ENTITY_ID", "FAILED"),
2782
3265
  domain: ErrorDomain.STORAGE,
2783
3266
  category: ErrorCategory.THIRD_PARTY
2784
3267
  },
@@ -2786,36 +3269,40 @@ var ScoresLibSQL = class extends ScoresStorage {
2786
3269
  );
2787
3270
  }
2788
3271
  }
2789
- async getScoresBySpan({
3272
+ async listScoresBySpan({
2790
3273
  traceId,
2791
3274
  spanId,
2792
3275
  pagination
2793
3276
  }) {
2794
3277
  try {
2795
- const countSQLResult = await this.client.execute({
3278
+ const { page, perPage: perPageInput } = pagination;
3279
+ const perPage = normalizePerPage(perPageInput, 100);
3280
+ const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
3281
+ const countSQLResult = await this.#client.execute({
2796
3282
  sql: `SELECT COUNT(*) as count FROM ${TABLE_SCORERS} WHERE traceId = ? AND spanId = ?`,
2797
3283
  args: [traceId, spanId]
2798
3284
  });
2799
3285
  const total = Number(countSQLResult.rows?.[0]?.count ?? 0);
2800
- const result = await this.client.execute({
3286
+ const limitValue = perPageInput === false ? total : perPage;
3287
+ const end = perPageInput === false ? total : start + perPage;
3288
+ const result = await this.#client.execute({
2801
3289
  sql: `SELECT * FROM ${TABLE_SCORERS} WHERE traceId = ? AND spanId = ? ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
2802
- args: [traceId, spanId, pagination.perPage + 1, pagination.page * pagination.perPage]
3290
+ args: [traceId, spanId, limitValue, start]
2803
3291
  });
2804
- const hasMore = result.rows?.length > pagination.perPage;
2805
- const scores = result.rows?.slice(0, pagination.perPage).map((row) => this.transformScoreRow(row)) ?? [];
3292
+ const scores = result.rows?.map((row) => this.transformScoreRow(row)) ?? [];
2806
3293
  return {
2807
3294
  scores,
2808
3295
  pagination: {
2809
3296
  total,
2810
- page: pagination.page,
2811
- perPage: pagination.perPage,
2812
- hasMore
3297
+ page,
3298
+ perPage: perPageForResponse,
3299
+ hasMore: end < total
2813
3300
  }
2814
3301
  };
2815
3302
  } catch (error) {
2816
3303
  throw new MastraError(
2817
3304
  {
2818
- id: "LIBSQL_STORE_GET_SCORES_BY_SPAN_FAILED",
3305
+ id: createStorageErrorId("LIBSQL", "LIST_SCORES_BY_SPAN", "FAILED"),
2819
3306
  domain: ErrorDomain.STORAGE,
2820
3307
  category: ErrorCategory.THIRD_PARTY
2821
3308
  },
@@ -2843,37 +3330,49 @@ function parseWorkflowRun(row) {
2843
3330
  };
2844
3331
  }
2845
3332
  var WorkflowsLibSQL = class extends WorkflowsStorage {
2846
- operations;
2847
- client;
2848
- maxRetries;
2849
- initialBackoffMs;
2850
- constructor({
2851
- operations,
2852
- client,
2853
- maxRetries = 5,
2854
- initialBackoffMs = 500
2855
- }) {
3333
+ #db;
3334
+ #client;
3335
+ executeWithRetry;
3336
+ constructor(config) {
2856
3337
  super();
2857
- this.operations = operations;
2858
- this.client = client;
2859
- this.maxRetries = maxRetries;
2860
- this.initialBackoffMs = initialBackoffMs;
3338
+ const client = resolveClient(config);
3339
+ const maxRetries = config.maxRetries ?? 5;
3340
+ const initialBackoffMs = config.initialBackoffMs ?? 500;
3341
+ this.#client = client;
3342
+ this.#db = new LibSQLDB({ client, maxRetries, initialBackoffMs });
3343
+ this.executeWithRetry = createExecuteWriteOperationWithRetry({
3344
+ logger: this.logger,
3345
+ maxRetries,
3346
+ initialBackoffMs
3347
+ });
2861
3348
  this.setupPragmaSettings().catch(
2862
3349
  (err) => this.logger.warn("LibSQL Workflows: Failed to setup PRAGMA settings.", err)
2863
3350
  );
2864
3351
  }
3352
+ async init() {
3353
+ const schema = TABLE_SCHEMAS[TABLE_WORKFLOW_SNAPSHOT];
3354
+ await this.#db.createTable({ tableName: TABLE_WORKFLOW_SNAPSHOT, schema });
3355
+ await this.#db.alterTable({
3356
+ tableName: TABLE_WORKFLOW_SNAPSHOT,
3357
+ schema,
3358
+ ifNotExists: ["resourceId"]
3359
+ });
3360
+ }
3361
+ async dangerouslyClearAll() {
3362
+ await this.#db.deleteData({ tableName: TABLE_WORKFLOW_SNAPSHOT });
3363
+ }
2865
3364
  async setupPragmaSettings() {
2866
3365
  try {
2867
- await this.client.execute("PRAGMA busy_timeout = 10000;");
3366
+ await this.#client.execute("PRAGMA busy_timeout = 10000;");
2868
3367
  this.logger.debug("LibSQL Workflows: PRAGMA busy_timeout=10000 set.");
2869
3368
  try {
2870
- await this.client.execute("PRAGMA journal_mode = WAL;");
3369
+ await this.#client.execute("PRAGMA journal_mode = WAL;");
2871
3370
  this.logger.debug("LibSQL Workflows: PRAGMA journal_mode=WAL set.");
2872
3371
  } catch {
2873
3372
  this.logger.debug("LibSQL Workflows: WAL mode not supported, using default journal mode.");
2874
3373
  }
2875
3374
  try {
2876
- await this.client.execute("PRAGMA synchronous = NORMAL;");
3375
+ await this.#client.execute("PRAGMA synchronous = NORMAL;");
2877
3376
  this.logger.debug("LibSQL Workflows: PRAGMA synchronous=NORMAL set.");
2878
3377
  } catch {
2879
3378
  this.logger.debug("LibSQL Workflows: Failed to set synchronous mode.");
@@ -2882,53 +3381,15 @@ var WorkflowsLibSQL = class extends WorkflowsStorage {
2882
3381
  this.logger.warn("LibSQL Workflows: Failed to set PRAGMA settings.", err);
2883
3382
  }
2884
3383
  }
2885
- async executeWithRetry(operation) {
2886
- let attempts = 0;
2887
- let backoff = this.initialBackoffMs;
2888
- while (attempts < this.maxRetries) {
2889
- try {
2890
- return await operation();
2891
- } catch (error) {
2892
- this.logger.debug("LibSQL Workflows: Error caught in retry loop", {
2893
- errorType: error.constructor.name,
2894
- errorCode: error.code,
2895
- errorMessage: error.message,
2896
- attempts,
2897
- maxRetries: this.maxRetries
2898
- });
2899
- const isLockError = error.code === "SQLITE_BUSY" || error.code === "SQLITE_LOCKED" || error.message?.toLowerCase().includes("database is locked") || error.message?.toLowerCase().includes("database table is locked") || error.message?.toLowerCase().includes("table is locked") || error.constructor.name === "SqliteError" && error.message?.toLowerCase().includes("locked");
2900
- if (isLockError) {
2901
- attempts++;
2902
- if (attempts >= this.maxRetries) {
2903
- this.logger.error(
2904
- `LibSQL Workflows: Operation failed after ${this.maxRetries} attempts due to database lock: ${error.message}`,
2905
- { error, attempts, maxRetries: this.maxRetries }
2906
- );
2907
- throw error;
2908
- }
2909
- this.logger.warn(
2910
- `LibSQL Workflows: Attempt ${attempts} failed due to database lock. Retrying in ${backoff}ms...`,
2911
- { errorMessage: error.message, attempts, backoff, maxRetries: this.maxRetries }
2912
- );
2913
- await new Promise((resolve) => setTimeout(resolve, backoff));
2914
- backoff *= 2;
2915
- } else {
2916
- this.logger.error("LibSQL Workflows: Non-lock error occurred, not retrying", { error });
2917
- throw error;
2918
- }
2919
- }
2920
- }
2921
- throw new Error("LibSQL Workflows: Max retries reached, but no error was re-thrown from the loop.");
2922
- }
2923
3384
  async updateWorkflowResults({
2924
3385
  workflowName,
2925
3386
  runId,
2926
3387
  stepId,
2927
3388
  result,
2928
- runtimeContext
3389
+ requestContext
2929
3390
  }) {
2930
3391
  return this.executeWithRetry(async () => {
2931
- const tx = await this.client.transaction("write");
3392
+ const tx = await this.#client.transaction("write");
2932
3393
  try {
2933
3394
  const existingSnapshotResult = await tx.execute({
2934
3395
  sql: `SELECT snapshot FROM ${TABLE_WORKFLOW_SNAPSHOT} WHERE workflow_name = ? AND run_id = ?`,
@@ -2941,20 +3402,21 @@ var WorkflowsLibSQL = class extends WorkflowsStorage {
2941
3402
  activePaths: [],
2942
3403
  timestamp: Date.now(),
2943
3404
  suspendedPaths: {},
3405
+ activeStepsPath: {},
2944
3406
  resumeLabels: {},
2945
3407
  serializedStepGraph: [],
3408
+ status: "pending",
2946
3409
  value: {},
2947
3410
  waitingPaths: {},
2948
- status: "pending",
2949
3411
  runId,
2950
- runtimeContext: {}
3412
+ requestContext: {}
2951
3413
  };
2952
3414
  } else {
2953
3415
  const existingSnapshot = existingSnapshotResult.rows[0].snapshot;
2954
3416
  snapshot = typeof existingSnapshot === "string" ? JSON.parse(existingSnapshot) : existingSnapshot;
2955
3417
  }
2956
3418
  snapshot.context[stepId] = result;
2957
- snapshot.runtimeContext = { ...snapshot.runtimeContext, ...runtimeContext };
3419
+ snapshot.requestContext = { ...snapshot.requestContext, ...requestContext };
2958
3420
  await tx.execute({
2959
3421
  sql: `UPDATE ${TABLE_WORKFLOW_SNAPSHOT} SET snapshot = ? WHERE workflow_name = ? AND run_id = ?`,
2960
3422
  args: [JSON.stringify(snapshot), workflowName, runId]
@@ -2967,7 +3429,7 @@ var WorkflowsLibSQL = class extends WorkflowsStorage {
2967
3429
  }
2968
3430
  throw error;
2969
3431
  }
2970
- });
3432
+ }, "updateWorkflowResults");
2971
3433
  }
2972
3434
  async updateWorkflowState({
2973
3435
  workflowName,
@@ -2975,7 +3437,7 @@ var WorkflowsLibSQL = class extends WorkflowsStorage {
2975
3437
  opts
2976
3438
  }) {
2977
3439
  return this.executeWithRetry(async () => {
2978
- const tx = await this.client.transaction("write");
3440
+ const tx = await this.#client.transaction("write");
2979
3441
  try {
2980
3442
  const existingSnapshotResult = await tx.execute({
2981
3443
  sql: `SELECT snapshot FROM ${TABLE_WORKFLOW_SNAPSHOT} WHERE workflow_name = ? AND run_id = ?`,
@@ -3004,24 +3466,27 @@ var WorkflowsLibSQL = class extends WorkflowsStorage {
3004
3466
  }
3005
3467
  throw error;
3006
3468
  }
3007
- });
3469
+ }, "updateWorkflowState");
3008
3470
  }
3009
3471
  async persistWorkflowSnapshot({
3010
3472
  workflowName,
3011
3473
  runId,
3012
3474
  resourceId,
3013
- snapshot
3475
+ snapshot,
3476
+ createdAt,
3477
+ updatedAt
3014
3478
  }) {
3479
+ const now = /* @__PURE__ */ new Date();
3015
3480
  const data = {
3016
3481
  workflow_name: workflowName,
3017
3482
  run_id: runId,
3018
3483
  resourceId,
3019
3484
  snapshot,
3020
- createdAt: /* @__PURE__ */ new Date(),
3021
- updatedAt: /* @__PURE__ */ new Date()
3485
+ createdAt: createdAt ?? now,
3486
+ updatedAt: updatedAt ?? now
3022
3487
  };
3023
3488
  this.logger.debug("Persisting workflow snapshot", { workflowName, runId, data });
3024
- await this.operations.insert({
3489
+ await this.#db.insert({
3025
3490
  tableName: TABLE_WORKFLOW_SNAPSHOT,
3026
3491
  record: data
3027
3492
  });
@@ -3031,7 +3496,7 @@ var WorkflowsLibSQL = class extends WorkflowsStorage {
3031
3496
  runId
3032
3497
  }) {
3033
3498
  this.logger.debug("Loading workflow snapshot", { workflowName, runId });
3034
- const d = await this.operations.load({
3499
+ const d = await this.#db.select({
3035
3500
  tableName: TABLE_WORKFLOW_SNAPSHOT,
3036
3501
  keys: { workflow_name: workflowName, run_id: runId }
3037
3502
  });
@@ -3053,7 +3518,7 @@ var WorkflowsLibSQL = class extends WorkflowsStorage {
3053
3518
  }
3054
3519
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
3055
3520
  try {
3056
- const result = await this.client.execute({
3521
+ const result = await this.#client.execute({
3057
3522
  sql: `SELECT * FROM ${TABLE_WORKFLOW_SNAPSHOT} ${whereClause} ORDER BY createdAt DESC LIMIT 1`,
3058
3523
  args
3059
3524
  });
@@ -3064,7 +3529,7 @@ var WorkflowsLibSQL = class extends WorkflowsStorage {
3064
3529
  } catch (error) {
3065
3530
  throw new MastraError(
3066
3531
  {
3067
- id: "LIBSQL_STORE_GET_WORKFLOW_RUN_BY_ID_FAILED",
3532
+ id: createStorageErrorId("LIBSQL", "GET_WORKFLOW_RUN_BY_ID", "FAILED"),
3068
3533
  domain: ErrorDomain.STORAGE,
3069
3534
  category: ErrorCategory.THIRD_PARTY
3070
3535
  },
@@ -3072,13 +3537,34 @@ var WorkflowsLibSQL = class extends WorkflowsStorage {
3072
3537
  );
3073
3538
  }
3074
3539
  }
3075
- async getWorkflowRuns({
3540
+ async deleteWorkflowRunById({ runId, workflowName }) {
3541
+ return this.executeWithRetry(async () => {
3542
+ try {
3543
+ await this.#client.execute({
3544
+ sql: `DELETE FROM ${TABLE_WORKFLOW_SNAPSHOT} WHERE workflow_name = ? AND run_id = ?`,
3545
+ args: [workflowName, runId]
3546
+ });
3547
+ } catch (error) {
3548
+ throw new MastraError(
3549
+ {
3550
+ id: createStorageErrorId("LIBSQL", "DELETE_WORKFLOW_RUN_BY_ID", "FAILED"),
3551
+ domain: ErrorDomain.STORAGE,
3552
+ category: ErrorCategory.THIRD_PARTY,
3553
+ details: { runId, workflowName }
3554
+ },
3555
+ error
3556
+ );
3557
+ }
3558
+ }, "deleteWorkflowRunById");
3559
+ }
3560
+ async listWorkflowRuns({
3076
3561
  workflowName,
3077
3562
  fromDate,
3078
3563
  toDate,
3079
- limit,
3080
- offset,
3081
- resourceId
3564
+ page,
3565
+ perPage,
3566
+ resourceId,
3567
+ status
3082
3568
  } = {}) {
3083
3569
  try {
3084
3570
  const conditions = [];
@@ -3087,6 +3573,10 @@ var WorkflowsLibSQL = class extends WorkflowsStorage {
3087
3573
  conditions.push("workflow_name = ?");
3088
3574
  args.push(workflowName);
3089
3575
  }
3576
+ if (status) {
3577
+ conditions.push("json_extract(snapshot, '$.status') = ?");
3578
+ args.push(status);
3579
+ }
3090
3580
  if (fromDate) {
3091
3581
  conditions.push("createdAt >= ?");
3092
3582
  args.push(fromDate.toISOString());
@@ -3096,7 +3586,7 @@ var WorkflowsLibSQL = class extends WorkflowsStorage {
3096
3586
  args.push(toDate.toISOString());
3097
3587
  }
3098
3588
  if (resourceId) {
3099
- const hasResourceId = await this.operations.hasColumn(TABLE_WORKFLOW_SNAPSHOT, "resourceId");
3589
+ const hasResourceId = await this.#db.hasColumn(TABLE_WORKFLOW_SNAPSHOT, "resourceId");
3100
3590
  if (hasResourceId) {
3101
3591
  conditions.push("resourceId = ?");
3102
3592
  args.push(resourceId);
@@ -3106,23 +3596,26 @@ var WorkflowsLibSQL = class extends WorkflowsStorage {
3106
3596
  }
3107
3597
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
3108
3598
  let total = 0;
3109
- if (limit !== void 0 && offset !== void 0) {
3110
- const countResult = await this.client.execute({
3599
+ const usePagination = typeof perPage === "number" && typeof page === "number";
3600
+ if (usePagination) {
3601
+ const countResult = await this.#client.execute({
3111
3602
  sql: `SELECT COUNT(*) as count FROM ${TABLE_WORKFLOW_SNAPSHOT} ${whereClause}`,
3112
3603
  args
3113
3604
  });
3114
3605
  total = Number(countResult.rows?.[0]?.count ?? 0);
3115
3606
  }
3116
- const result = await this.client.execute({
3117
- sql: `SELECT * FROM ${TABLE_WORKFLOW_SNAPSHOT} ${whereClause} ORDER BY createdAt DESC${limit !== void 0 && offset !== void 0 ? ` LIMIT ? OFFSET ?` : ""}`,
3118
- args: limit !== void 0 && offset !== void 0 ? [...args, limit, offset] : args
3607
+ const normalizedPerPage = usePagination ? normalizePerPage(perPage, Number.MAX_SAFE_INTEGER) : 0;
3608
+ const offset = usePagination ? page * normalizedPerPage : 0;
3609
+ const result = await this.#client.execute({
3610
+ sql: `SELECT * FROM ${TABLE_WORKFLOW_SNAPSHOT} ${whereClause} ORDER BY createdAt DESC${usePagination ? ` LIMIT ? OFFSET ?` : ""}`,
3611
+ args: usePagination ? [...args, normalizedPerPage, offset] : args
3119
3612
  });
3120
3613
  const runs = (result.rows || []).map((row) => parseWorkflowRun(row));
3121
3614
  return { runs, total: total || runs.length };
3122
3615
  } catch (error) {
3123
3616
  throw new MastraError(
3124
3617
  {
3125
- id: "LIBSQL_STORE_GET_WORKFLOW_RUNS_FAILED",
3618
+ id: createStorageErrorId("LIBSQL", "LIST_WORKFLOW_RUNS", "FAILED"),
3126
3619
  domain: ErrorDomain.STORAGE,
3127
3620
  category: ErrorCategory.THIRD_PARTY
3128
3621
  },
@@ -3139,7 +3632,10 @@ var LibSQLStore = class extends MastraStorage {
3139
3632
  initialBackoffMs;
3140
3633
  stores;
3141
3634
  constructor(config) {
3142
- super({ name: `LibSQLStore` });
3635
+ if (!config.id || typeof config.id !== "string" || config.id.trim() === "") {
3636
+ throw new Error("LibSQLStore: id must be provided and cannot be empty.");
3637
+ }
3638
+ super({ id: config.id, name: `LibSQLStore`, disableInit: config.disableInit });
3143
3639
  this.maxRetries = config.maxRetries ?? 5;
3144
3640
  this.initialBackoffMs = config.initialBackoffMs ?? 100;
3145
3641
  if ("url" in config) {
@@ -3157,23 +3653,22 @@ var LibSQLStore = class extends MastraStorage {
3157
3653
  } else {
3158
3654
  this.client = config.client;
3159
3655
  }
3160
- const operations = new StoreOperationsLibSQL({
3656
+ const domainConfig = {
3161
3657
  client: this.client,
3162
3658
  maxRetries: this.maxRetries,
3163
3659
  initialBackoffMs: this.initialBackoffMs
3164
- });
3165
- const scores = new ScoresLibSQL({ client: this.client, operations });
3166
- const workflows = new WorkflowsLibSQL({ client: this.client, operations });
3167
- const memory = new MemoryLibSQL({ client: this.client, operations });
3168
- const legacyEvals = new LegacyEvalsLibSQL({ client: this.client });
3169
- const observability = new ObservabilityLibSQL({ operations });
3660
+ };
3661
+ const scores = new ScoresLibSQL(domainConfig);
3662
+ const workflows = new WorkflowsLibSQL(domainConfig);
3663
+ const memory = new MemoryLibSQL(domainConfig);
3664
+ const observability = new ObservabilityLibSQL(domainConfig);
3665
+ const agents = new AgentsLibSQL(domainConfig);
3170
3666
  this.stores = {
3171
- operations,
3172
3667
  scores,
3173
3668
  workflows,
3174
3669
  memory,
3175
- legacyEvals,
3176
- observability
3670
+ observability,
3671
+ agents
3177
3672
  };
3178
3673
  }
3179
3674
  get supports() {
@@ -3183,56 +3678,14 @@ var LibSQLStore = class extends MastraStorage {
3183
3678
  hasColumn: true,
3184
3679
  createTable: true,
3185
3680
  deleteMessages: true,
3186
- aiTracing: true,
3187
- getScoresBySpan: true
3681
+ observabilityInstance: true,
3682
+ listScoresBySpan: true,
3683
+ agents: true
3188
3684
  };
3189
3685
  }
3190
- async createTable({
3191
- tableName,
3192
- schema
3193
- }) {
3194
- await this.stores.operations.createTable({ tableName, schema });
3195
- }
3196
- /**
3197
- * Alters table schema to add columns if they don't exist
3198
- * @param tableName Name of the table
3199
- * @param schema Schema of the table
3200
- * @param ifNotExists Array of column names to add if they don't exist
3201
- */
3202
- async alterTable({
3203
- tableName,
3204
- schema,
3205
- ifNotExists
3206
- }) {
3207
- await this.stores.operations.alterTable({ tableName, schema, ifNotExists });
3208
- }
3209
- async clearTable({ tableName }) {
3210
- await this.stores.operations.clearTable({ tableName });
3211
- }
3212
- async dropTable({ tableName }) {
3213
- await this.stores.operations.dropTable({ tableName });
3214
- }
3215
- insert(args) {
3216
- return this.stores.operations.insert(args);
3217
- }
3218
- batchInsert(args) {
3219
- return this.stores.operations.batchInsert(args);
3220
- }
3221
- async load({ tableName, keys }) {
3222
- return this.stores.operations.load({ tableName, keys });
3223
- }
3224
3686
  async getThreadById({ threadId }) {
3225
3687
  return this.stores.memory.getThreadById({ threadId });
3226
3688
  }
3227
- /**
3228
- * @deprecated use getThreadsByResourceIdPaginated instead for paginated results.
3229
- */
3230
- async getThreadsByResourceId(args) {
3231
- return this.stores.memory.getThreadsByResourceId(args);
3232
- }
3233
- async getThreadsByResourceIdPaginated(args) {
3234
- return this.stores.memory.getThreadsByResourceIdPaginated(args);
3235
- }
3236
3689
  async saveThread({ thread }) {
3237
3690
  return this.stores.memory.saveThread({ thread });
3238
3691
  }
@@ -3246,24 +3699,12 @@ var LibSQLStore = class extends MastraStorage {
3246
3699
  async deleteThread({ threadId }) {
3247
3700
  return this.stores.memory.deleteThread({ threadId });
3248
3701
  }
3249
- async getMessages({
3250
- threadId,
3251
- selectBy,
3252
- format
3253
- }) {
3254
- return this.stores.memory.getMessages({ threadId, selectBy, format });
3255
- }
3256
- async getMessagesById({
3257
- messageIds,
3258
- format
3259
- }) {
3260
- return this.stores.memory.getMessagesById({ messageIds, format });
3261
- }
3262
- async getMessagesPaginated(args) {
3263
- return this.stores.memory.getMessagesPaginated(args);
3702
+ async listMessagesById({ messageIds }) {
3703
+ return this.stores.memory.listMessagesById({ messageIds });
3264
3704
  }
3265
3705
  async saveMessages(args) {
3266
- return this.stores.memory.saveMessages(args);
3706
+ const result = await this.stores.memory.saveMessages({ messages: args.messages });
3707
+ return { messages: result.messages };
3267
3708
  }
3268
3709
  async updateMessages({
3269
3710
  messages
@@ -3273,40 +3714,33 @@ var LibSQLStore = class extends MastraStorage {
3273
3714
  async deleteMessages(messageIds) {
3274
3715
  return this.stores.memory.deleteMessages(messageIds);
3275
3716
  }
3276
- /** @deprecated use getEvals instead */
3277
- async getEvalsByAgentName(agentName, type) {
3278
- return this.stores.legacyEvals.getEvalsByAgentName(agentName, type);
3279
- }
3280
- async getEvals(options = {}) {
3281
- return this.stores.legacyEvals.getEvals(options);
3282
- }
3283
3717
  async getScoreById({ id }) {
3284
3718
  return this.stores.scores.getScoreById({ id });
3285
3719
  }
3286
3720
  async saveScore(score) {
3287
3721
  return this.stores.scores.saveScore(score);
3288
3722
  }
3289
- async getScoresByScorerId({
3723
+ async listScoresByScorerId({
3290
3724
  scorerId,
3291
3725
  entityId,
3292
3726
  entityType,
3293
3727
  source,
3294
3728
  pagination
3295
3729
  }) {
3296
- return this.stores.scores.getScoresByScorerId({ scorerId, entityId, entityType, source, pagination });
3730
+ return this.stores.scores.listScoresByScorerId({ scorerId, entityId, entityType, source, pagination });
3297
3731
  }
3298
- async getScoresByRunId({
3732
+ async listScoresByRunId({
3299
3733
  runId,
3300
3734
  pagination
3301
3735
  }) {
3302
- return this.stores.scores.getScoresByRunId({ runId, pagination });
3736
+ return this.stores.scores.listScoresByRunId({ runId, pagination });
3303
3737
  }
3304
- async getScoresByEntityId({
3738
+ async listScoresByEntityId({
3305
3739
  entityId,
3306
3740
  entityType,
3307
3741
  pagination
3308
3742
  }) {
3309
- return this.stores.scores.getScoresByEntityId({ entityId, entityType, pagination });
3743
+ return this.stores.scores.listScoresByEntityId({ entityId, entityType, pagination });
3310
3744
  }
3311
3745
  /**
3312
3746
  * WORKFLOWS
@@ -3316,9 +3750,9 @@ var LibSQLStore = class extends MastraStorage {
3316
3750
  runId,
3317
3751
  stepId,
3318
3752
  result,
3319
- runtimeContext
3753
+ requestContext
3320
3754
  }) {
3321
- return this.stores.workflows.updateWorkflowResults({ workflowName, runId, stepId, result, runtimeContext });
3755
+ return this.stores.workflows.updateWorkflowResults({ workflowName, runId, stepId, result, requestContext });
3322
3756
  }
3323
3757
  async updateWorkflowState({
3324
3758
  workflowName,
@@ -3341,15 +3775,8 @@ var LibSQLStore = class extends MastraStorage {
3341
3775
  }) {
3342
3776
  return this.stores.workflows.loadWorkflowSnapshot({ workflowName, runId });
3343
3777
  }
3344
- async getWorkflowRuns({
3345
- workflowName,
3346
- fromDate,
3347
- toDate,
3348
- limit,
3349
- offset,
3350
- resourceId
3351
- } = {}) {
3352
- return this.stores.workflows.getWorkflowRuns({ workflowName, fromDate, toDate, limit, offset, resourceId });
3778
+ async listWorkflowRuns(args = {}) {
3779
+ return this.stores.workflows.listWorkflowRuns(args);
3353
3780
  }
3354
3781
  async getWorkflowRunById({
3355
3782
  runId,
@@ -3357,6 +3784,9 @@ var LibSQLStore = class extends MastraStorage {
3357
3784
  }) {
3358
3785
  return this.stores.workflows.getWorkflowRunById({ runId, workflowName });
3359
3786
  }
3787
+ async deleteWorkflowRunById({ runId, workflowName }) {
3788
+ return this.stores.workflows.deleteWorkflowRunById({ runId, workflowName });
3789
+ }
3360
3790
  async getResourceById({ resourceId }) {
3361
3791
  return this.stores.memory.getResourceById({ resourceId });
3362
3792
  }
@@ -3370,30 +3800,30 @@ var LibSQLStore = class extends MastraStorage {
3370
3800
  }) {
3371
3801
  return this.stores.memory.updateResource({ resourceId, workingMemory, metadata });
3372
3802
  }
3373
- async createAISpan(span) {
3374
- return this.stores.observability.createAISpan(span);
3803
+ async createSpan(span) {
3804
+ return this.stores.observability.createSpan(span);
3375
3805
  }
3376
- async updateAISpan(params) {
3377
- return this.stores.observability.updateAISpan(params);
3806
+ async updateSpan(params) {
3807
+ return this.stores.observability.updateSpan(params);
3378
3808
  }
3379
- async getAITrace(traceId) {
3380
- return this.stores.observability.getAITrace(traceId);
3809
+ async getTrace(traceId) {
3810
+ return this.stores.observability.getTrace(traceId);
3381
3811
  }
3382
- async getAITracesPaginated(args) {
3383
- return this.stores.observability.getAITracesPaginated(args);
3812
+ async getTracesPaginated(args) {
3813
+ return this.stores.observability.getTracesPaginated(args);
3384
3814
  }
3385
- async getScoresBySpan({
3815
+ async listScoresBySpan({
3386
3816
  traceId,
3387
3817
  spanId,
3388
3818
  pagination
3389
3819
  }) {
3390
- return this.stores.scores.getScoresBySpan({ traceId, spanId, pagination });
3820
+ return this.stores.scores.listScoresBySpan({ traceId, spanId, pagination });
3391
3821
  }
3392
- async batchCreateAISpans(args) {
3393
- return this.stores.observability.batchCreateAISpans(args);
3822
+ async batchCreateSpans(args) {
3823
+ return this.stores.observability.batchCreateSpans(args);
3394
3824
  }
3395
- async batchUpdateAISpans(args) {
3396
- return this.stores.observability.batchUpdateAISpans(args);
3825
+ async batchUpdateSpans(args) {
3826
+ return this.stores.observability.batchUpdateSpans(args);
3397
3827
  }
3398
3828
  };
3399
3829