@mastra/libsql 1.0.0-beta.13 → 1.0.0-beta.14
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 +58 -0
- package/dist/docs/README.md +1 -1
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/SOURCE_MAP.json +1 -1
- package/dist/docs/memory/02-storage.md +3 -3
- package/dist/docs/storage/01-reference.md +15 -15
- package/dist/index.cjs +231 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +232 -23
- 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/domains/observability/index.d.ts +23 -0
- package/dist/storage/domains/observability/index.d.ts.map +1 -1
- package/dist/storage/index.d.ts +2 -2
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/vector/index.d.ts.map +1 -1
- package/dist/vector/sql-builder.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createClient } from '@libsql/client';
|
|
2
2
|
import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
|
|
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,
|
|
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
7
|
import { MastraBase } from '@mastra/core/base';
|
|
8
8
|
import { MessageList } from '@mastra/core/agent';
|
|
@@ -242,10 +242,10 @@ var FILTER_OPERATORS = {
|
|
|
242
242
|
};
|
|
243
243
|
},
|
|
244
244
|
// Element Operators
|
|
245
|
-
$exists: (key) => {
|
|
245
|
+
$exists: (key, value) => {
|
|
246
246
|
const jsonPath = getJsonPath(key);
|
|
247
247
|
return {
|
|
248
|
-
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`,
|
|
249
249
|
needsValue: false
|
|
250
250
|
};
|
|
251
251
|
},
|
|
@@ -538,7 +538,7 @@ var LibSQLVector = class extends MastraVector {
|
|
|
538
538
|
try {
|
|
539
539
|
return await operation();
|
|
540
540
|
} catch (error) {
|
|
541
|
-
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")) {
|
|
542
542
|
attempts++;
|
|
543
543
|
if (attempts >= this.maxRetries) {
|
|
544
544
|
this.logger.error(
|
|
@@ -572,22 +572,14 @@ var LibSQLVector = class extends MastraVector {
|
|
|
572
572
|
minScore = -1
|
|
573
573
|
// Default to -1 to include all results (cosine similarity ranges from -1 to 1)
|
|
574
574
|
}) {
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
throw new MastraError(
|
|
584
|
-
{
|
|
585
|
-
id: createVectorErrorId("LIBSQL", "QUERY", "INVALID_ARGS"),
|
|
586
|
-
domain: ErrorDomain.STORAGE,
|
|
587
|
-
category: ErrorCategory.USER
|
|
588
|
-
},
|
|
589
|
-
error
|
|
590
|
-
);
|
|
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
|
+
});
|
|
591
583
|
}
|
|
592
584
|
try {
|
|
593
585
|
const parsedIndexName = parseSqlIdentifier(indexName, "index name");
|
|
@@ -647,6 +639,7 @@ var LibSQLVector = class extends MastraVector {
|
|
|
647
639
|
}
|
|
648
640
|
}
|
|
649
641
|
async doUpsert({ indexName, vectors, metadata, ids }) {
|
|
642
|
+
validateUpsertInput("LIBSQL", vectors, metadata, ids);
|
|
650
643
|
const tx = await this.turso.transaction("write");
|
|
651
644
|
try {
|
|
652
645
|
const parsedIndexName = parseSqlIdentifier(indexName, "index name");
|
|
@@ -1710,6 +1703,9 @@ var LibSQLDB = class extends MastraBase {
|
|
|
1710
1703
|
if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
|
|
1711
1704
|
tableConstraints.push("UNIQUE (workflow_name, run_id)");
|
|
1712
1705
|
}
|
|
1706
|
+
if (tableName === TABLE_SPANS) {
|
|
1707
|
+
tableConstraints.push("UNIQUE (spanId, traceId)");
|
|
1708
|
+
}
|
|
1713
1709
|
const allDefinitions = [...columnDefinitions, ...tableConstraints].join(",\n ");
|
|
1714
1710
|
const sql = `CREATE TABLE IF NOT EXISTS ${parsedTableName} (
|
|
1715
1711
|
${allDefinitions}
|
|
@@ -1720,6 +1716,9 @@ var LibSQLDB = class extends MastraBase {
|
|
|
1720
1716
|
await this.migrateSpansTable();
|
|
1721
1717
|
}
|
|
1722
1718
|
} catch (error) {
|
|
1719
|
+
if (error instanceof MastraError) {
|
|
1720
|
+
throw error;
|
|
1721
|
+
}
|
|
1723
1722
|
throw new MastraError(
|
|
1724
1723
|
{
|
|
1725
1724
|
id: createStorageErrorId("LIBSQL", "CREATE_TABLE", "FAILED"),
|
|
@@ -1733,7 +1732,7 @@ var LibSQLDB = class extends MastraBase {
|
|
|
1733
1732
|
}
|
|
1734
1733
|
/**
|
|
1735
1734
|
* Migrates the spans table schema from OLD_SPAN_SCHEMA to current SPAN_SCHEMA.
|
|
1736
|
-
* This adds new columns that don't exist in old schema.
|
|
1735
|
+
* This adds new columns that don't exist in old schema and ensures required indexes exist.
|
|
1737
1736
|
*/
|
|
1738
1737
|
async migrateSpansTable() {
|
|
1739
1738
|
const schema = TABLE_SCHEMAS[TABLE_SPANS];
|
|
@@ -1747,11 +1746,205 @@ var LibSQLDB = class extends MastraBase {
|
|
|
1747
1746
|
this.logger.debug(`LibSQLDB: Added column '${columnName}' to ${TABLE_SPANS}`);
|
|
1748
1747
|
}
|
|
1749
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
|
+
}
|
|
1750
1787
|
this.logger.info(`LibSQLDB: Migration completed for ${TABLE_SPANS}`);
|
|
1751
1788
|
} catch (error) {
|
|
1789
|
+
if (error instanceof MastraError) {
|
|
1790
|
+
throw error;
|
|
1791
|
+
}
|
|
1752
1792
|
this.logger.warn(`LibSQLDB: Failed to migrate spans table ${TABLE_SPANS}:`, error);
|
|
1753
1793
|
}
|
|
1754
1794
|
}
|
|
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() {
|
|
1800
|
+
try {
|
|
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;
|
|
1805
|
+
} catch {
|
|
1806
|
+
return false;
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
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...`
|
|
1853
|
+
);
|
|
1854
|
+
await this.deduplicateSpans();
|
|
1855
|
+
} else {
|
|
1856
|
+
this.logger.info(`No duplicate spans found.`);
|
|
1857
|
+
}
|
|
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
|
+
};
|
|
1867
|
+
}
|
|
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() {
|
|
1900
|
+
try {
|
|
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
|
+
}
|
|
1755
1948
|
/**
|
|
1756
1949
|
* Gets a default value for a column type (used when adding NOT NULL columns).
|
|
1757
1950
|
*/
|
|
@@ -3019,6 +3212,22 @@ var ObservabilityLibSQL = class extends ObservabilityStorage {
|
|
|
3019
3212
|
async dangerouslyClearAll() {
|
|
3020
3213
|
await this.#db.deleteData({ tableName: TABLE_SPANS });
|
|
3021
3214
|
}
|
|
3215
|
+
/**
|
|
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
|
|
3220
|
+
*/
|
|
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
|
+
}
|
|
3022
3231
|
get tracingStrategy() {
|
|
3023
3232
|
return {
|
|
3024
3233
|
preferred: "batch-with-updates",
|
|
@@ -4054,7 +4263,7 @@ var WorkflowsLibSQL = class extends WorkflowsStorage {
|
|
|
4054
4263
|
};
|
|
4055
4264
|
|
|
4056
4265
|
// src/storage/index.ts
|
|
4057
|
-
var LibSQLStore = class extends
|
|
4266
|
+
var LibSQLStore = class extends MastraCompositeStore {
|
|
4058
4267
|
client;
|
|
4059
4268
|
maxRetries;
|
|
4060
4269
|
initialBackoffMs;
|