@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.
- package/CHANGELOG.md +861 -3
- package/README.md +30 -20
- package/dist/index.cjs +2098 -1668
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2032 -1602
- package/dist/index.js.map +1 -1
- package/dist/storage/db/index.d.ts +259 -0
- package/dist/storage/db/index.d.ts.map +1 -0
- package/dist/storage/{domains → db}/utils.d.ts +4 -0
- package/dist/storage/db/utils.d.ts.map +1 -0
- package/dist/storage/domains/agents/index.d.ts +23 -0
- package/dist/storage/domains/agents/index.d.ts.map +1 -0
- package/dist/storage/domains/memory/index.d.ts +19 -53
- package/dist/storage/domains/memory/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/index.d.ts +17 -17
- package/dist/storage/domains/observability/index.d.ts.map +1 -1
- package/dist/storage/domains/scores/index.d.ts +15 -14
- package/dist/storage/domains/scores/index.d.ts.map +1 -1
- package/dist/storage/domains/workflows/index.d.ts +18 -32
- package/dist/storage/domains/workflows/index.d.ts.map +1 -1
- package/dist/storage/index.d.ts +70 -128
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/vector/index.d.ts +7 -3
- package/dist/vector/index.d.ts.map +1 -1
- package/dist/vector/sql-builder.d.ts.map +1 -1
- package/package.json +12 -8
- package/dist/storage/domains/legacy-evals/index.d.ts +0 -18
- package/dist/storage/domains/legacy-evals/index.d.ts.map +0 -1
- package/dist/storage/domains/operations/index.d.ts +0 -110
- package/dist/storage/domains/operations/index.d.ts.map +0 -1
- 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 {
|
|
7
|
+
import { MastraBase } from '@mastra/core/base';
|
|
7
8
|
import { MessageList } from '@mastra/core/agent';
|
|
8
|
-
import { saveScorePayloadSchema } from '@mastra/core/
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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(
|
|
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: "
|
|
912
|
+
id: createVectorErrorId("LIBSQL", "UPDATE_VECTOR", "NO_TARGET"),
|
|
841
913
|
domain: ErrorDomain.STORAGE,
|
|
842
914
|
category: ErrorCategory.USER,
|
|
843
|
-
details: { indexName
|
|
844
|
-
text: "
|
|
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
|
-
|
|
850
|
-
|
|
851
|
-
|
|
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: "
|
|
939
|
+
id: createVectorErrorId("LIBSQL", "UPDATE_VECTOR", "FAILED"),
|
|
862
940
|
domain: ErrorDomain.STORAGE,
|
|
863
941
|
category: ErrorCategory.THIRD_PARTY,
|
|
864
|
-
details:
|
|
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: "
|
|
961
|
+
id: createVectorErrorId("LIBSQL", "DELETE_VECTOR", "FAILED"),
|
|
884
962
|
domain: ErrorDomain.STORAGE,
|
|
885
963
|
category: ErrorCategory.THIRD_PARTY,
|
|
886
|
-
details: {
|
|
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
|
-
|
|
900
|
-
|
|
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
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
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
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
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
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
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
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
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.
|
|
1029
|
-
|
|
1030
|
-
|
|
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
|
|
1125
|
-
const
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
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
|
-
|
|
1146
|
-
const
|
|
1147
|
-
if (
|
|
1148
|
-
|
|
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: "
|
|
1061
|
+
id: createVectorErrorId("LIBSQL", "DELETE_VECTORS", "FAILED"),
|
|
1153
1062
|
domain: ErrorDomain.STORAGE,
|
|
1154
1063
|
category: ErrorCategory.THIRD_PARTY,
|
|
1155
|
-
details: {
|
|
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
|
-
|
|
1162
|
-
messageIds,
|
|
1163
|
-
format
|
|
1164
|
-
}) {
|
|
1165
|
-
if (messageIds.length === 0) return [];
|
|
1074
|
+
truncateIndex(args) {
|
|
1166
1075
|
try {
|
|
1167
|
-
|
|
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: "
|
|
1080
|
+
id: createVectorErrorId("LIBSQL", "TRUNCATE_INDEX", "FAILED"),
|
|
1189
1081
|
domain: ErrorDomain.STORAGE,
|
|
1190
1082
|
category: ErrorCategory.THIRD_PARTY,
|
|
1191
|
-
details: {
|
|
1083
|
+
details: { indexName: args.indexName }
|
|
1192
1084
|
},
|
|
1193
1085
|
error
|
|
1194
1086
|
);
|
|
1195
1087
|
}
|
|
1196
1088
|
}
|
|
1197
|
-
async
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
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
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
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
|
-
}
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
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
|
-
|
|
1224
|
-
const
|
|
1225
|
-
const
|
|
1226
|
-
const
|
|
1227
|
-
|
|
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(
|
|
2203
|
+
queryParams.push(
|
|
2204
|
+
filter.dateRange.start instanceof Date ? filter.dateRange.start.toISOString() : filter.dateRange.start
|
|
2205
|
+
);
|
|
1230
2206
|
}
|
|
1231
|
-
if (
|
|
2207
|
+
if (filter?.dateRange?.end) {
|
|
1232
2208
|
conditions.push(`"createdAt" <= ?`);
|
|
1233
|
-
queryParams.push(
|
|
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
|
|
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
|
-
|
|
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
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
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
|
-
|
|
1257
|
-
const
|
|
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:
|
|
2264
|
+
messages: finalMessages,
|
|
1260
2265
|
total,
|
|
1261
2266
|
page,
|
|
1262
|
-
perPage,
|
|
1263
|
-
hasMore
|
|
2267
|
+
perPage: perPageForResponse,
|
|
2268
|
+
hasMore
|
|
1264
2269
|
};
|
|
1265
2270
|
} catch (error) {
|
|
1266
2271
|
const mastraError = new MastraError(
|
|
1267
2272
|
{
|
|
1268
|
-
id: "
|
|
2273
|
+
id: createStorageErrorId("LIBSQL", "LIST_MESSAGES", "FAILED"),
|
|
1269
2274
|
domain: ErrorDomain.STORAGE,
|
|
1270
2275
|
category: ErrorCategory.THIRD_PARTY,
|
|
1271
|
-
details: {
|
|
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
|
-
|
|
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
|
|
2345
|
+
await this.#client.batch(batch, "write");
|
|
1335
2346
|
}
|
|
1336
2347
|
}
|
|
1337
2348
|
if (threadUpdateStatement) {
|
|
1338
|
-
await this
|
|
2349
|
+
await this.#client.execute(threadUpdateStatement);
|
|
1339
2350
|
}
|
|
1340
2351
|
const list = new MessageList().add(messages, "memory");
|
|
1341
|
-
|
|
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: "
|
|
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
|
|
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
|
|
1430
|
-
const updatedResult = await this
|
|
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
|
|
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
|
-
|
|
1727
|
-
|
|
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: "
|
|
2484
|
+
id: createStorageErrorId("LIBSQL", "DELETE_MESSAGES", "FAILED"),
|
|
1767
2485
|
domain: ErrorDomain.STORAGE,
|
|
1768
2486
|
category: ErrorCategory.THIRD_PARTY,
|
|
1769
|
-
details: {
|
|
2487
|
+
details: { messageIds: messageIds.join(", ") }
|
|
1770
2488
|
},
|
|
1771
2489
|
error
|
|
1772
2490
|
);
|
|
1773
2491
|
}
|
|
1774
2492
|
}
|
|
1775
|
-
}
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
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
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
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
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
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
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
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.
|
|
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
|
|
2564
|
+
async getThreadById({ threadId }) {
|
|
1978
2565
|
try {
|
|
1979
|
-
const
|
|
1980
|
-
tableName:
|
|
1981
|
-
|
|
1982
|
-
orderBy: "startedAt DESC"
|
|
2566
|
+
const result = await this.#db.select({
|
|
2567
|
+
tableName: TABLE_THREADS,
|
|
2568
|
+
keys: { id: threadId }
|
|
1983
2569
|
});
|
|
1984
|
-
if (!
|
|
2570
|
+
if (!result) {
|
|
1985
2571
|
return null;
|
|
1986
2572
|
}
|
|
1987
2573
|
return {
|
|
1988
|
-
|
|
1989
|
-
|
|
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: "
|
|
2582
|
+
id: createStorageErrorId("LIBSQL", "GET_THREAD_BY_ID", "FAILED"),
|
|
1995
2583
|
domain: ErrorDomain.STORAGE,
|
|
1996
|
-
category: ErrorCategory.
|
|
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
|
|
2006
|
-
|
|
2007
|
-
|
|
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: "
|
|
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
|
-
|
|
2601
|
+
new Error("page must be >= 0")
|
|
2028
2602
|
);
|
|
2029
2603
|
}
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
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
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
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
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
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
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
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
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
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
|
-
|
|
2648
|
+
const mastraError = new MastraError(
|
|
2122
2649
|
{
|
|
2123
|
-
id: "
|
|
2650
|
+
id: createStorageErrorId("LIBSQL", "LIST_THREADS_BY_RESOURCE_ID", "FAILED"),
|
|
2124
2651
|
domain: ErrorDomain.STORAGE,
|
|
2125
|
-
category: ErrorCategory.
|
|
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
|
|
2668
|
+
async saveThread({ thread }) {
|
|
2132
2669
|
try {
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
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
|
-
|
|
2679
|
+
const mastraError = new MastraError(
|
|
2144
2680
|
{
|
|
2145
|
-
id: "
|
|
2681
|
+
id: createStorageErrorId("LIBSQL", "SAVE_THREAD", "FAILED"),
|
|
2146
2682
|
domain: ErrorDomain.STORAGE,
|
|
2147
|
-
category: ErrorCategory.
|
|
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
|
|
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
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
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: "
|
|
2728
|
+
id: createStorageErrorId("LIBSQL", "UPDATE_THREAD", "FAILED"),
|
|
2166
2729
|
domain: ErrorDomain.STORAGE,
|
|
2167
|
-
category: ErrorCategory.
|
|
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
|
|
2738
|
+
async deleteThread({ threadId }) {
|
|
2174
2739
|
try {
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
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: "
|
|
2751
|
+
id: createStorageErrorId("LIBSQL", "DELETE_THREAD", "FAILED"),
|
|
2184
2752
|
domain: ErrorDomain.STORAGE,
|
|
2185
|
-
category: ErrorCategory.
|
|
2753
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
2754
|
+
details: { threadId }
|
|
2186
2755
|
},
|
|
2187
2756
|
error
|
|
2188
2757
|
);
|
|
2189
2758
|
}
|
|
2190
2759
|
}
|
|
2191
2760
|
};
|
|
2192
|
-
var
|
|
2193
|
-
|
|
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
|
-
|
|
2212
|
-
this
|
|
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
|
-
|
|
2222
|
-
|
|
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
|
|
2249
|
-
tableName
|
|
2250
|
-
|
|
2251
|
-
|
|
2771
|
+
async dangerouslyClearAll() {
|
|
2772
|
+
await this.#db.deleteData({ tableName: TABLE_SPANS });
|
|
2773
|
+
}
|
|
2774
|
+
async createSpan(span) {
|
|
2252
2775
|
try {
|
|
2253
|
-
|
|
2254
|
-
const
|
|
2255
|
-
|
|
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: "
|
|
2786
|
+
id: createStorageErrorId("LIBSQL", "CREATE_SPAN", "FAILED"),
|
|
2260
2787
|
domain: ErrorDomain.STORAGE,
|
|
2261
|
-
category: ErrorCategory.
|
|
2788
|
+
category: ErrorCategory.USER,
|
|
2262
2789
|
details: {
|
|
2263
|
-
|
|
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
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
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: "
|
|
2817
|
+
id: createStorageErrorId("LIBSQL", "GET_TRACE", "FAILED"),
|
|
2404
2818
|
domain: ErrorDomain.STORAGE,
|
|
2405
|
-
category: ErrorCategory.
|
|
2819
|
+
category: ErrorCategory.USER,
|
|
2406
2820
|
details: {
|
|
2407
|
-
|
|
2821
|
+
traceId
|
|
2408
2822
|
}
|
|
2409
2823
|
},
|
|
2410
2824
|
error
|
|
2411
2825
|
);
|
|
2412
|
-
}
|
|
2826
|
+
}
|
|
2413
2827
|
}
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
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: "
|
|
2842
|
+
id: createStorageErrorId("LIBSQL", "UPDATE_SPAN", "FAILED"),
|
|
2430
2843
|
domain: ErrorDomain.STORAGE,
|
|
2431
|
-
category: ErrorCategory.
|
|
2844
|
+
category: ErrorCategory.USER,
|
|
2432
2845
|
details: {
|
|
2433
|
-
|
|
2846
|
+
spanId,
|
|
2847
|
+
traceId
|
|
2434
2848
|
}
|
|
2435
2849
|
},
|
|
2436
2850
|
error
|
|
2437
2851
|
);
|
|
2438
|
-
}
|
|
2852
|
+
}
|
|
2439
2853
|
}
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
async executeBatchUpdate({
|
|
2444
|
-
tableName,
|
|
2445
|
-
updates
|
|
2854
|
+
async getTracesPaginated({
|
|
2855
|
+
filters,
|
|
2856
|
+
pagination
|
|
2446
2857
|
}) {
|
|
2447
|
-
|
|
2448
|
-
const
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
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: "
|
|
2946
|
+
id: createStorageErrorId("LIBSQL", "GET_TRACES_PAGINATED", "FAILED"),
|
|
2473
2947
|
domain: ErrorDomain.STORAGE,
|
|
2474
|
-
category: ErrorCategory.
|
|
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
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
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: "
|
|
2968
|
+
id: createStorageErrorId("LIBSQL", "BATCH_CREATE_SPANS", "FAILED"),
|
|
2530
2969
|
domain: ErrorDomain.STORAGE,
|
|
2531
|
-
category: ErrorCategory.
|
|
2532
|
-
details: {
|
|
2533
|
-
tableName
|
|
2534
|
-
}
|
|
2970
|
+
category: ErrorCategory.USER
|
|
2535
2971
|
},
|
|
2536
2972
|
error
|
|
2537
2973
|
);
|
|
2538
2974
|
}
|
|
2539
2975
|
}
|
|
2540
|
-
async
|
|
2541
|
-
const parsedTableName = parseSqlIdentifier(tableName, "table name");
|
|
2976
|
+
async batchUpdateSpans(args) {
|
|
2542
2977
|
try {
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
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: "
|
|
2988
|
+
id: createStorageErrorId("LIBSQL", "BATCH_UPDATE_SPANS", "FAILED"),
|
|
2548
2989
|
domain: ErrorDomain.STORAGE,
|
|
2549
|
-
category: ErrorCategory.
|
|
2550
|
-
details: {
|
|
2551
|
-
tableName
|
|
2552
|
-
}
|
|
2990
|
+
category: ErrorCategory.USER
|
|
2553
2991
|
},
|
|
2554
|
-
|
|
2992
|
+
error
|
|
2555
2993
|
);
|
|
2556
|
-
this.logger?.trackException?.(mastraError);
|
|
2557
|
-
this.logger?.error?.(mastraError.toString());
|
|
2558
2994
|
}
|
|
2559
2995
|
}
|
|
2560
|
-
async
|
|
2561
|
-
const parsedTableName = parseSqlIdentifier(tableName, "table name");
|
|
2996
|
+
async batchDeleteTraces(args) {
|
|
2562
2997
|
try {
|
|
2563
|
-
|
|
2564
|
-
|
|
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: "
|
|
3006
|
+
id: createStorageErrorId("LIBSQL", "BATCH_DELETE_TRACES", "FAILED"),
|
|
2568
3007
|
domain: ErrorDomain.STORAGE,
|
|
2569
|
-
category: ErrorCategory.
|
|
2570
|
-
details: {
|
|
2571
|
-
tableName
|
|
2572
|
-
}
|
|
3008
|
+
category: ErrorCategory.USER
|
|
2573
3009
|
},
|
|
2574
|
-
|
|
3010
|
+
error
|
|
2575
3011
|
);
|
|
2576
3012
|
}
|
|
2577
3013
|
}
|
|
2578
3014
|
};
|
|
2579
3015
|
var ScoresLibSQL = class extends ScoresStorage {
|
|
2580
|
-
|
|
2581
|
-
client;
|
|
2582
|
-
constructor(
|
|
3016
|
+
#db;
|
|
3017
|
+
#client;
|
|
3018
|
+
constructor(config) {
|
|
2583
3019
|
super();
|
|
2584
|
-
|
|
2585
|
-
this
|
|
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
|
|
3035
|
+
async listScoresByRunId({
|
|
2588
3036
|
runId,
|
|
2589
3037
|
pagination
|
|
2590
3038
|
}) {
|
|
2591
3039
|
try {
|
|
2592
|
-
const
|
|
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,
|
|
3063
|
+
args: [runId, limitValue, start]
|
|
2595
3064
|
});
|
|
3065
|
+
const scores = result.rows?.map((row) => this.transformScoreRow(row)) ?? [];
|
|
2596
3066
|
return {
|
|
2597
|
-
scores
|
|
3067
|
+
scores,
|
|
2598
3068
|
pagination: {
|
|
2599
|
-
total
|
|
2600
|
-
page
|
|
2601
|
-
perPage:
|
|
2602
|
-
hasMore:
|
|
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: "
|
|
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
|
|
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
|
|
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,
|
|
3136
|
+
args: [...queryParams, limitValue, start]
|
|
2646
3137
|
});
|
|
3138
|
+
const scores = result.rows?.map((row) => this.transformScoreRow(row)) ?? [];
|
|
2647
3139
|
return {
|
|
2648
|
-
scores
|
|
3140
|
+
scores,
|
|
2649
3141
|
pagination: {
|
|
2650
|
-
total
|
|
2651
|
-
page
|
|
2652
|
-
perPage:
|
|
2653
|
-
hasMore:
|
|
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: "
|
|
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
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
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
|
|
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: "
|
|
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
|
|
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
|
-
|
|
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:
|
|
2742
|
-
updatedAt:
|
|
2743
|
-
...parsedScore
|
|
3204
|
+
createdAt: now.toISOString(),
|
|
3205
|
+
updatedAt: now.toISOString()
|
|
2744
3206
|
}
|
|
2745
3207
|
});
|
|
2746
|
-
|
|
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: "
|
|
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
|
|
3220
|
+
async listScoresByEntityId({
|
|
2760
3221
|
entityId,
|
|
2761
3222
|
entityType,
|
|
2762
3223
|
pagination
|
|
2763
3224
|
}) {
|
|
2764
3225
|
try {
|
|
2765
|
-
const
|
|
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,
|
|
3249
|
+
args: [entityId, entityType, limitValue, start]
|
|
2768
3250
|
});
|
|
3251
|
+
const scores = result.rows?.map((row) => this.transformScoreRow(row)) ?? [];
|
|
2769
3252
|
return {
|
|
2770
|
-
scores
|
|
3253
|
+
scores,
|
|
2771
3254
|
pagination: {
|
|
2772
|
-
total
|
|
2773
|
-
page
|
|
2774
|
-
perPage:
|
|
2775
|
-
hasMore:
|
|
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: "
|
|
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
|
|
3272
|
+
async listScoresBySpan({
|
|
2790
3273
|
traceId,
|
|
2791
3274
|
spanId,
|
|
2792
3275
|
pagination
|
|
2793
3276
|
}) {
|
|
2794
3277
|
try {
|
|
2795
|
-
const
|
|
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
|
|
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,
|
|
3290
|
+
args: [traceId, spanId, limitValue, start]
|
|
2803
3291
|
});
|
|
2804
|
-
const
|
|
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
|
|
2811
|
-
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: "
|
|
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
|
-
|
|
2847
|
-
client;
|
|
2848
|
-
|
|
2849
|
-
|
|
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
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
this
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
3389
|
+
requestContext
|
|
2929
3390
|
}) {
|
|
2930
3391
|
return this.executeWithRetry(async () => {
|
|
2931
|
-
const tx = await this
|
|
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
|
-
|
|
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.
|
|
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
|
|
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:
|
|
3021
|
-
updatedAt:
|
|
3485
|
+
createdAt: createdAt ?? now,
|
|
3486
|
+
updatedAt: updatedAt ?? now
|
|
3022
3487
|
};
|
|
3023
3488
|
this.logger.debug("Persisting workflow snapshot", { workflowName, runId, data });
|
|
3024
|
-
await this.
|
|
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.
|
|
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
|
|
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: "
|
|
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
|
|
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
|
-
|
|
3080
|
-
|
|
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.
|
|
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
|
-
|
|
3110
|
-
|
|
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
|
|
3117
|
-
|
|
3118
|
-
|
|
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: "
|
|
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
|
-
|
|
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
|
|
3656
|
+
const domainConfig = {
|
|
3161
3657
|
client: this.client,
|
|
3162
3658
|
maxRetries: this.maxRetries,
|
|
3163
3659
|
initialBackoffMs: this.initialBackoffMs
|
|
3164
|
-
}
|
|
3165
|
-
const scores = new ScoresLibSQL(
|
|
3166
|
-
const workflows = new WorkflowsLibSQL(
|
|
3167
|
-
const memory = new MemoryLibSQL(
|
|
3168
|
-
const
|
|
3169
|
-
const
|
|
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
|
-
|
|
3176
|
-
|
|
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
|
-
|
|
3187
|
-
|
|
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
|
|
3250
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
3730
|
+
return this.stores.scores.listScoresByScorerId({ scorerId, entityId, entityType, source, pagination });
|
|
3297
3731
|
}
|
|
3298
|
-
async
|
|
3732
|
+
async listScoresByRunId({
|
|
3299
3733
|
runId,
|
|
3300
3734
|
pagination
|
|
3301
3735
|
}) {
|
|
3302
|
-
return this.stores.scores.
|
|
3736
|
+
return this.stores.scores.listScoresByRunId({ runId, pagination });
|
|
3303
3737
|
}
|
|
3304
|
-
async
|
|
3738
|
+
async listScoresByEntityId({
|
|
3305
3739
|
entityId,
|
|
3306
3740
|
entityType,
|
|
3307
3741
|
pagination
|
|
3308
3742
|
}) {
|
|
3309
|
-
return this.stores.scores.
|
|
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
|
-
|
|
3753
|
+
requestContext
|
|
3320
3754
|
}) {
|
|
3321
|
-
return this.stores.workflows.updateWorkflowResults({ workflowName, runId, stepId, result,
|
|
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
|
|
3345
|
-
|
|
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
|
|
3374
|
-
return this.stores.observability.
|
|
3803
|
+
async createSpan(span) {
|
|
3804
|
+
return this.stores.observability.createSpan(span);
|
|
3375
3805
|
}
|
|
3376
|
-
async
|
|
3377
|
-
return this.stores.observability.
|
|
3806
|
+
async updateSpan(params) {
|
|
3807
|
+
return this.stores.observability.updateSpan(params);
|
|
3378
3808
|
}
|
|
3379
|
-
async
|
|
3380
|
-
return this.stores.observability.
|
|
3809
|
+
async getTrace(traceId) {
|
|
3810
|
+
return this.stores.observability.getTrace(traceId);
|
|
3381
3811
|
}
|
|
3382
|
-
async
|
|
3383
|
-
return this.stores.observability.
|
|
3812
|
+
async getTracesPaginated(args) {
|
|
3813
|
+
return this.stores.observability.getTracesPaginated(args);
|
|
3384
3814
|
}
|
|
3385
|
-
async
|
|
3815
|
+
async listScoresBySpan({
|
|
3386
3816
|
traceId,
|
|
3387
3817
|
spanId,
|
|
3388
3818
|
pagination
|
|
3389
3819
|
}) {
|
|
3390
|
-
return this.stores.scores.
|
|
3820
|
+
return this.stores.scores.listScoresBySpan({ traceId, spanId, pagination });
|
|
3391
3821
|
}
|
|
3392
|
-
async
|
|
3393
|
-
return this.stores.observability.
|
|
3822
|
+
async batchCreateSpans(args) {
|
|
3823
|
+
return this.stores.observability.batchCreateSpans(args);
|
|
3394
3824
|
}
|
|
3395
|
-
async
|
|
3396
|
-
return this.stores.observability.
|
|
3825
|
+
async batchUpdateSpans(args) {
|
|
3826
|
+
return this.stores.observability.batchUpdateSpans(args);
|
|
3397
3827
|
}
|
|
3398
3828
|
};
|
|
3399
3829
|
|