@mastra/libsql 1.0.0-beta.9 → 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 +1164 -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 +546 -107
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +543 -109
- package/dist/index.js.map +1 -1
- package/dist/storage/db/index.d.ts +42 -1
- package/dist/storage/db/index.d.ts.map +1 -1
- package/dist/storage/db/utils.d.ts +16 -1
- package/dist/storage/db/utils.d.ts.map +1 -1
- package/dist/storage/domains/memory/index.d.ts +3 -2
- package/dist/storage/domains/memory/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/index.d.ts +23 -0
- package/dist/storage/domains/observability/index.d.ts.map +1 -1
- package/dist/storage/domains/scores/index.d.ts +0 -1
- package/dist/storage/domains/scores/index.d.ts.map +1 -1
- package/dist/storage/domains/workflows/index.d.ts +1 -0
- package/dist/storage/domains/workflows/index.d.ts.map +1 -1
- package/dist/storage/index.d.ts +10 -4
- 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/index.cjs
CHANGED
|
@@ -244,10 +244,10 @@ var FILTER_OPERATORS = {
|
|
|
244
244
|
};
|
|
245
245
|
},
|
|
246
246
|
// Element Operators
|
|
247
|
-
$exists: (key) => {
|
|
247
|
+
$exists: (key, value) => {
|
|
248
248
|
const jsonPath = getJsonPath(key);
|
|
249
249
|
return {
|
|
250
|
-
sql: `json_extract(metadata, ${jsonPath}) IS NOT NULL`,
|
|
250
|
+
sql: value === false ? `json_extract(metadata, ${jsonPath}) IS NULL` : `json_extract(metadata, ${jsonPath}) IS NOT NULL`,
|
|
251
251
|
needsValue: false
|
|
252
252
|
};
|
|
253
253
|
},
|
|
@@ -511,7 +511,7 @@ var LibSQLVector = class extends vector.MastraVector {
|
|
|
511
511
|
maxRetries;
|
|
512
512
|
initialBackoffMs;
|
|
513
513
|
constructor({
|
|
514
|
-
|
|
514
|
+
url,
|
|
515
515
|
authToken,
|
|
516
516
|
syncUrl,
|
|
517
517
|
syncInterval,
|
|
@@ -521,14 +521,14 @@ var LibSQLVector = class extends vector.MastraVector {
|
|
|
521
521
|
}) {
|
|
522
522
|
super({ id });
|
|
523
523
|
this.turso = client.createClient({
|
|
524
|
-
url
|
|
524
|
+
url,
|
|
525
525
|
syncUrl,
|
|
526
526
|
authToken,
|
|
527
527
|
syncInterval
|
|
528
528
|
});
|
|
529
529
|
this.maxRetries = maxRetries;
|
|
530
530
|
this.initialBackoffMs = initialBackoffMs;
|
|
531
|
-
if (
|
|
531
|
+
if (url.includes(`file:`) || url.includes(`:memory:`)) {
|
|
532
532
|
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));
|
|
533
533
|
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));
|
|
534
534
|
}
|
|
@@ -540,7 +540,7 @@ var LibSQLVector = class extends vector.MastraVector {
|
|
|
540
540
|
try {
|
|
541
541
|
return await operation();
|
|
542
542
|
} catch (error) {
|
|
543
|
-
if (error.code === "SQLITE_BUSY" || error.message && error.message.toLowerCase().includes("database is locked")) {
|
|
543
|
+
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")) {
|
|
544
544
|
attempts++;
|
|
545
545
|
if (attempts >= this.maxRetries) {
|
|
546
546
|
this.logger.error(
|
|
@@ -574,22 +574,14 @@ var LibSQLVector = class extends vector.MastraVector {
|
|
|
574
574
|
minScore = -1
|
|
575
575
|
// Default to -1 to include all results (cosine similarity ranges from -1 to 1)
|
|
576
576
|
}) {
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
throw new error.MastraError(
|
|
586
|
-
{
|
|
587
|
-
id: storage.createVectorErrorId("LIBSQL", "QUERY", "INVALID_ARGS"),
|
|
588
|
-
domain: error.ErrorDomain.STORAGE,
|
|
589
|
-
category: error.ErrorCategory.USER
|
|
590
|
-
},
|
|
591
|
-
error$1
|
|
592
|
-
);
|
|
577
|
+
vector.validateTopK("LIBSQL", topK);
|
|
578
|
+
if (!Array.isArray(queryVector) || !queryVector.every((x) => typeof x === "number" && Number.isFinite(x))) {
|
|
579
|
+
throw new error.MastraError({
|
|
580
|
+
id: storage.createVectorErrorId("LIBSQL", "QUERY", "INVALID_ARGS"),
|
|
581
|
+
domain: error.ErrorDomain.STORAGE,
|
|
582
|
+
category: error.ErrorCategory.USER,
|
|
583
|
+
details: { message: "queryVector must be an array of finite numbers" }
|
|
584
|
+
});
|
|
593
585
|
}
|
|
594
586
|
try {
|
|
595
587
|
const parsedIndexName = utils.parseSqlIdentifier(indexName, "index name");
|
|
@@ -649,6 +641,7 @@ var LibSQLVector = class extends vector.MastraVector {
|
|
|
649
641
|
}
|
|
650
642
|
}
|
|
651
643
|
async doUpsert({ indexName, vectors, metadata, ids }) {
|
|
644
|
+
vector.validateUpsertInput("LIBSQL", vectors, metadata, ids);
|
|
652
645
|
const tx = await this.turso.transaction("write");
|
|
653
646
|
try {
|
|
654
647
|
const parsedIndexName = utils.parseSqlIdentifier(indexName, "index name");
|
|
@@ -1095,6 +1088,14 @@ var LibSQLVector = class extends vector.MastraVector {
|
|
|
1095
1088
|
});
|
|
1096
1089
|
}
|
|
1097
1090
|
};
|
|
1091
|
+
function buildSelectColumns(tableName) {
|
|
1092
|
+
const schema = storage.TABLE_SCHEMAS[tableName];
|
|
1093
|
+
return Object.keys(schema).map((col) => {
|
|
1094
|
+
const colDef = schema[col];
|
|
1095
|
+
const parsedCol = utils.parseSqlIdentifier(col, "column name");
|
|
1096
|
+
return colDef?.type === "jsonb" ? `json(${parsedCol}) as ${parsedCol}` : parsedCol;
|
|
1097
|
+
}).join(", ");
|
|
1098
|
+
}
|
|
1098
1099
|
function isLockError(error) {
|
|
1099
1100
|
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");
|
|
1100
1101
|
}
|
|
@@ -1143,17 +1144,27 @@ function createExecuteWriteOperationWithRetry({
|
|
|
1143
1144
|
}
|
|
1144
1145
|
function prepareStatement({ tableName, record }) {
|
|
1145
1146
|
const parsedTableName = utils.parseSqlIdentifier(tableName, "table name");
|
|
1146
|
-
const
|
|
1147
|
-
const
|
|
1147
|
+
const schema = storage.TABLE_SCHEMAS[tableName];
|
|
1148
|
+
const columnNames = Object.keys(record);
|
|
1149
|
+
const columns = columnNames.map((col) => utils.parseSqlIdentifier(col, "column name"));
|
|
1150
|
+
const values = columnNames.map((col) => {
|
|
1151
|
+
const v = record[col];
|
|
1148
1152
|
if (typeof v === `undefined` || v === null) {
|
|
1149
1153
|
return null;
|
|
1150
1154
|
}
|
|
1155
|
+
const colDef = schema[col];
|
|
1156
|
+
if (colDef?.type === "jsonb") {
|
|
1157
|
+
return JSON.stringify(v);
|
|
1158
|
+
}
|
|
1151
1159
|
if (v instanceof Date) {
|
|
1152
1160
|
return v.toISOString();
|
|
1153
1161
|
}
|
|
1154
1162
|
return typeof v === "object" ? JSON.stringify(v) : v;
|
|
1155
1163
|
});
|
|
1156
|
-
const placeholders =
|
|
1164
|
+
const placeholders = columnNames.map((col) => {
|
|
1165
|
+
const colDef = schema[col];
|
|
1166
|
+
return colDef?.type === "jsonb" ? "jsonb(?)" : "?";
|
|
1167
|
+
}).join(", ");
|
|
1157
1168
|
return {
|
|
1158
1169
|
sql: `INSERT OR REPLACE INTO ${parsedTableName} (${columns.join(", ")}) VALUES (${placeholders})`,
|
|
1159
1170
|
args: values
|
|
@@ -1166,19 +1177,33 @@ function prepareUpdateStatement({
|
|
|
1166
1177
|
}) {
|
|
1167
1178
|
const parsedTableName = utils.parseSqlIdentifier(tableName, "table name");
|
|
1168
1179
|
const schema = storage.TABLE_SCHEMAS[tableName];
|
|
1169
|
-
const
|
|
1170
|
-
const
|
|
1171
|
-
const
|
|
1180
|
+
const updateColumnNames = Object.keys(updates);
|
|
1181
|
+
const updateColumns = updateColumnNames.map((col) => utils.parseSqlIdentifier(col, "column name"));
|
|
1182
|
+
const updateValues = updateColumnNames.map((col) => {
|
|
1183
|
+
const colDef = schema[col];
|
|
1184
|
+
const v = updates[col];
|
|
1185
|
+
if (colDef?.type === "jsonb") {
|
|
1186
|
+
return transformToSqlValue(v, true);
|
|
1187
|
+
}
|
|
1188
|
+
return transformToSqlValue(v, false);
|
|
1189
|
+
});
|
|
1190
|
+
const setClause = updateColumns.map((col, i) => {
|
|
1191
|
+
const colDef = schema[updateColumnNames[i]];
|
|
1192
|
+
return colDef?.type === "jsonb" ? `${col} = jsonb(?)` : `${col} = ?`;
|
|
1193
|
+
}).join(", ");
|
|
1172
1194
|
const whereClause = prepareWhereClause(keys, schema);
|
|
1173
1195
|
return {
|
|
1174
1196
|
sql: `UPDATE ${parsedTableName} SET ${setClause}${whereClause.sql}`,
|
|
1175
1197
|
args: [...updateValues, ...whereClause.args]
|
|
1176
1198
|
};
|
|
1177
1199
|
}
|
|
1178
|
-
function transformToSqlValue(value) {
|
|
1200
|
+
function transformToSqlValue(value, forceJsonStringify = false) {
|
|
1179
1201
|
if (typeof value === "undefined" || value === null) {
|
|
1180
1202
|
return null;
|
|
1181
1203
|
}
|
|
1204
|
+
if (forceJsonStringify) {
|
|
1205
|
+
return JSON.stringify(value);
|
|
1206
|
+
}
|
|
1182
1207
|
if (value instanceof Date) {
|
|
1183
1208
|
return value.toISOString();
|
|
1184
1209
|
}
|
|
@@ -1535,11 +1560,12 @@ var LibSQLDB = class extends base.MastraBase {
|
|
|
1535
1560
|
*/
|
|
1536
1561
|
async select({ tableName, keys }) {
|
|
1537
1562
|
const parsedTableName = utils.parseSqlIdentifier(tableName, "table name");
|
|
1563
|
+
const columns = buildSelectColumns(tableName);
|
|
1538
1564
|
const parsedKeys = Object.keys(keys).map((key) => utils.parseSqlIdentifier(key, "column name"));
|
|
1539
1565
|
const conditions = parsedKeys.map((key) => `${key} = ?`).join(" AND ");
|
|
1540
1566
|
const values = Object.values(keys);
|
|
1541
1567
|
const result = await this.client.execute({
|
|
1542
|
-
sql: `SELECT
|
|
1568
|
+
sql: `SELECT ${columns} FROM ${parsedTableName} WHERE ${conditions} ORDER BY createdAt DESC LIMIT 1`,
|
|
1543
1569
|
args: values
|
|
1544
1570
|
});
|
|
1545
1571
|
if (!result.rows || result.rows.length === 0) {
|
|
@@ -1579,7 +1605,8 @@ var LibSQLDB = class extends base.MastraBase {
|
|
|
1579
1605
|
args
|
|
1580
1606
|
}) {
|
|
1581
1607
|
const parsedTableName = utils.parseSqlIdentifier(tableName, "table name");
|
|
1582
|
-
|
|
1608
|
+
const columns = buildSelectColumns(tableName);
|
|
1609
|
+
let statement = `SELECT ${columns} FROM ${parsedTableName}`;
|
|
1583
1610
|
if (whereClause?.sql) {
|
|
1584
1611
|
statement += ` ${whereClause.sql}`;
|
|
1585
1612
|
}
|
|
@@ -1596,7 +1623,17 @@ var LibSQLDB = class extends base.MastraBase {
|
|
|
1596
1623
|
sql: statement,
|
|
1597
1624
|
args: [...whereClause?.args ?? [], ...args ?? []]
|
|
1598
1625
|
});
|
|
1599
|
-
return result.rows
|
|
1626
|
+
return (result.rows ?? []).map((row) => {
|
|
1627
|
+
return Object.fromEntries(
|
|
1628
|
+
Object.entries(row || {}).map(([k, v]) => {
|
|
1629
|
+
try {
|
|
1630
|
+
return [k, typeof v === "string" ? v.startsWith("{") || v.startsWith("[") ? JSON.parse(v) : v : v];
|
|
1631
|
+
} catch {
|
|
1632
|
+
return [k, v];
|
|
1633
|
+
}
|
|
1634
|
+
})
|
|
1635
|
+
);
|
|
1636
|
+
});
|
|
1600
1637
|
}
|
|
1601
1638
|
/**
|
|
1602
1639
|
* Returns the total count of records matching the optional WHERE clause.
|
|
@@ -1640,7 +1677,7 @@ var LibSQLDB = class extends base.MastraBase {
|
|
|
1640
1677
|
// SQLite uses 0/1 for booleans
|
|
1641
1678
|
case "jsonb":
|
|
1642
1679
|
return "TEXT";
|
|
1643
|
-
//
|
|
1680
|
+
// SQLite: column stores TEXT, we use jsonb()/json() functions for binary optimization
|
|
1644
1681
|
default:
|
|
1645
1682
|
return storage.getSqlType(type);
|
|
1646
1683
|
}
|
|
@@ -1668,6 +1705,9 @@ var LibSQLDB = class extends base.MastraBase {
|
|
|
1668
1705
|
if (tableName === storage.TABLE_WORKFLOW_SNAPSHOT) {
|
|
1669
1706
|
tableConstraints.push("UNIQUE (workflow_name, run_id)");
|
|
1670
1707
|
}
|
|
1708
|
+
if (tableName === storage.TABLE_SPANS) {
|
|
1709
|
+
tableConstraints.push("UNIQUE (spanId, traceId)");
|
|
1710
|
+
}
|
|
1671
1711
|
const allDefinitions = [...columnDefinitions, ...tableConstraints].join(",\n ");
|
|
1672
1712
|
const sql = `CREATE TABLE IF NOT EXISTS ${parsedTableName} (
|
|
1673
1713
|
${allDefinitions}
|
|
@@ -1678,6 +1718,9 @@ var LibSQLDB = class extends base.MastraBase {
|
|
|
1678
1718
|
await this.migrateSpansTable();
|
|
1679
1719
|
}
|
|
1680
1720
|
} catch (error$1) {
|
|
1721
|
+
if (error$1 instanceof error.MastraError) {
|
|
1722
|
+
throw error$1;
|
|
1723
|
+
}
|
|
1681
1724
|
throw new error.MastraError(
|
|
1682
1725
|
{
|
|
1683
1726
|
id: storage.createStorageErrorId("LIBSQL", "CREATE_TABLE", "FAILED"),
|
|
@@ -1691,7 +1734,7 @@ var LibSQLDB = class extends base.MastraBase {
|
|
|
1691
1734
|
}
|
|
1692
1735
|
/**
|
|
1693
1736
|
* Migrates the spans table schema from OLD_SPAN_SCHEMA to current SPAN_SCHEMA.
|
|
1694
|
-
* This adds new columns that don't exist in old schema.
|
|
1737
|
+
* This adds new columns that don't exist in old schema and ensures required indexes exist.
|
|
1695
1738
|
*/
|
|
1696
1739
|
async migrateSpansTable() {
|
|
1697
1740
|
const schema = storage.TABLE_SCHEMAS[storage.TABLE_SPANS];
|
|
@@ -1705,9 +1748,203 @@ var LibSQLDB = class extends base.MastraBase {
|
|
|
1705
1748
|
this.logger.debug(`LibSQLDB: Added column '${columnName}' to ${storage.TABLE_SPANS}`);
|
|
1706
1749
|
}
|
|
1707
1750
|
}
|
|
1751
|
+
const indexExists = await this.spansUniqueIndexExists();
|
|
1752
|
+
if (!indexExists) {
|
|
1753
|
+
const duplicateInfo = await this.checkForDuplicateSpans();
|
|
1754
|
+
if (duplicateInfo.hasDuplicates) {
|
|
1755
|
+
const errorMessage = `
|
|
1756
|
+
===========================================================================
|
|
1757
|
+
MIGRATION REQUIRED: Duplicate spans detected in ${storage.TABLE_SPANS}
|
|
1758
|
+
===========================================================================
|
|
1759
|
+
|
|
1760
|
+
Found ${duplicateInfo.duplicateCount} duplicate (traceId, spanId) combinations.
|
|
1761
|
+
|
|
1762
|
+
The spans table requires a unique constraint on (traceId, spanId), but your
|
|
1763
|
+
database contains duplicate entries that must be resolved first.
|
|
1764
|
+
|
|
1765
|
+
To fix this, run the manual migration command:
|
|
1766
|
+
|
|
1767
|
+
npx mastra migrate
|
|
1768
|
+
|
|
1769
|
+
This command will:
|
|
1770
|
+
1. Remove duplicate spans (keeping the most complete/recent version)
|
|
1771
|
+
2. Add the required unique constraint
|
|
1772
|
+
|
|
1773
|
+
Note: This migration may take some time for large tables.
|
|
1774
|
+
===========================================================================
|
|
1775
|
+
`;
|
|
1776
|
+
throw new error.MastraError({
|
|
1777
|
+
id: storage.createStorageErrorId("LIBSQL", "MIGRATION_REQUIRED", "DUPLICATE_SPANS"),
|
|
1778
|
+
domain: error.ErrorDomain.STORAGE,
|
|
1779
|
+
category: error.ErrorCategory.USER,
|
|
1780
|
+
text: errorMessage
|
|
1781
|
+
});
|
|
1782
|
+
} else {
|
|
1783
|
+
await this.client.execute(
|
|
1784
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS "mastra_ai_spans_spanid_traceid_idx" ON "${storage.TABLE_SPANS}" ("spanId", "traceId")`
|
|
1785
|
+
);
|
|
1786
|
+
this.logger.debug(`LibSQLDB: Created unique index on (spanId, traceId) for ${storage.TABLE_SPANS}`);
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1708
1789
|
this.logger.info(`LibSQLDB: Migration completed for ${storage.TABLE_SPANS}`);
|
|
1790
|
+
} catch (error$1) {
|
|
1791
|
+
if (error$1 instanceof error.MastraError) {
|
|
1792
|
+
throw error$1;
|
|
1793
|
+
}
|
|
1794
|
+
this.logger.warn(`LibSQLDB: Failed to migrate spans table ${storage.TABLE_SPANS}:`, error$1);
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
/**
|
|
1798
|
+
* Checks if the unique index on (spanId, traceId) already exists on the spans table.
|
|
1799
|
+
* Used to skip deduplication when the index already exists (migration already complete).
|
|
1800
|
+
*/
|
|
1801
|
+
async spansUniqueIndexExists() {
|
|
1802
|
+
try {
|
|
1803
|
+
const result = await this.client.execute(
|
|
1804
|
+
`SELECT 1 FROM sqlite_master WHERE type = 'index' AND name = 'mastra_ai_spans_spanid_traceid_idx'`
|
|
1805
|
+
);
|
|
1806
|
+
return (result.rows?.length ?? 0) > 0;
|
|
1807
|
+
} catch {
|
|
1808
|
+
return false;
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
/**
|
|
1812
|
+
* Checks for duplicate (traceId, spanId) combinations in the spans table.
|
|
1813
|
+
* Returns information about duplicates for logging/CLI purposes.
|
|
1814
|
+
*/
|
|
1815
|
+
async checkForDuplicateSpans() {
|
|
1816
|
+
try {
|
|
1817
|
+
const result = await this.client.execute(`
|
|
1818
|
+
SELECT COUNT(*) as duplicate_count FROM (
|
|
1819
|
+
SELECT "spanId", "traceId"
|
|
1820
|
+
FROM "${storage.TABLE_SPANS}"
|
|
1821
|
+
GROUP BY "spanId", "traceId"
|
|
1822
|
+
HAVING COUNT(*) > 1
|
|
1823
|
+
)
|
|
1824
|
+
`);
|
|
1825
|
+
const duplicateCount = Number(result.rows?.[0]?.duplicate_count ?? 0);
|
|
1826
|
+
return {
|
|
1827
|
+
hasDuplicates: duplicateCount > 0,
|
|
1828
|
+
duplicateCount
|
|
1829
|
+
};
|
|
1830
|
+
} catch (error) {
|
|
1831
|
+
this.logger.debug(`LibSQLDB: Could not check for duplicates: ${error}`);
|
|
1832
|
+
return { hasDuplicates: false, duplicateCount: 0 };
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
/**
|
|
1836
|
+
* Manually run the spans migration to deduplicate and add the unique constraint.
|
|
1837
|
+
* This is intended to be called from the CLI when duplicates are detected.
|
|
1838
|
+
*
|
|
1839
|
+
* @returns Migration result with status and details
|
|
1840
|
+
*/
|
|
1841
|
+
async migrateSpans() {
|
|
1842
|
+
const indexExists = await this.spansUniqueIndexExists();
|
|
1843
|
+
if (indexExists) {
|
|
1844
|
+
return {
|
|
1845
|
+
success: true,
|
|
1846
|
+
alreadyMigrated: true,
|
|
1847
|
+
duplicatesRemoved: 0,
|
|
1848
|
+
message: `Migration already complete. Unique index exists on ${storage.TABLE_SPANS}.`
|
|
1849
|
+
};
|
|
1850
|
+
}
|
|
1851
|
+
const duplicateInfo = await this.checkForDuplicateSpans();
|
|
1852
|
+
if (duplicateInfo.hasDuplicates) {
|
|
1853
|
+
this.logger.info(
|
|
1854
|
+
`Found ${duplicateInfo.duplicateCount} duplicate (traceId, spanId) combinations. Starting deduplication...`
|
|
1855
|
+
);
|
|
1856
|
+
await this.deduplicateSpans();
|
|
1857
|
+
} else {
|
|
1858
|
+
this.logger.info(`No duplicate spans found.`);
|
|
1859
|
+
}
|
|
1860
|
+
await this.client.execute(
|
|
1861
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS "mastra_ai_spans_spanid_traceid_idx" ON "${storage.TABLE_SPANS}" ("spanId", "traceId")`
|
|
1862
|
+
);
|
|
1863
|
+
return {
|
|
1864
|
+
success: true,
|
|
1865
|
+
alreadyMigrated: false,
|
|
1866
|
+
duplicatesRemoved: duplicateInfo.duplicateCount,
|
|
1867
|
+
message: duplicateInfo.hasDuplicates ? `Migration complete. Removed duplicates and added unique index to ${storage.TABLE_SPANS}.` : `Migration complete. Added unique index to ${storage.TABLE_SPANS}.`
|
|
1868
|
+
};
|
|
1869
|
+
}
|
|
1870
|
+
/**
|
|
1871
|
+
* Check migration status for the spans table.
|
|
1872
|
+
* Returns information about whether migration is needed.
|
|
1873
|
+
*/
|
|
1874
|
+
async checkSpansMigrationStatus() {
|
|
1875
|
+
const indexExists = await this.spansUniqueIndexExists();
|
|
1876
|
+
if (indexExists) {
|
|
1877
|
+
return {
|
|
1878
|
+
needsMigration: false,
|
|
1879
|
+
hasDuplicates: false,
|
|
1880
|
+
duplicateCount: 0,
|
|
1881
|
+
constraintExists: true,
|
|
1882
|
+
tableName: storage.TABLE_SPANS
|
|
1883
|
+
};
|
|
1884
|
+
}
|
|
1885
|
+
const duplicateInfo = await this.checkForDuplicateSpans();
|
|
1886
|
+
return {
|
|
1887
|
+
needsMigration: true,
|
|
1888
|
+
hasDuplicates: duplicateInfo.hasDuplicates,
|
|
1889
|
+
duplicateCount: duplicateInfo.duplicateCount,
|
|
1890
|
+
constraintExists: false,
|
|
1891
|
+
tableName: storage.TABLE_SPANS
|
|
1892
|
+
};
|
|
1893
|
+
}
|
|
1894
|
+
/**
|
|
1895
|
+
* Deduplicates spans table by removing duplicate (spanId, traceId) combinations.
|
|
1896
|
+
* Keeps the "best" record for each duplicate group based on:
|
|
1897
|
+
* 1. Completed spans (endedAt IS NOT NULL) over incomplete ones
|
|
1898
|
+
* 2. Most recently updated (updatedAt DESC)
|
|
1899
|
+
* 3. Most recently created (createdAt DESC) as tiebreaker
|
|
1900
|
+
*/
|
|
1901
|
+
async deduplicateSpans() {
|
|
1902
|
+
try {
|
|
1903
|
+
const duplicateCheck = await this.client.execute(`
|
|
1904
|
+
SELECT COUNT(*) as duplicate_count FROM (
|
|
1905
|
+
SELECT "spanId", "traceId"
|
|
1906
|
+
FROM "${storage.TABLE_SPANS}"
|
|
1907
|
+
GROUP BY "spanId", "traceId"
|
|
1908
|
+
HAVING COUNT(*) > 1
|
|
1909
|
+
)
|
|
1910
|
+
`);
|
|
1911
|
+
const duplicateCount = Number(duplicateCheck.rows?.[0]?.duplicate_count ?? 0);
|
|
1912
|
+
if (duplicateCount === 0) {
|
|
1913
|
+
this.logger.debug(`LibSQLDB: No duplicate spans found, skipping deduplication`);
|
|
1914
|
+
return;
|
|
1915
|
+
}
|
|
1916
|
+
this.logger.warn(`LibSQLDB: Found ${duplicateCount} duplicate (spanId, traceId) combinations, deduplicating...`);
|
|
1917
|
+
const deleteResult = await this.client.execute(`
|
|
1918
|
+
DELETE FROM "${storage.TABLE_SPANS}"
|
|
1919
|
+
WHERE rowid NOT IN (
|
|
1920
|
+
SELECT MIN(best_rowid) FROM (
|
|
1921
|
+
SELECT
|
|
1922
|
+
rowid as best_rowid,
|
|
1923
|
+
"spanId",
|
|
1924
|
+
"traceId",
|
|
1925
|
+
ROW_NUMBER() OVER (
|
|
1926
|
+
PARTITION BY "spanId", "traceId"
|
|
1927
|
+
ORDER BY
|
|
1928
|
+
CASE WHEN "endedAt" IS NOT NULL THEN 0 ELSE 1 END,
|
|
1929
|
+
"updatedAt" DESC,
|
|
1930
|
+
"createdAt" DESC
|
|
1931
|
+
) as rn
|
|
1932
|
+
FROM "${storage.TABLE_SPANS}"
|
|
1933
|
+
) ranked
|
|
1934
|
+
WHERE rn = 1
|
|
1935
|
+
GROUP BY "spanId", "traceId"
|
|
1936
|
+
)
|
|
1937
|
+
AND ("spanId", "traceId") IN (
|
|
1938
|
+
SELECT "spanId", "traceId"
|
|
1939
|
+
FROM "${storage.TABLE_SPANS}"
|
|
1940
|
+
GROUP BY "spanId", "traceId"
|
|
1941
|
+
HAVING COUNT(*) > 1
|
|
1942
|
+
)
|
|
1943
|
+
`);
|
|
1944
|
+
const deletedCount = deleteResult.rowsAffected ?? 0;
|
|
1945
|
+
this.logger.warn(`LibSQLDB: Deleted ${deletedCount} duplicate span records`);
|
|
1709
1946
|
} catch (error) {
|
|
1710
|
-
this.logger.warn(`LibSQLDB: Failed to
|
|
1947
|
+
this.logger.warn(`LibSQLDB: Failed to deduplicate spans:`, error);
|
|
1711
1948
|
}
|
|
1712
1949
|
}
|
|
1713
1950
|
/**
|
|
@@ -2212,13 +2449,15 @@ var MemoryLibSQL = class extends storage.MemoryStorage {
|
|
|
2212
2449
|
queryParams.push(resourceId);
|
|
2213
2450
|
}
|
|
2214
2451
|
if (filter?.dateRange?.start) {
|
|
2215
|
-
|
|
2452
|
+
const startOp = filter.dateRange.startExclusive ? ">" : ">=";
|
|
2453
|
+
conditions.push(`"createdAt" ${startOp} ?`);
|
|
2216
2454
|
queryParams.push(
|
|
2217
2455
|
filter.dateRange.start instanceof Date ? filter.dateRange.start.toISOString() : filter.dateRange.start
|
|
2218
2456
|
);
|
|
2219
2457
|
}
|
|
2220
2458
|
if (filter?.dateRange?.end) {
|
|
2221
|
-
|
|
2459
|
+
const endOp = filter.dateRange.endExclusive ? "<" : "<=";
|
|
2460
|
+
conditions.push(`"createdAt" ${endOp} ?`);
|
|
2222
2461
|
queryParams.push(
|
|
2223
2462
|
filter.dateRange.end instanceof Date ? filter.dateRange.end.toISOString() : filter.dateRange.end
|
|
2224
2463
|
);
|
|
@@ -2524,8 +2763,8 @@ var MemoryLibSQL = class extends storage.MemoryStorage {
|
|
|
2524
2763
|
await this.#db.insert({
|
|
2525
2764
|
tableName: storage.TABLE_RESOURCES,
|
|
2526
2765
|
record: {
|
|
2527
|
-
...resource
|
|
2528
|
-
metadata
|
|
2766
|
+
...resource
|
|
2767
|
+
// metadata is handled by prepareStatement which stringifies jsonb columns
|
|
2529
2768
|
}
|
|
2530
2769
|
});
|
|
2531
2770
|
return resource;
|
|
@@ -2562,7 +2801,7 @@ var MemoryLibSQL = class extends storage.MemoryStorage {
|
|
|
2562
2801
|
values.push(workingMemory);
|
|
2563
2802
|
}
|
|
2564
2803
|
if (metadata) {
|
|
2565
|
-
updates.push("metadata = ?");
|
|
2804
|
+
updates.push("metadata = jsonb(?)");
|
|
2566
2805
|
values.push(JSON.stringify(updatedResource.metadata));
|
|
2567
2806
|
}
|
|
2568
2807
|
updates.push("updatedAt = ?");
|
|
@@ -2601,33 +2840,76 @@ var MemoryLibSQL = class extends storage.MemoryStorage {
|
|
|
2601
2840
|
);
|
|
2602
2841
|
}
|
|
2603
2842
|
}
|
|
2604
|
-
async
|
|
2605
|
-
const {
|
|
2606
|
-
|
|
2843
|
+
async listThreads(args) {
|
|
2844
|
+
const { page = 0, perPage: perPageInput, orderBy, filter } = args;
|
|
2845
|
+
try {
|
|
2846
|
+
this.validatePaginationInput(page, perPageInput ?? 100);
|
|
2847
|
+
} catch (error$1) {
|
|
2607
2848
|
throw new error.MastraError(
|
|
2608
2849
|
{
|
|
2609
|
-
id: storage.createStorageErrorId("LIBSQL", "
|
|
2850
|
+
id: storage.createStorageErrorId("LIBSQL", "LIST_THREADS", "INVALID_PAGE"),
|
|
2610
2851
|
domain: error.ErrorDomain.STORAGE,
|
|
2611
2852
|
category: error.ErrorCategory.USER,
|
|
2612
|
-
details: { page }
|
|
2853
|
+
details: { page, ...perPageInput !== void 0 && { perPage: perPageInput } }
|
|
2613
2854
|
},
|
|
2614
|
-
new Error("
|
|
2855
|
+
error$1 instanceof Error ? error$1 : new Error("Invalid pagination parameters")
|
|
2615
2856
|
);
|
|
2616
2857
|
}
|
|
2617
2858
|
const perPage = storage.normalizePerPage(perPageInput, 100);
|
|
2859
|
+
try {
|
|
2860
|
+
this.validateMetadataKeys(filter?.metadata);
|
|
2861
|
+
} catch (error$1) {
|
|
2862
|
+
throw new error.MastraError(
|
|
2863
|
+
{
|
|
2864
|
+
id: storage.createStorageErrorId("LIBSQL", "LIST_THREADS", "INVALID_METADATA_KEY"),
|
|
2865
|
+
domain: error.ErrorDomain.STORAGE,
|
|
2866
|
+
category: error.ErrorCategory.USER,
|
|
2867
|
+
details: { metadataKeys: filter?.metadata ? Object.keys(filter.metadata).join(", ") : "" }
|
|
2868
|
+
},
|
|
2869
|
+
error$1 instanceof Error ? error$1 : new Error("Invalid metadata key")
|
|
2870
|
+
);
|
|
2871
|
+
}
|
|
2618
2872
|
const { offset, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage);
|
|
2619
2873
|
const { field, direction } = this.parseOrderBy(orderBy);
|
|
2620
2874
|
try {
|
|
2621
|
-
const
|
|
2622
|
-
const queryParams = [
|
|
2875
|
+
const whereClauses = [];
|
|
2876
|
+
const queryParams = [];
|
|
2877
|
+
if (filter?.resourceId) {
|
|
2878
|
+
whereClauses.push("resourceId = ?");
|
|
2879
|
+
queryParams.push(filter.resourceId);
|
|
2880
|
+
}
|
|
2881
|
+
if (filter?.metadata && Object.keys(filter.metadata).length > 0) {
|
|
2882
|
+
for (const [key, value] of Object.entries(filter.metadata)) {
|
|
2883
|
+
if (value === null) {
|
|
2884
|
+
whereClauses.push(`json_extract(metadata, '$.${key}') IS NULL`);
|
|
2885
|
+
} else if (typeof value === "boolean") {
|
|
2886
|
+
whereClauses.push(`json_extract(metadata, '$.${key}') = ?`);
|
|
2887
|
+
queryParams.push(value ? 1 : 0);
|
|
2888
|
+
} else if (typeof value === "number") {
|
|
2889
|
+
whereClauses.push(`json_extract(metadata, '$.${key}') = ?`);
|
|
2890
|
+
queryParams.push(value);
|
|
2891
|
+
} else if (typeof value === "string") {
|
|
2892
|
+
whereClauses.push(`json_extract(metadata, '$.${key}') = ?`);
|
|
2893
|
+
queryParams.push(value);
|
|
2894
|
+
} else {
|
|
2895
|
+
throw new error.MastraError({
|
|
2896
|
+
id: storage.createStorageErrorId("LIBSQL", "LIST_THREADS", "INVALID_METADATA_VALUE"),
|
|
2897
|
+
domain: error.ErrorDomain.STORAGE,
|
|
2898
|
+
category: error.ErrorCategory.USER,
|
|
2899
|
+
text: `Metadata filter value for key "${key}" must be a scalar type (string, number, boolean, or null), got ${typeof value}`,
|
|
2900
|
+
details: { key, valueType: typeof value }
|
|
2901
|
+
});
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
|
|
2906
|
+
const baseQuery = `FROM ${storage.TABLE_THREADS} ${whereClause}`;
|
|
2623
2907
|
const mapRowToStorageThreadType = (row) => ({
|
|
2624
2908
|
id: row.id,
|
|
2625
2909
|
resourceId: row.resourceId,
|
|
2626
2910
|
title: row.title,
|
|
2627
2911
|
createdAt: new Date(row.createdAt),
|
|
2628
|
-
// Convert string to Date
|
|
2629
2912
|
updatedAt: new Date(row.updatedAt),
|
|
2630
|
-
// Convert string to Date
|
|
2631
2913
|
metadata: typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata
|
|
2632
2914
|
});
|
|
2633
2915
|
const countResult = await this.#client.execute({
|
|
@@ -2646,7 +2928,7 @@ var MemoryLibSQL = class extends storage.MemoryStorage {
|
|
|
2646
2928
|
}
|
|
2647
2929
|
const limitValue = perPageInput === false ? total : perPage;
|
|
2648
2930
|
const dataResult = await this.#client.execute({
|
|
2649
|
-
sql: `SELECT
|
|
2931
|
+
sql: `SELECT ${buildSelectColumns(storage.TABLE_THREADS)} ${baseQuery} ORDER BY "${field}" ${direction} LIMIT ? OFFSET ?`,
|
|
2650
2932
|
args: [...queryParams, limitValue, offset]
|
|
2651
2933
|
});
|
|
2652
2934
|
const threads = (dataResult.rows || []).map(mapRowToStorageThreadType);
|
|
@@ -2658,12 +2940,18 @@ var MemoryLibSQL = class extends storage.MemoryStorage {
|
|
|
2658
2940
|
hasMore: perPageInput === false ? false : offset + perPage < total
|
|
2659
2941
|
};
|
|
2660
2942
|
} catch (error$1) {
|
|
2943
|
+
if (error$1 instanceof error.MastraError && error$1.category === error.ErrorCategory.USER) {
|
|
2944
|
+
throw error$1;
|
|
2945
|
+
}
|
|
2661
2946
|
const mastraError = new error.MastraError(
|
|
2662
2947
|
{
|
|
2663
|
-
id: storage.createStorageErrorId("LIBSQL", "
|
|
2948
|
+
id: storage.createStorageErrorId("LIBSQL", "LIST_THREADS", "FAILED"),
|
|
2664
2949
|
domain: error.ErrorDomain.STORAGE,
|
|
2665
2950
|
category: error.ErrorCategory.THIRD_PARTY,
|
|
2666
|
-
details: {
|
|
2951
|
+
details: {
|
|
2952
|
+
...filter?.resourceId && { resourceId: filter.resourceId },
|
|
2953
|
+
hasMetadataFilter: !!filter?.metadata
|
|
2954
|
+
}
|
|
2667
2955
|
},
|
|
2668
2956
|
error$1
|
|
2669
2957
|
);
|
|
@@ -2683,8 +2971,8 @@ var MemoryLibSQL = class extends storage.MemoryStorage {
|
|
|
2683
2971
|
await this.#db.insert({
|
|
2684
2972
|
tableName: storage.TABLE_THREADS,
|
|
2685
2973
|
record: {
|
|
2686
|
-
...thread
|
|
2687
|
-
metadata
|
|
2974
|
+
...thread
|
|
2975
|
+
// metadata is handled by prepareStatement which stringifies jsonb columns
|
|
2688
2976
|
}
|
|
2689
2977
|
});
|
|
2690
2978
|
return thread;
|
|
@@ -2731,7 +3019,7 @@ var MemoryLibSQL = class extends storage.MemoryStorage {
|
|
|
2731
3019
|
};
|
|
2732
3020
|
try {
|
|
2733
3021
|
await this.#client.execute({
|
|
2734
|
-
sql: `UPDATE ${storage.TABLE_THREADS} SET title = ?, metadata = ? WHERE id = ?`,
|
|
3022
|
+
sql: `UPDATE ${storage.TABLE_THREADS} SET title = ?, metadata = jsonb(?) WHERE id = ?`,
|
|
2735
3023
|
args: [title, JSON.stringify(updatedThread.metadata), id]
|
|
2736
3024
|
});
|
|
2737
3025
|
return updatedThread;
|
|
@@ -2770,6 +3058,148 @@ var MemoryLibSQL = class extends storage.MemoryStorage {
|
|
|
2770
3058
|
);
|
|
2771
3059
|
}
|
|
2772
3060
|
}
|
|
3061
|
+
async cloneThread(args) {
|
|
3062
|
+
const { sourceThreadId, newThreadId: providedThreadId, resourceId, title, metadata, options } = args;
|
|
3063
|
+
const sourceThread = await this.getThreadById({ threadId: sourceThreadId });
|
|
3064
|
+
if (!sourceThread) {
|
|
3065
|
+
throw new error.MastraError({
|
|
3066
|
+
id: storage.createStorageErrorId("LIBSQL", "CLONE_THREAD", "SOURCE_NOT_FOUND"),
|
|
3067
|
+
domain: error.ErrorDomain.STORAGE,
|
|
3068
|
+
category: error.ErrorCategory.USER,
|
|
3069
|
+
text: `Source thread with id ${sourceThreadId} not found`,
|
|
3070
|
+
details: { sourceThreadId }
|
|
3071
|
+
});
|
|
3072
|
+
}
|
|
3073
|
+
const newThreadId = providedThreadId || crypto.randomUUID();
|
|
3074
|
+
const existingThread = await this.getThreadById({ threadId: newThreadId });
|
|
3075
|
+
if (existingThread) {
|
|
3076
|
+
throw new error.MastraError({
|
|
3077
|
+
id: storage.createStorageErrorId("LIBSQL", "CLONE_THREAD", "THREAD_EXISTS"),
|
|
3078
|
+
domain: error.ErrorDomain.STORAGE,
|
|
3079
|
+
category: error.ErrorCategory.USER,
|
|
3080
|
+
text: `Thread with id ${newThreadId} already exists`,
|
|
3081
|
+
details: { newThreadId }
|
|
3082
|
+
});
|
|
3083
|
+
}
|
|
3084
|
+
try {
|
|
3085
|
+
let messageQuery = `SELECT id, content, role, type, "createdAt", thread_id, "resourceId"
|
|
3086
|
+
FROM "${storage.TABLE_MESSAGES}" WHERE thread_id = ?`;
|
|
3087
|
+
const messageParams = [sourceThreadId];
|
|
3088
|
+
if (options?.messageFilter?.startDate) {
|
|
3089
|
+
messageQuery += ` AND "createdAt" >= ?`;
|
|
3090
|
+
messageParams.push(
|
|
3091
|
+
options.messageFilter.startDate instanceof Date ? options.messageFilter.startDate.toISOString() : options.messageFilter.startDate
|
|
3092
|
+
);
|
|
3093
|
+
}
|
|
3094
|
+
if (options?.messageFilter?.endDate) {
|
|
3095
|
+
messageQuery += ` AND "createdAt" <= ?`;
|
|
3096
|
+
messageParams.push(
|
|
3097
|
+
options.messageFilter.endDate instanceof Date ? options.messageFilter.endDate.toISOString() : options.messageFilter.endDate
|
|
3098
|
+
);
|
|
3099
|
+
}
|
|
3100
|
+
if (options?.messageFilter?.messageIds && options.messageFilter.messageIds.length > 0) {
|
|
3101
|
+
messageQuery += ` AND id IN (${options.messageFilter.messageIds.map(() => "?").join(", ")})`;
|
|
3102
|
+
messageParams.push(...options.messageFilter.messageIds);
|
|
3103
|
+
}
|
|
3104
|
+
messageQuery += ` ORDER BY "createdAt" ASC`;
|
|
3105
|
+
if (options?.messageLimit && options.messageLimit > 0) {
|
|
3106
|
+
const limitQuery = `SELECT * FROM (${messageQuery.replace('ORDER BY "createdAt" ASC', 'ORDER BY "createdAt" DESC')} LIMIT ?) ORDER BY "createdAt" ASC`;
|
|
3107
|
+
messageParams.push(options.messageLimit);
|
|
3108
|
+
messageQuery = limitQuery;
|
|
3109
|
+
}
|
|
3110
|
+
const sourceMessagesResult = await this.#client.execute({ sql: messageQuery, args: messageParams });
|
|
3111
|
+
const sourceMessages = sourceMessagesResult.rows || [];
|
|
3112
|
+
const now = /* @__PURE__ */ new Date();
|
|
3113
|
+
const nowStr = now.toISOString();
|
|
3114
|
+
const lastMessageId = sourceMessages.length > 0 ? sourceMessages[sourceMessages.length - 1].id : void 0;
|
|
3115
|
+
const cloneMetadata = {
|
|
3116
|
+
sourceThreadId,
|
|
3117
|
+
clonedAt: now,
|
|
3118
|
+
...lastMessageId && { lastMessageId }
|
|
3119
|
+
};
|
|
3120
|
+
const newThread = {
|
|
3121
|
+
id: newThreadId,
|
|
3122
|
+
resourceId: resourceId || sourceThread.resourceId,
|
|
3123
|
+
title: title || (sourceThread.title ? `Clone of ${sourceThread.title}` : void 0),
|
|
3124
|
+
metadata: {
|
|
3125
|
+
...metadata,
|
|
3126
|
+
clone: cloneMetadata
|
|
3127
|
+
},
|
|
3128
|
+
createdAt: now,
|
|
3129
|
+
updatedAt: now
|
|
3130
|
+
};
|
|
3131
|
+
const tx = await this.#client.transaction("write");
|
|
3132
|
+
try {
|
|
3133
|
+
await tx.execute({
|
|
3134
|
+
sql: `INSERT INTO "${storage.TABLE_THREADS}" (id, "resourceId", title, metadata, "createdAt", "updatedAt")
|
|
3135
|
+
VALUES (?, ?, ?, jsonb(?), ?, ?)`,
|
|
3136
|
+
args: [
|
|
3137
|
+
newThread.id,
|
|
3138
|
+
newThread.resourceId,
|
|
3139
|
+
newThread.title || null,
|
|
3140
|
+
JSON.stringify(newThread.metadata),
|
|
3141
|
+
nowStr,
|
|
3142
|
+
nowStr
|
|
3143
|
+
]
|
|
3144
|
+
});
|
|
3145
|
+
const clonedMessages = [];
|
|
3146
|
+
const targetResourceId = resourceId || sourceThread.resourceId;
|
|
3147
|
+
for (const sourceMsg of sourceMessages) {
|
|
3148
|
+
const newMessageId = crypto.randomUUID();
|
|
3149
|
+
const contentStr = sourceMsg.content;
|
|
3150
|
+
let parsedContent;
|
|
3151
|
+
try {
|
|
3152
|
+
parsedContent = JSON.parse(contentStr);
|
|
3153
|
+
} catch {
|
|
3154
|
+
parsedContent = { format: 2, parts: [{ type: "text", text: contentStr }] };
|
|
3155
|
+
}
|
|
3156
|
+
await tx.execute({
|
|
3157
|
+
sql: `INSERT INTO "${storage.TABLE_MESSAGES}" (id, thread_id, content, role, type, "createdAt", "resourceId")
|
|
3158
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
3159
|
+
args: [
|
|
3160
|
+
newMessageId,
|
|
3161
|
+
newThreadId,
|
|
3162
|
+
contentStr,
|
|
3163
|
+
sourceMsg.role,
|
|
3164
|
+
sourceMsg.type || "v2",
|
|
3165
|
+
sourceMsg.createdAt,
|
|
3166
|
+
targetResourceId
|
|
3167
|
+
]
|
|
3168
|
+
});
|
|
3169
|
+
clonedMessages.push({
|
|
3170
|
+
id: newMessageId,
|
|
3171
|
+
threadId: newThreadId,
|
|
3172
|
+
content: parsedContent,
|
|
3173
|
+
role: sourceMsg.role,
|
|
3174
|
+
type: sourceMsg.type || void 0,
|
|
3175
|
+
createdAt: new Date(sourceMsg.createdAt),
|
|
3176
|
+
resourceId: targetResourceId
|
|
3177
|
+
});
|
|
3178
|
+
}
|
|
3179
|
+
await tx.commit();
|
|
3180
|
+
return {
|
|
3181
|
+
thread: newThread,
|
|
3182
|
+
clonedMessages
|
|
3183
|
+
};
|
|
3184
|
+
} catch (error) {
|
|
3185
|
+
await tx.rollback();
|
|
3186
|
+
throw error;
|
|
3187
|
+
}
|
|
3188
|
+
} catch (error$1) {
|
|
3189
|
+
if (error$1 instanceof error.MastraError) {
|
|
3190
|
+
throw error$1;
|
|
3191
|
+
}
|
|
3192
|
+
throw new error.MastraError(
|
|
3193
|
+
{
|
|
3194
|
+
id: storage.createStorageErrorId("LIBSQL", "CLONE_THREAD", "FAILED"),
|
|
3195
|
+
domain: error.ErrorDomain.STORAGE,
|
|
3196
|
+
category: error.ErrorCategory.THIRD_PARTY,
|
|
3197
|
+
details: { sourceThreadId, newThreadId }
|
|
3198
|
+
},
|
|
3199
|
+
error$1
|
|
3200
|
+
);
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
2773
3203
|
};
|
|
2774
3204
|
var ObservabilityLibSQL = class extends storage.ObservabilityStorage {
|
|
2775
3205
|
#db;
|
|
@@ -2784,6 +3214,22 @@ var ObservabilityLibSQL = class extends storage.ObservabilityStorage {
|
|
|
2784
3214
|
async dangerouslyClearAll() {
|
|
2785
3215
|
await this.#db.deleteData({ tableName: storage.TABLE_SPANS });
|
|
2786
3216
|
}
|
|
3217
|
+
/**
|
|
3218
|
+
* Manually run the spans migration to deduplicate and add the unique constraint.
|
|
3219
|
+
* This is intended to be called from the CLI when duplicates are detected.
|
|
3220
|
+
*
|
|
3221
|
+
* @returns Migration result with status and details
|
|
3222
|
+
*/
|
|
3223
|
+
async migrateSpans() {
|
|
3224
|
+
return this.#db.migrateSpans();
|
|
3225
|
+
}
|
|
3226
|
+
/**
|
|
3227
|
+
* Check migration status for the spans table.
|
|
3228
|
+
* Returns information about whether migration is needed.
|
|
3229
|
+
*/
|
|
3230
|
+
async checkSpansMigrationStatus() {
|
|
3231
|
+
return this.#db.checkSpansMigrationStatus();
|
|
3232
|
+
}
|
|
2787
3233
|
get tracingStrategy() {
|
|
2788
3234
|
return {
|
|
2789
3235
|
preferred: "batch-with-updates",
|
|
@@ -3251,7 +3697,7 @@ var ScoresLibSQL = class extends storage.ScoresStorage {
|
|
|
3251
3697
|
const limitValue = perPageInput === false ? total : perPage;
|
|
3252
3698
|
const end = perPageInput === false ? total : start + perPage;
|
|
3253
3699
|
const result = await this.#client.execute({
|
|
3254
|
-
sql: `SELECT
|
|
3700
|
+
sql: `SELECT ${buildSelectColumns(storage.TABLE_SCORERS)} FROM ${storage.TABLE_SCORERS} WHERE runId = ? ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
|
|
3255
3701
|
args: [runId, limitValue, start]
|
|
3256
3702
|
});
|
|
3257
3703
|
const scores = result.rows?.map((row) => this.transformScoreRow(row)) ?? [];
|
|
@@ -3324,7 +3770,7 @@ var ScoresLibSQL = class extends storage.ScoresStorage {
|
|
|
3324
3770
|
const limitValue = perPageInput === false ? total : perPage;
|
|
3325
3771
|
const end = perPageInput === false ? total : start + perPage;
|
|
3326
3772
|
const result = await this.#client.execute({
|
|
3327
|
-
sql: `SELECT
|
|
3773
|
+
sql: `SELECT ${buildSelectColumns(storage.TABLE_SCORERS)} FROM ${storage.TABLE_SCORERS} ${whereClause} ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
|
|
3328
3774
|
args: [...queryParams, limitValue, start]
|
|
3329
3775
|
});
|
|
3330
3776
|
const scores = result.rows?.map((row) => this.transformScoreRow(row)) ?? [];
|
|
@@ -3350,16 +3796,13 @@ var ScoresLibSQL = class extends storage.ScoresStorage {
|
|
|
3350
3796
|
}
|
|
3351
3797
|
/**
|
|
3352
3798
|
* LibSQL-specific score row transformation.
|
|
3353
|
-
* Maps additionalLLMContext column to additionalContext field.
|
|
3354
3799
|
*/
|
|
3355
3800
|
transformScoreRow(row) {
|
|
3356
|
-
return storage.transformScoreRow(row
|
|
3357
|
-
fieldMappings: { additionalContext: "additionalLLMContext" }
|
|
3358
|
-
});
|
|
3801
|
+
return storage.transformScoreRow(row);
|
|
3359
3802
|
}
|
|
3360
3803
|
async getScoreById({ id }) {
|
|
3361
3804
|
const result = await this.#client.execute({
|
|
3362
|
-
sql: `SELECT
|
|
3805
|
+
sql: `SELECT ${buildSelectColumns(storage.TABLE_SCORERS)} FROM ${storage.TABLE_SCORERS} WHERE id = ?`,
|
|
3363
3806
|
args: [id]
|
|
3364
3807
|
});
|
|
3365
3808
|
return result.rows?.[0] ? this.transformScoreRow(result.rows[0]) : null;
|
|
@@ -3437,7 +3880,7 @@ var ScoresLibSQL = class extends storage.ScoresStorage {
|
|
|
3437
3880
|
const limitValue = perPageInput === false ? total : perPage;
|
|
3438
3881
|
const end = perPageInput === false ? total : start + perPage;
|
|
3439
3882
|
const result = await this.#client.execute({
|
|
3440
|
-
sql: `SELECT
|
|
3883
|
+
sql: `SELECT ${buildSelectColumns(storage.TABLE_SCORERS)} FROM ${storage.TABLE_SCORERS} WHERE entityId = ? AND entityType = ? ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
|
|
3441
3884
|
args: [entityId, entityType, limitValue, start]
|
|
3442
3885
|
});
|
|
3443
3886
|
const scores = result.rows?.map((row) => this.transformScoreRow(row)) ?? [];
|
|
@@ -3478,7 +3921,7 @@ var ScoresLibSQL = class extends storage.ScoresStorage {
|
|
|
3478
3921
|
const limitValue = perPageInput === false ? total : perPage;
|
|
3479
3922
|
const end = perPageInput === false ? total : start + perPage;
|
|
3480
3923
|
const result = await this.#client.execute({
|
|
3481
|
-
sql: `SELECT
|
|
3924
|
+
sql: `SELECT ${buildSelectColumns(storage.TABLE_SCORERS)} FROM ${storage.TABLE_SCORERS} WHERE traceId = ? AND spanId = ? ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
|
|
3482
3925
|
args: [traceId, spanId, limitValue, start]
|
|
3483
3926
|
});
|
|
3484
3927
|
const scores = result.rows?.map((row) => this.transformScoreRow(row)) ?? [];
|
|
@@ -3503,24 +3946,6 @@ var ScoresLibSQL = class extends storage.ScoresStorage {
|
|
|
3503
3946
|
}
|
|
3504
3947
|
}
|
|
3505
3948
|
};
|
|
3506
|
-
function parseWorkflowRun(row) {
|
|
3507
|
-
let parsedSnapshot = row.snapshot;
|
|
3508
|
-
if (typeof parsedSnapshot === "string") {
|
|
3509
|
-
try {
|
|
3510
|
-
parsedSnapshot = JSON.parse(row.snapshot);
|
|
3511
|
-
} catch (e) {
|
|
3512
|
-
console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
|
|
3513
|
-
}
|
|
3514
|
-
}
|
|
3515
|
-
return {
|
|
3516
|
-
workflowName: row.workflow_name,
|
|
3517
|
-
runId: row.run_id,
|
|
3518
|
-
snapshot: parsedSnapshot,
|
|
3519
|
-
resourceId: row.resourceId,
|
|
3520
|
-
createdAt: new Date(row.createdAt),
|
|
3521
|
-
updatedAt: new Date(row.updatedAt)
|
|
3522
|
-
};
|
|
3523
|
-
}
|
|
3524
3949
|
var WorkflowsLibSQL = class extends storage.WorkflowsStorage {
|
|
3525
3950
|
#db;
|
|
3526
3951
|
#client;
|
|
@@ -3541,6 +3966,24 @@ var WorkflowsLibSQL = class extends storage.WorkflowsStorage {
|
|
|
3541
3966
|
(err) => this.logger.warn("LibSQL Workflows: Failed to setup PRAGMA settings.", err)
|
|
3542
3967
|
);
|
|
3543
3968
|
}
|
|
3969
|
+
parseWorkflowRun(row) {
|
|
3970
|
+
let parsedSnapshot = row.snapshot;
|
|
3971
|
+
if (typeof parsedSnapshot === "string") {
|
|
3972
|
+
try {
|
|
3973
|
+
parsedSnapshot = JSON.parse(row.snapshot);
|
|
3974
|
+
} catch (e) {
|
|
3975
|
+
this.logger.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
3978
|
+
return {
|
|
3979
|
+
workflowName: row.workflow_name,
|
|
3980
|
+
runId: row.run_id,
|
|
3981
|
+
snapshot: parsedSnapshot,
|
|
3982
|
+
resourceId: row.resourceId,
|
|
3983
|
+
createdAt: new Date(row.createdAt),
|
|
3984
|
+
updatedAt: new Date(row.updatedAt)
|
|
3985
|
+
};
|
|
3986
|
+
}
|
|
3544
3987
|
async init() {
|
|
3545
3988
|
const schema = storage.TABLE_SCHEMAS[storage.TABLE_WORKFLOW_SNAPSHOT];
|
|
3546
3989
|
await this.#db.createTable({ tableName: storage.TABLE_WORKFLOW_SNAPSHOT, schema });
|
|
@@ -3584,7 +4027,7 @@ var WorkflowsLibSQL = class extends storage.WorkflowsStorage {
|
|
|
3584
4027
|
const tx = await this.#client.transaction("write");
|
|
3585
4028
|
try {
|
|
3586
4029
|
const existingSnapshotResult = await tx.execute({
|
|
3587
|
-
sql: `SELECT snapshot FROM ${storage.TABLE_WORKFLOW_SNAPSHOT} WHERE workflow_name = ? AND run_id = ?`,
|
|
4030
|
+
sql: `SELECT json(snapshot) as snapshot FROM ${storage.TABLE_WORKFLOW_SNAPSHOT} WHERE workflow_name = ? AND run_id = ?`,
|
|
3588
4031
|
args: [workflowName, runId]
|
|
3589
4032
|
});
|
|
3590
4033
|
let snapshot;
|
|
@@ -3609,9 +4052,13 @@ var WorkflowsLibSQL = class extends storage.WorkflowsStorage {
|
|
|
3609
4052
|
}
|
|
3610
4053
|
snapshot.context[stepId] = result;
|
|
3611
4054
|
snapshot.requestContext = { ...snapshot.requestContext, ...requestContext };
|
|
4055
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3612
4056
|
await tx.execute({
|
|
3613
|
-
sql: `
|
|
3614
|
-
|
|
4057
|
+
sql: `INSERT INTO ${storage.TABLE_WORKFLOW_SNAPSHOT} (workflow_name, run_id, snapshot, createdAt, updatedAt)
|
|
4058
|
+
VALUES (?, ?, jsonb(?), ?, ?)
|
|
4059
|
+
ON CONFLICT(workflow_name, run_id)
|
|
4060
|
+
DO UPDATE SET snapshot = excluded.snapshot, updatedAt = excluded.updatedAt`,
|
|
4061
|
+
args: [workflowName, runId, JSON.stringify(snapshot), now, now]
|
|
3615
4062
|
});
|
|
3616
4063
|
await tx.commit();
|
|
3617
4064
|
return snapshot.context;
|
|
@@ -3632,7 +4079,7 @@ var WorkflowsLibSQL = class extends storage.WorkflowsStorage {
|
|
|
3632
4079
|
const tx = await this.#client.transaction("write");
|
|
3633
4080
|
try {
|
|
3634
4081
|
const existingSnapshotResult = await tx.execute({
|
|
3635
|
-
sql: `SELECT snapshot FROM ${storage.TABLE_WORKFLOW_SNAPSHOT} WHERE workflow_name = ? AND run_id = ?`,
|
|
4082
|
+
sql: `SELECT json(snapshot) as snapshot FROM ${storage.TABLE_WORKFLOW_SNAPSHOT} WHERE workflow_name = ? AND run_id = ?`,
|
|
3636
4083
|
args: [workflowName, runId]
|
|
3637
4084
|
});
|
|
3638
4085
|
if (!existingSnapshotResult.rows?.[0]) {
|
|
@@ -3647,7 +4094,7 @@ var WorkflowsLibSQL = class extends storage.WorkflowsStorage {
|
|
|
3647
4094
|
}
|
|
3648
4095
|
const updatedSnapshot = { ...snapshot, ...opts };
|
|
3649
4096
|
await tx.execute({
|
|
3650
|
-
sql: `UPDATE ${storage.TABLE_WORKFLOW_SNAPSHOT} SET snapshot = ? WHERE workflow_name = ? AND run_id = ?`,
|
|
4097
|
+
sql: `UPDATE ${storage.TABLE_WORKFLOW_SNAPSHOT} SET snapshot = jsonb(?) WHERE workflow_name = ? AND run_id = ?`,
|
|
3651
4098
|
args: [JSON.stringify(updatedSnapshot), workflowName, runId]
|
|
3652
4099
|
});
|
|
3653
4100
|
await tx.commit();
|
|
@@ -3711,13 +4158,13 @@ var WorkflowsLibSQL = class extends storage.WorkflowsStorage {
|
|
|
3711
4158
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
3712
4159
|
try {
|
|
3713
4160
|
const result = await this.#client.execute({
|
|
3714
|
-
sql: `SELECT
|
|
4161
|
+
sql: `SELECT workflow_name, run_id, resourceId, json(snapshot) as snapshot, createdAt, updatedAt FROM ${storage.TABLE_WORKFLOW_SNAPSHOT} ${whereClause} ORDER BY createdAt DESC LIMIT 1`,
|
|
3715
4162
|
args
|
|
3716
4163
|
});
|
|
3717
4164
|
if (!result.rows?.[0]) {
|
|
3718
4165
|
return null;
|
|
3719
4166
|
}
|
|
3720
|
-
return parseWorkflowRun(result.rows[0]);
|
|
4167
|
+
return this.parseWorkflowRun(result.rows[0]);
|
|
3721
4168
|
} catch (error$1) {
|
|
3722
4169
|
throw new error.MastraError(
|
|
3723
4170
|
{
|
|
@@ -3783,7 +4230,7 @@ var WorkflowsLibSQL = class extends storage.WorkflowsStorage {
|
|
|
3783
4230
|
conditions.push("resourceId = ?");
|
|
3784
4231
|
args.push(resourceId);
|
|
3785
4232
|
} else {
|
|
3786
|
-
|
|
4233
|
+
this.logger.warn(`[${storage.TABLE_WORKFLOW_SNAPSHOT}] resourceId column not found. Skipping resourceId filter.`);
|
|
3787
4234
|
}
|
|
3788
4235
|
}
|
|
3789
4236
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
@@ -3799,10 +4246,10 @@ var WorkflowsLibSQL = class extends storage.WorkflowsStorage {
|
|
|
3799
4246
|
const normalizedPerPage = usePagination ? storage.normalizePerPage(perPage, Number.MAX_SAFE_INTEGER) : 0;
|
|
3800
4247
|
const offset = usePagination ? page * normalizedPerPage : 0;
|
|
3801
4248
|
const result = await this.#client.execute({
|
|
3802
|
-
sql: `SELECT
|
|
4249
|
+
sql: `SELECT workflow_name, run_id, resourceId, json(snapshot) as snapshot, createdAt, updatedAt FROM ${storage.TABLE_WORKFLOW_SNAPSHOT} ${whereClause} ORDER BY createdAt DESC${usePagination ? ` LIMIT ? OFFSET ?` : ""}`,
|
|
3803
4250
|
args: usePagination ? [...args, normalizedPerPage, offset] : args
|
|
3804
4251
|
});
|
|
3805
|
-
const runs = (result.rows || []).map((row) => parseWorkflowRun(row));
|
|
4252
|
+
const runs = (result.rows || []).map((row) => this.parseWorkflowRun(row));
|
|
3806
4253
|
return { runs, total: total || runs.length };
|
|
3807
4254
|
} catch (error$1) {
|
|
3808
4255
|
throw new error.MastraError(
|
|
@@ -3818,7 +4265,7 @@ var WorkflowsLibSQL = class extends storage.WorkflowsStorage {
|
|
|
3818
4265
|
};
|
|
3819
4266
|
|
|
3820
4267
|
// src/storage/index.ts
|
|
3821
|
-
var LibSQLStore = class extends storage.
|
|
4268
|
+
var LibSQLStore = class extends storage.MastraCompositeStore {
|
|
3822
4269
|
client;
|
|
3823
4270
|
maxRetries;
|
|
3824
4271
|
initialBackoffMs;
|
|
@@ -3863,19 +4310,6 @@ var LibSQLStore = class extends storage.MastraStorage {
|
|
|
3863
4310
|
agents
|
|
3864
4311
|
};
|
|
3865
4312
|
}
|
|
3866
|
-
get supports() {
|
|
3867
|
-
return {
|
|
3868
|
-
selectByIncludeResourceScope: true,
|
|
3869
|
-
resourceWorkingMemory: true,
|
|
3870
|
-
hasColumn: true,
|
|
3871
|
-
createTable: true,
|
|
3872
|
-
deleteMessages: true,
|
|
3873
|
-
observability: true,
|
|
3874
|
-
indexManagement: false,
|
|
3875
|
-
listScoresBySpan: true,
|
|
3876
|
-
agents: true
|
|
3877
|
-
};
|
|
3878
|
-
}
|
|
3879
4313
|
};
|
|
3880
4314
|
|
|
3881
4315
|
// src/vector/prompt.ts
|
|
@@ -3977,9 +4411,14 @@ Example Complex Query:
|
|
|
3977
4411
|
]
|
|
3978
4412
|
}`;
|
|
3979
4413
|
|
|
4414
|
+
exports.AgentsLibSQL = AgentsLibSQL;
|
|
3980
4415
|
exports.DefaultStorage = LibSQLStore;
|
|
3981
4416
|
exports.LIBSQL_PROMPT = LIBSQL_PROMPT;
|
|
3982
4417
|
exports.LibSQLStore = LibSQLStore;
|
|
3983
4418
|
exports.LibSQLVector = LibSQLVector;
|
|
4419
|
+
exports.MemoryLibSQL = MemoryLibSQL;
|
|
4420
|
+
exports.ObservabilityLibSQL = ObservabilityLibSQL;
|
|
4421
|
+
exports.ScoresLibSQL = ScoresLibSQL;
|
|
4422
|
+
exports.WorkflowsLibSQL = WorkflowsLibSQL;
|
|
3984
4423
|
//# sourceMappingURL=index.cjs.map
|
|
3985
4424
|
//# sourceMappingURL=index.cjs.map
|