@mastra/libsql 1.0.0-beta.1 → 1.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
3
3
  import { parseSqlIdentifier, parseFieldKey } from '@mastra/core/utils';
4
4
  import { MastraVector } from '@mastra/core/vector';
5
5
  import { BaseFilterTranslator } from '@mastra/core/vector/filter';
6
- import { MastraStorage, StoreOperations, TABLE_WORKFLOW_SNAPSHOT, TABLE_SPANS, ScoresStorage, TABLE_SCORERS, normalizePerPage, calculatePagination, safelyParseJSON, WorkflowsStorage, MemoryStorage, TABLE_MESSAGES, TABLE_THREADS, TABLE_RESOURCES, ObservabilityStorage, TABLE_SCHEMAS, SPAN_SCHEMA } from '@mastra/core/storage';
6
+ import { MastraStorage, StoreOperations, TABLE_WORKFLOW_SNAPSHOT, TABLE_SPANS, ScoresStorage, TABLE_SCORERS, normalizePerPage, calculatePagination, transformScoreRow, WorkflowsStorage, MemoryStorage, TABLE_MESSAGES, TABLE_THREADS, TABLE_RESOURCES, ObservabilityStorage, TABLE_SCHEMAS, safelyParseJSON, SPAN_SCHEMA } from '@mastra/core/storage';
7
7
  import { MessageList } from '@mastra/core/agent';
8
8
  import { saveScorePayloadSchema } from '@mastra/core/evals';
9
9
 
@@ -90,12 +90,20 @@ var createBasicOperator = (symbol) => {
90
90
  };
91
91
  };
