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