92
92
  var createNumericOperator = (symbol) => {
93
- return (key) => {
93
+ return (key, value) => {
94
94
  const jsonPath = getJsonPath(key);
95
- return {
96
- sql: `CAST(json_extract(metadata, ${jsonPath}) AS NUMERIC) ${symbol} ?`,
97
- needsValue: true
98
- };
95
+ const isNumeric = typeof value === "number" || typeof value === "string" && !isNaN(Number(value)) && value.trim() !== "";
96
+ if (isNumeric) {
97
+ return {
98
+ sql: `CAST(json_extract(metadata, ${jsonPath}) AS NUMERIC) ${symbol} ?`,
99
+ needsValue: true
100
+ };
101
+ } else {
102
+ return {
103
+ sql: `CAST(json_extract(metadata, ${jsonPath}) AS TEXT) ${symbol} ?`,
104
+ needsValue: true
105
+ };
106
+ }
99
107
  };
100
108
  };
101
109
  var validateJsonArray = (key) => {
@@ -824,8 +832,27 @@ var LibSQLVector = class extends MastraVector {
824
832
  updateVector(args) {
825
833
  return this.executeWriteOperationWithRetry(() => this.doUpdateVector(args));
826
834
  }
827
- async doUpdateVector({ indexName, id, update }) {
835
+ async doUpdateVector(params) {
836
+ const { indexName, update } = params;
828
837
  const parsedIndexName = parseSqlIdentifier(indexName, "index name");
838
+ if ("id" in params && params.id && "filter" in params && params.filter) {
839
+ throw new MastraError({
840
+ id: "LIBSQL_VECTOR_UPDATE_MUTUALLY_EXCLUSIVE_PARAMS",
841
+ domain: ErrorDomain.STORAGE,
842
+ category: ErrorCategory.USER,
843
+ details: { indexName },
844
+ text: "id and filter are mutually exclusive - provide only one"
845
+ });
846
+ }
847
+ if (!update.vector && !update.metadata) {
848
+ throw new MastraError({
849
+ id: "LIBSQL_VECTOR_UPDATE_VECTOR_INVALID_ARGS",
850
+ domain: ErrorDomain.STORAGE,
851
+ category: ErrorCategory.USER,
852
+ details: { indexName },
853
+ text: "No updates provided"
854
+ });
855
+ }
829
856
  const updates = [];
830
857
  const args = [];
831
858
  if (update.vector) {
@@ -837,32 +864,81 @@ var LibSQLVector = class extends MastraVector {
837
864
  args.push(JSON.stringify(update.metadata));
838
865
  }
839
866
  if (updates.length === 0) {
867
+ return;
868
+ }
869
+ let whereClause;
870
+ let whereValues;
871
+ if ("id" in params && params.id) {
872
+ whereClause = "vector_id = ?";
873
+ whereValues = [params.id];
874
+ } else if ("filter" in params && params.filter) {
875
+ const filter = params.filter;
876
+ if (!filter || Object.keys(filter).length === 0) {
877
+ throw new MastraError({
878
+ id: "LIBSQL_VECTOR_UPDATE_EMPTY_FILTER",
879
+ domain: ErrorDomain.STORAGE,
880
+ category: ErrorCategory.USER,
881
+ details: { indexName },
882
+ text: "Cannot update with empty filter"
883
+ });
884
+ }
885
+ const translatedFilter = this.transformFilter(filter);
886
+ const { sql: filterSql, values: filterValues } = buildFilterQuery(translatedFilter);
887
+ if (!filterSql || filterSql.trim() === "") {
888
+ throw new MastraError({
889
+ id: "LIBSQL_VECTOR_UPDATE_INVALID_FILTER",
890
+ domain: ErrorDomain.STORAGE,
891
+ category: ErrorCategory.USER,
892
+ details: { indexName },
893
+ text: "Filter produced empty WHERE clause"
894
+ });
895
+ }
896
+ const normalizedCondition = filterSql.replace(/^\s*WHERE\s+/i, "").trim().toLowerCase();
897
+ const matchAllPatterns = ["true", "1 = 1", "1=1"];
898
+ if (matchAllPatterns.includes(normalizedCondition)) {
899
+ throw new MastraError({
900
+ id: "LIBSQL_VECTOR_UPDATE_MATCH_ALL_FILTER",
901
+ domain: ErrorDomain.STORAGE,
902
+ category: ErrorCategory.USER,
903
+ details: { indexName, filterSql: normalizedCondition },
904
+ text: "Filter matches all vectors. Provide a specific filter to update targeted vectors."
905
+ });
906
+ }
907
+ whereClause = filterSql.replace(/^WHERE\s+/i, "");
908
+ whereValues = filterValues;
909
+ } else {
840
910
  throw new MastraError({
841
- id: "LIBSQL_VECTOR_UPDATE_VECTOR_INVALID_ARGS",
911
+ id: "LIBSQL_VECTOR_UPDATE_MISSING_PARAMS",
842
912
  domain: ErrorDomain.STORAGE,
843
913
  category: ErrorCategory.USER,
844
- details: { indexName, id },
845
- text: "No updates provided"
914
+ details: { indexName },
915
+ text: "Either id or filter must be provided"
846
916
  });
847
917
  }
848
- args.push(id);
849
918
  const query = `
850
- UPDATE ${parsedIndexName}
851
- SET ${updates.join(", ")}
852
- WHERE vector_id = ?;
853
- `;
919
+ UPDATE ${parsedIndexName}
920
+ SET ${updates.join(", ")}
921
+ WHERE ${whereClause};
922
+ `;
854
923
  try {
855
924
  await this.turso.execute({
856
925
  sql: query,
857
- args
926
+ args: [...args, ...whereValues]
858
927
  });
859
928
  } catch (error) {
929
+ const errorDetails = { indexName };
930
+ if ("id" in params && params.id) {
931
+ errorDetails.id = params.id;
932
+ }
933
+ if ("filter" in params && params.filter) {
934
+ errorDetails.filter = JSON.stringify(params.filter);
935
+ }
860
936
  throw new MastraError(
861
937
  {
862
938
  id: "LIBSQL_VECTOR_UPDATE_VECTOR_FAILED",
863
939
  domain: ErrorDomain.STORAGE,
864
940
  category: ErrorCategory.THIRD_PARTY,
865
- details: { indexName, id }
941
+ details: errorDetails
866
942
  },
867
943
  error
868
944
  );
@@ -884,7 +960,10 @@ var LibSQLVector = class extends MastraVector {
884
960
  id: "LIBSQL_VECTOR_DELETE_VECTOR_FAILED",
885
961
  domain: ErrorDomain.STORAGE,
886
962
  category: ErrorCategory.THIRD_PARTY,
887
- details: { indexName: args.indexName, id: args.id }
963
+ details: {
964
+ indexName: args.indexName,
965
+ ...args.id && { id: args.id }
966
+ }
888
967
  },
889
968
  error
890
969
  );
@@ -897,6 +976,100 @@ var LibSQLVector = class extends MastraVector {
897
976
  args: [id]
898
977
  });
899
978
  }
979
+ deleteVectors(args) {
980
+ return this.executeWriteOperationWithRetry(() => this.doDeleteVectors(args));
981
+ }
982
+ async doDeleteVectors({ indexName, filter, ids }) {
983
+ const parsedIndexName = parseSqlIdentifier(indexName, "index name");
984
+ if (!filter && !ids) {
985
+ throw new MastraError({
986
+ id: "LIBSQL_VECTOR_DELETE_MISSING_PARAMS",
987
+ domain: ErrorDomain.STORAGE,
988
+ category: ErrorCategory.USER,
989
+ details: { indexName },
990
+ text: "Either filter or ids must be provided"
991
+ });
992
+ }
993
+ if (filter && ids) {
994
+ throw new MastraError({
995
+ id: "LIBSQL_VECTOR_DELETE_CONFLICTING_PARAMS",
996
+ domain: ErrorDomain.STORAGE,
997
+ category: ErrorCategory.USER,
998
+ details: { indexName },
999
+ text: "Cannot provide both filter and ids - they are mutually exclusive"
1000
+ });
1001
+ }
1002
+ let query;
1003
+ let values;
1004
+ if (ids) {
1005
+ if (ids.length === 0) {
1006
+ throw new MastraError({
1007
+ id: "LIBSQL_VECTOR_DELETE_EMPTY_IDS",
1008
+ domain: ErrorDomain.STORAGE,
1009
+ category: ErrorCategory.USER,
1010
+ details: { indexName },
1011
+ text: "Cannot delete with empty ids array"
1012
+ });
1013
+ }
1014
+ const placeholders = ids.map(() => "?").join(", ");
1015
+ query = `DELETE FROM ${parsedIndexName} WHERE vector_id IN (${placeholders})`;
1016
+ values = ids;
1017
+ } else {
1018
+ if (!filter || Object.keys(filter).length === 0) {
1019
+ throw new MastraError({
1020
+ id: "LIBSQL_VECTOR_DELETE_EMPTY_FILTER",
1021
+ domain: ErrorDomain.STORAGE,
1022
+ category: ErrorCategory.USER,
1023
+ details: { indexName },
1024
+ text: "Cannot delete with empty filter. Use deleteIndex to delete all vectors."
1025
+ });
1026
+ }
1027
+ const translatedFilter = this.transformFilter(filter);
1028
+ const { sql: filterSql, values: filterValues } = buildFilterQuery(translatedFilter);
1029
+ if (!filterSql || filterSql.trim() === "") {
1030
+ throw new MastraError({
1031
+ id: "LIBSQL_VECTOR_DELETE_INVALID_FILTER",
1032
+ domain: ErrorDomain.STORAGE,
1033
+ category: ErrorCategory.USER,
1034
+ details: { indexName },
1035
+ text: "Filter produced empty WHERE clause"
1036
+ });
1037
+ }
1038
+ const normalizedCondition = filterSql.replace(/^\s*WHERE\s+/i, "").trim().toLowerCase();
1039
+ const matchAllPatterns = ["true", "1 = 1", "1=1"];
1040
+ if (matchAllPatterns.includes(normalizedCondition)) {
1041
+ throw new MastraError({
1042
+ id: "LIBSQL_VECTOR_DELETE_MATCH_ALL_FILTER",
1043
+ domain: ErrorDomain.STORAGE,
1044
+ category: ErrorCategory.USER,
1045
+ details: { indexName, filterSql: normalizedCondition },
1046
+ text: "Filter matches all vectors. Use deleteIndex to delete all vectors from an index."
1047
+ });
1048
+ }
1049
+ query = `DELETE FROM ${parsedIndexName} ${filterSql}`;
1050
+ values = filterValues;
1051
+ }
1052
+ try {
1053
+ await this.turso.execute({
1054
+ sql: query,
1055
+ args: values
1056
+ });
1057
+ } catch (error) {
1058
+ throw new MastraError(
1059
+ {
1060
+ id: "LIBSQL_VECTOR_DELETE_VECTORS_FAILED",
1061
+ domain: ErrorDomain.STORAGE,
1062
+ category: ErrorCategory.THIRD_PARTY,
1063
+ details: {
1064
+ indexName,
1065
+ ...filter && { filter: JSON.stringify(filter) },
1066
+ ...ids && { idsCount: ids.length }
1067
+ }
1068
+ },
1069
+ error
1070
+ );
1071
+ }
1072
+ }
900
1073
  truncateIndex(args) {
901
1074
  try {
902
1075
  return this.executeWriteOperationWithRetry(() => this._doTruncateIndex(args));
@@ -944,26 +1117,24 @@ var MemoryLibSQL = class extends MemoryStorage {
944
1117
  if (row.type && row.type !== `v2`) result.type = row.type;
945
1118
  return result;
946
1119
  }
947
- async _getIncludedMessages({
948
- threadId,
949
- include
950
- }) {
951
- if (!threadId.trim()) throw new Error("threadId must be a non-empty string");
952
- if (!include) return null;
1120
+ async _getIncludedMessages({ include }) {
1121
+ if (!include || include.length === 0) return null;
953
1122
  const unionQueries = [];
954
1123
  const params = [];
955
1124
  for (const inc of include) {
956
1125
  const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
957
- const searchId = inc.threadId || threadId;
958
1126
  unionQueries.push(
959
1127
  `
960
1128
  SELECT * FROM (
961
- WITH numbered_messages AS (
1129
+ WITH target_thread AS (
1130
+ SELECT thread_id FROM "${TABLE_MESSAGES}" WHERE id = ?
1131
+ ),
1132
+ numbered_messages AS (
962
1133
  SELECT
963
1134
  id, content, role, type, "createdAt", thread_id, "resourceId",
964
1135
  ROW_NUMBER() OVER (ORDER BY "createdAt" ASC) as row_num
965
1136
  FROM "${TABLE_MESSAGES}"
966
- WHERE thread_id = ?
1137
+ WHERE thread_id = (SELECT thread_id FROM target_thread)
967
1138
  ),
968
1139
  target_positions AS (
969
1140
  SELECT row_num as target_pos
@@ -978,7 +1149,7 @@ var MemoryLibSQL = class extends MemoryStorage {
978
1149
  `
979
1150
  // Keep ASC for final sorting after fetching context
980
1151
  );
981
- params.push(searchId, id, withPreviousMessages, withNextMessages);
1152
+ params.push(id, id, withPreviousMessages, withNextMessages);
982
1153
  }
983
1154
  const finalQuery = unionQueries.join(" UNION ALL ") + ' ORDER BY "createdAt" ASC';
984
1155
  const includedResult = await this.client.execute({ sql: finalQuery, args: params });
@@ -1025,15 +1196,16 @@ var MemoryLibSQL = class extends MemoryStorage {
1025
1196
  }
1026
1197
  async listMessages(args) {
1027
1198
  const { threadId, resourceId, include, filter, perPage: perPageInput, page = 0, orderBy } = args;
1028
- if (!threadId.trim()) {
1199
+ const threadIds = Array.isArray(threadId) ? threadId : [threadId];
1200
+ if (threadIds.length === 0 || threadIds.some((id) => !id.trim())) {
1029
1201
  throw new MastraError(
1030
1202
  {
1031
1203
  id: "STORAGE_LIBSQL_LIST_MESSAGES_INVALID_THREAD_ID",
1032
1204
  domain: ErrorDomain.STORAGE,
1033
1205
  category: ErrorCategory.THIRD_PARTY,
1034
- details: { threadId }
1206
+ details: { threadId: Array.isArray(threadId) ? threadId.join(",") : threadId }
1035
1207
  },
1036
- new Error("threadId must be a non-empty string")
1208
+ new Error("threadId must be a non-empty string or array of non-empty strings")
1037
1209
  );
1038
1210
  }
1039
1211
  if (page < 0) {
@@ -1052,8 +1224,9 @@ var MemoryLibSQL = class extends MemoryStorage {
1052
1224
  try {
1053
1225
  const { field, direction } = this.parseOrderBy(orderBy, "ASC");
1054
1226
  const orderByStatement = `ORDER BY "${field}" ${direction}`;
1055
- const conditions = [`thread_id = ?`];
1056
- const queryParams = [threadId];
1227
+ const threadPlaceholders = threadIds.map(() => "?").join(", ");
1228
+ const conditions = [`thread_id IN (${threadPlaceholders})`];
1229
+ const queryParams = [...threadIds];
1057
1230
  if (resourceId) {
1058
1231
  conditions.push(`"resourceId" = ?`);
1059
1232
  queryParams.push(resourceId);
@@ -1093,7 +1266,7 @@ var MemoryLibSQL = class extends MemoryStorage {
1093
1266
  }
1094
1267
  const messageIds = new Set(messages.map((m) => m.id));
1095
1268
  if (include && include.length > 0) {
1096
- const includeMessages = await this._getIncludedMessages({ threadId, include });
1269
+ const includeMessages = await this._getIncludedMessages({ include });
1097
1270
  if (includeMessages) {
1098
1271
  for (const includeMsg of includeMessages) {
1099
1272
  if (!messageIds.has(includeMsg.id)) {
@@ -1114,7 +1287,10 @@ var MemoryLibSQL = class extends MemoryStorage {
1114
1287
  }
1115
1288
  return direction === "ASC" ? String(aValue).localeCompare(String(bValue)) : String(bValue).localeCompare(String(aValue));
1116
1289
  });
1117
- const returnedThreadMessageIds = new Set(finalMessages.filter((m) => m.threadId === threadId).map((m) => m.id));
1290
+ const threadIdSet = new Set(threadIds);
1291
+ const returnedThreadMessageIds = new Set(
1292
+ finalMessages.filter((m) => m.threadId && threadIdSet.has(m.threadId)).map((m) => m.id)
1293
+ );
1118
1294
  const allThreadMessagesReturned = returnedThreadMessageIds.size >= total;
1119
1295
  const hasMore = perPageInput !== false && !allThreadMessagesReturned && offset + perPage < total;
1120
1296
  return {
@@ -1131,7 +1307,7 @@ var MemoryLibSQL = class extends MemoryStorage {
1131
1307
  domain: ErrorDomain.STORAGE,
1132
1308
  category: ErrorCategory.THIRD_PARTY,
1133
1309
  details: {
1134
- threadId,
1310
+ threadId: Array.isArray(threadId) ? threadId.join(",") : threadId,
1135
1311
  resourceId: resourceId ?? ""
1136
1312
  }
1137
1313
  },
@@ -2550,45 +2726,14 @@ var ScoresLibSQL = class extends ScoresStorage {
2550
2726
  );
2551
2727
  }
2552
2728
  }
2729
+ /**
2730
+ * LibSQL-specific score row transformation.
2731
+ * Maps additionalLLMContext column to additionalContext field.
2732
+ */
2553
2733
  transformScoreRow(row) {
2554
- const scorerValue = safelyParseJSON(row.scorer);
2555
- const inputValue = safelyParseJSON(row.input ?? "{}");
2556
- const outputValue = safelyParseJSON(row.output ?? "{}");
2557
- const additionalLLMContextValue = row.additionalLLMContext ? safelyParseJSON(row.additionalLLMContext) : null;
2558
- const requestContextValue = row.requestContext ? safelyParseJSON(row.requestContext) : null;
2559
- const metadataValue = row.metadata ? safelyParseJSON(row.metadata) : null;
2560
- const entityValue = row.entity ? safelyParseJSON(row.entity) : null;
2561
- const preprocessStepResultValue = row.preprocessStepResult ? safelyParseJSON(row.preprocessStepResult) : null;
2562
- const analyzeStepResultValue = row.analyzeStepResult ? safelyParseJSON(row.analyzeStepResult) : null;
2563
- return {
2564
- id: row.id,
2565
- traceId: row.traceId,
2566
- spanId: row.spanId,
2567
- runId: row.runId,
2568
- scorer: scorerValue,
2569
- score: row.score,
2570
- reason: row.reason,
2571
- preprocessStepResult: preprocessStepResultValue,
2572
- analyzeStepResult: analyzeStepResultValue,
2573
- analyzePrompt: row.analyzePrompt,
2574
- preprocessPrompt: row.preprocessPrompt,
2575
- generateScorePrompt: row.generateScorePrompt,
2576
- generateReasonPrompt: row.generateReasonPrompt,
2577
- metadata: metadataValue,
2578
- input: inputValue,
2579
- output: outputValue,
2580
- additionalContext: additionalLLMContextValue,
2581
- requestContext: requestContextValue,
2582
- entityType: row.entityType,
2583
- entity: entityValue,
2584
- entityId: row.entityId,
2585
- scorerId: row.scorerId,
2586
- source: row.source,
2587
- resourceId: row.resourceId,
2588
- threadId: row.threadId,
2589
- createdAt: row.createdAt,
2590
- updatedAt: row.updatedAt
2591
- };
2734
+ return transformScoreRow(row, {
2735
+ fieldMappings: { additionalContext: "additionalLLMContext" }
2736
+ });
2592
2737
  }
2593
2738
  async getScoreById({ id }) {
2594
2739
  const result = await this.client.execute({