'use strict'; var client = require('@libsql/client'); var error = require('@mastra/core/error'); var storage = require('@mastra/core/storage'); var utils = require('@mastra/core/utils'); var vector = require('@mastra/core/vector'); var filter = require('@mastra/core/vector/filter'); var base = require('@mastra/core/base'); var crypto$1 = require('crypto'); var agent = require('@mastra/core/agent'); var evals = require('@mastra/core/evals'); var skills = require('@mastra/core/storage/domains/skills'); // src/vector/index.ts var LibSQLFilterTranslator = class extends filter.BaseFilterTranslator { getSupportedOperators() { return { ...filter.BaseFilterTranslator.DEFAULT_OPERATORS, regex: [], custom: ["$contains", "$size"] }; } translate(filter) { if (this.isEmpty(filter)) { return filter; } this.validateFilter(filter); return this.translateNode(filter); } translateNode(node, currentPath = "") { if (this.isRegex(node)) { throw new Error("Direct regex pattern format is not supported in LibSQL"); } const withPath = (result2) => currentPath ? { [currentPath]: result2 } : result2; if (this.isPrimitive(node)) { return withPath({ $eq: this.normalizeComparisonValue(node) }); } if (Array.isArray(node)) { return withPath({ $in: this.normalizeArrayValues(node) }); } const entries = Object.entries(node); const result = {}; for (const [key, value] of entries) { const newPath = currentPath ? `${currentPath}.${key}` : key; if (this.isLogicalOperator(key)) { result[key] = Array.isArray(value) ? value.map((filter) => this.translateNode(filter)) : this.translateNode(value); } else if (this.isOperator(key)) { if (this.isArrayOperator(key) && !Array.isArray(value) && key !== "$elemMatch") { result[key] = [value]; } else if (this.isBasicOperator(key) && Array.isArray(value)) { result[key] = JSON.stringify(value); } else { result[key] = value; } } else if (typeof value === "object" && value !== null) { const hasOperators = Object.keys(value).some((k) => this.isOperator(k)); if (hasOperators) { result[newPath] = this.translateNode(value); } else { Object.assign(result, this.translateNode(value, newPath)); } } else { result[newPath] = this.translateNode(value); } } return result; } // TODO: Look more into regex support for LibSQL // private translateRegexPattern(pattern: string, options: string = ''): any { // if (!options) return { $regex: pattern }; // const flags = options // .split('') // .filter(f => 'imsux'.includes(f)) // .join(''); // return { // $regex: pattern, // $options: flags, // }; // } }; var createBasicOperator = (symbol) => { return (key, value) => { const jsonPath = getJsonPath(key); return { sql: `CASE WHEN ? IS NULL THEN json_extract(metadata, ${jsonPath}) IS ${symbol === "=" ? "" : "NOT"} NULL ELSE json_extract(metadata, ${jsonPath}) ${symbol} ? END`, needsValue: true, transformValue: () => { return [value, value]; } }; }; }; var createNumericOperator = (symbol) => { return (key, value) => { const jsonPath = getJsonPath(key); const isNumeric = typeof value === "number" || typeof value === "string" && !isNaN(Number(value)) && value.trim() !== ""; if (isNumeric) { return { sql: `CAST(json_extract(metadata, ${jsonPath}) AS NUMERIC) ${symbol} ?`, needsValue: true }; } else { return { sql: `CAST(json_extract(metadata, ${jsonPath}) AS TEXT) ${symbol} ?`, needsValue: true }; } }; }; var validateJsonArray = (key) => { const jsonPath = getJsonPath(key); return `json_valid(json_extract(metadata, ${jsonPath})) AND json_type(json_extract(metadata, ${jsonPath})) = 'array'`; }; var pattern = /json_extract\(metadata, '\$\.(?:"[^"]*"(?:\."[^"]*")*|[^']+)'\)/g; function buildElemMatchConditions(value) { const conditions = Object.entries(value).map(([field, fieldValue]) => { if (field.startsWith("$")) { const { sql, values } = buildCondition("elem.value", { [field]: fieldValue }); const elemSql = sql.replace(pattern, "elem.value"); return { sql: elemSql, values }; } else if (typeof fieldValue === "object" && !Array.isArray(fieldValue)) { const { sql, values } = buildCondition(field, fieldValue); const jsonPath = parseJsonPathKey(field); const elemSql = sql.replace(pattern, `json_extract(elem.value, '$.${jsonPath}')`); return { sql: elemSql, values }; } else { const jsonPath = parseJsonPathKey(field); return { sql: `json_extract(elem.value, '$.${jsonPath}') = ?`, values: [fieldValue] }; } }); return conditions; } var FILTER_OPERATORS = { $eq: createBasicOperator("="), $ne: createBasicOperator("!="), $gt: createNumericOperator(">"), $gte: createNumericOperator(">="), $lt: createNumericOperator("<"), $lte: createNumericOperator("<="), // Array Operators $in: (key, value) => { const jsonPath = getJsonPath(key); const arr = Array.isArray(value) ? value : [value]; if (arr.length === 0) { return { sql: "1 = 0", needsValue: true, transformValue: () => [] }; } const paramPlaceholders = arr.map(() => "?").join(","); return { sql: `( CASE WHEN ${validateJsonArray(key)} THEN EXISTS ( SELECT 1 FROM json_each(json_extract(metadata, ${jsonPath})) as elem WHERE elem.value IN (SELECT value FROM json_each(?)) ) ELSE json_extract(metadata, ${jsonPath}) IN (${paramPlaceholders}) END )`, needsValue: true, transformValue: () => [JSON.stringify(arr), ...arr] }; }, $nin: (key, value) => { const jsonPath = getJsonPath(key); const arr = Array.isArray(value) ? value : [value]; if (arr.length === 0) { return { sql: "1 = 1", needsValue: true, transformValue: () => [] }; } const paramPlaceholders = arr.map(() => "?").join(","); return { sql: `( CASE WHEN ${validateJsonArray(key)} THEN NOT EXISTS ( SELECT 1 FROM json_each(json_extract(metadata, ${jsonPath})) as elem WHERE elem.value IN (SELECT value FROM json_each(?)) ) ELSE json_extract(metadata, ${jsonPath}) NOT IN (${paramPlaceholders}) END )`, needsValue: true, transformValue: () => [JSON.stringify(arr), ...arr] }; }, $all: (key, value) => { const jsonPath = getJsonPath(key); let sql; const arrayValue = Array.isArray(value) ? value : [value]; if (arrayValue.length === 0) { sql = "1 = 0"; } else { sql = `( CASE WHEN ${validateJsonArray(key)} THEN NOT EXISTS ( SELECT value FROM json_each(?) WHERE value NOT IN ( SELECT value FROM json_each(json_extract(metadata, ${jsonPath})) ) ) ELSE FALSE END )`; } return { sql, needsValue: true, transformValue: () => { if (arrayValue.length === 0) { return []; } return [JSON.stringify(arrayValue)]; } }; }, $elemMatch: (key, value) => { const jsonPath = getJsonPath(key); if (typeof value !== "object" || Array.isArray(value)) { throw new Error("$elemMatch requires an object with conditions"); } const conditions = buildElemMatchConditions(value); return { sql: `( CASE WHEN ${validateJsonArray(key)} THEN EXISTS ( SELECT 1 FROM json_each(json_extract(metadata, ${jsonPath})) as elem WHERE ${conditions.map((c) => c.sql).join(" AND ")} ) ELSE FALSE END )`, needsValue: true, transformValue: () => conditions.flatMap((c) => c.values) }; }, // Element Operators $exists: (key, value) => { const jsonPath = getJsonPath(key); return { sql: value === false ? `json_extract(metadata, ${jsonPath}) IS NULL` : `json_extract(metadata, ${jsonPath}) IS NOT NULL`, needsValue: false }; }, // Logical Operators $and: (key) => ({ sql: `(${key})`, needsValue: false }), $or: (key) => ({ sql: `(${key})`, needsValue: false }), $not: (key) => ({ sql: `NOT (${key})`, needsValue: false }), $nor: (key) => ({ sql: `NOT (${key})`, needsValue: false }), $size: (key, paramIndex) => { const jsonPath = getJsonPath(key); return { sql: `( CASE WHEN json_type(json_extract(metadata, ${jsonPath})) = 'array' THEN json_array_length(json_extract(metadata, ${jsonPath})) = $${paramIndex} ELSE FALSE END )`, needsValue: true }; }, // /** // * Regex Operators // * Supports case insensitive and multiline // */ // $regex: (key: string): FilterOperator => ({ // sql: `json_extract(metadata, '$."${toJsonPathKey(key)}"') = ?`, // needsValue: true, // transformValue: (value: any) => { // const pattern = typeof value === 'object' ? value.$regex : value; // const options = typeof value === 'object' ? value.$options || '' : ''; // let sql = `json_extract(metadata, '$."${toJsonPathKey(key)}"')`; // // Handle multiline // // if (options.includes('m')) { // // sql = `REPLACE(${sql}, CHAR(10), '\n')`; // // } // // let finalPattern = pattern; // // if (options) { // // finalPattern = `(\\?${options})${pattern}`; // // } // // // Handle case insensitivity // // if (options.includes('i')) { // // sql = `LOWER(${sql}) REGEXP LOWER(?)`; // // } else { // // sql = `${sql} REGEXP ?`; // // } // if (options.includes('m')) { // sql = `EXISTS ( // SELECT 1 // FROM json_each( // json_array( // ${sql}, // REPLACE(${sql}, CHAR(10), CHAR(13)) // ) // ) as lines // WHERE lines.value REGEXP ? // )`; // } else { // sql = `${sql} REGEXP ?`; // } // // Handle case insensitivity // if (options.includes('i')) { // sql = sql.replace('REGEXP ?', 'REGEXP LOWER(?)'); // sql = sql.replace('value REGEXP', 'LOWER(value) REGEXP'); // } // // Handle extended - allows whitespace and comments in pattern // if (options.includes('x')) { // // Remove whitespace and comments from pattern // const cleanPattern = pattern.replace(/\s+|#.*$/gm, ''); // return { // sql, // values: [cleanPattern], // }; // } // return { // sql, // values: [pattern], // }; // }, // }), $contains: (key, value) => { const jsonPathKey = parseJsonPathKey(key); let sql; if (Array.isArray(value)) { sql = `( SELECT ${validateJsonArray(jsonPathKey)} AND EXISTS ( SELECT 1 FROM json_each(json_extract(metadata, '$."${jsonPathKey}"')) as m WHERE m.value IN (SELECT value FROM json_each(?)) ) )`; } else if (typeof value === "string") { sql = `lower(json_extract(metadata, '$."${jsonPathKey}"')) LIKE '%' || lower(?) || '%' ESCAPE '\\'`; } else { sql = `json_extract(metadata, '$."${jsonPathKey}"') = ?`; } return { sql, needsValue: true, transformValue: () => { if (Array.isArray(value)) { return [JSON.stringify(value)]; } if (typeof value === "object" && value !== null) { return [JSON.stringify(value)]; } if (typeof value === "string") { return [escapeLikePattern(value)]; } return [value]; } }; } /** * $objectContains: True JSON containment for advanced use (deep sub-object match). * Usage: { field: { $objectContains: { ...subobject } } } */ // $objectContains: (key: string) => ({ // sql: '', // Will be overridden by transformValue // needsValue: true, // transformValue: (value: any) => ({ // sql: `json_type(json_extract(metadata, '$."${toJsonPathKey(key)}"')) = 'object' // AND json_patch(json_extract(metadata, '$."${toJsonPathKey(key)}"'), ?) = json_extract(metadata, '$."${toJsonPathKey(key)}"')`, // values: [JSON.stringify(value)], // }), // }), }; function isFilterResult(obj) { return obj && typeof obj === "object" && typeof obj.sql === "string" && Array.isArray(obj.values); } var parseJsonPathKey = (key) => { const parsedKey = utils.parseFieldKey(key); if (parsedKey.includes(".")) { return parsedKey.split(".").map((segment) => `"${segment}"`).join("."); } return parsedKey; }; var getJsonPath = (key) => { const jsonPathKey = parseJsonPathKey(key); return `'$.${jsonPathKey}'`; }; function escapeLikePattern(str) { return str.replace(/([%_\\])/g, "\\$1"); } function buildFilterQuery(filter) { if (!filter) { return { sql: "", values: [] }; } const values = []; const conditions = Object.entries(filter).map(([key, value]) => { const condition = buildCondition(key, value); values.push(...condition.values); return condition.sql; }).join(" AND "); return { sql: conditions ? `WHERE ${conditions}` : "", values }; } function buildCondition(key, value, parentPath) { if (["$and", "$or", "$not", "$nor"].includes(key)) { return handleLogicalOperator(key, value); } if (!value || typeof value !== "object") { const jsonPath = getJsonPath(key); return { sql: `json_extract(metadata, ${jsonPath}) = ?`, values: [value] }; } return handleOperator(key, value); } function handleLogicalOperator(key, value, parentPath) { if (!value || Array.isArray(value) && value.length === 0) { switch (key) { case "$and": case "$nor": return { sql: "true", values: [] }; case "$or": return { sql: "false", values: [] }; case "$not": throw new Error("$not operator cannot be empty"); default: return { sql: "true", values: [] }; } } if (key === "$not") { const entries = Object.entries(value); const conditions2 = entries.map(([fieldKey, fieldValue]) => buildCondition(fieldKey, fieldValue)); return { sql: `NOT (${conditions2.map((c) => c.sql).join(" AND ")})`, values: conditions2.flatMap((c) => c.values) }; } const values = []; const joinOperator = key === "$or" || key === "$nor" ? "OR" : "AND"; const conditions = Array.isArray(value) ? value.map((f) => { const entries = !!f ? Object.entries(f) : []; return entries.map(([k, v]) => buildCondition(k, v)); }) : [buildCondition(key, value)]; const joined = conditions.flat().map((c) => { values.push(...c.values); return c.sql; }).join(` ${joinOperator} `); return { sql: key === "$nor" ? `NOT (${joined})` : `(${joined})`, values }; } function handleOperator(key, value) { if (typeof value === "object" && !Array.isArray(value)) { const entries = Object.entries(value); const results = entries.map( ([operator2, operatorValue2]) => operator2 === "$not" ? { sql: `NOT (${Object.entries(operatorValue2).map(([op, val]) => processOperator(key, op, val).sql).join(" AND ")})`, values: Object.entries(operatorValue2).flatMap( ([op, val]) => processOperator(key, op, val).values ) } : processOperator(key, operator2, operatorValue2) ); return { sql: `(${results.map((r) => r.sql).join(" AND ")})`, values: results.flatMap((r) => r.values) }; } const [[operator, operatorValue] = []] = Object.entries(value); return processOperator(key, operator, operatorValue); } var processOperator = (key, operator, operatorValue) => { if (!operator.startsWith("$") || !FILTER_OPERATORS[operator]) { throw new Error(`Invalid operator: ${operator}`); } const operatorFn = FILTER_OPERATORS[operator]; const operatorResult = operatorFn(key, operatorValue); if (!operatorResult.needsValue) { return { sql: operatorResult.sql, values: [] }; } const transformed = operatorResult.transformValue ? operatorResult.transformValue() : operatorValue; if (isFilterResult(transformed)) { return transformed; } return { sql: operatorResult.sql, values: Array.isArray(transformed) ? transformed : [transformed] }; }; // src/vector/index.ts var LibSQLVector = class extends vector.MastraVector { turso; maxRetries; initialBackoffMs; overFetchMultiplier; isMemoryDb; vectorIndexes; constructor({ url, authToken, syncUrl, syncInterval, maxRetries = 5, initialBackoffMs = 100, vectorTopKOverFetchMultiplier = 10, id }) { super({ id }); this.turso = client.createClient({ url, syncUrl, authToken, syncInterval }); this.maxRetries = maxRetries; this.initialBackoffMs = initialBackoffMs; if (!Number.isInteger(vectorTopKOverFetchMultiplier) || vectorTopKOverFetchMultiplier < 1) { throw new Error("vectorTopKOverFetchMultiplier must be a positive integer"); } this.overFetchMultiplier = vectorTopKOverFetchMultiplier; this.isMemoryDb = url.includes(":memory:"); if (url.includes(`file:`) || this.isMemoryDb) { 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)); 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)); } this.vectorIndexes = this.isMemoryDb ? Promise.resolve(/* @__PURE__ */ new Set()) : this.discoverVectorIndexes(); } async discoverVectorIndexes() { try { const result = await this.turso.execute({ sql: `SELECT name FROM sqlite_master WHERE type='index' AND name LIKE '%_vector_idx'`, args: [] }); return new Set(result.rows.map((row) => row.name)); } catch { return /* @__PURE__ */ new Set(); } } async executeWriteOperationWithRetry(operation, isTransaction = false) { let attempts = 0; let backoff = this.initialBackoffMs; while (attempts < this.maxRetries) { try { return await operation(); } catch (error) { 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")) { attempts++; if (attempts >= this.maxRetries) { this.logger.error( `LibSQLVector: Operation failed after ${this.maxRetries} attempts due to: ${error.message}`, error ); throw error; } this.logger.warn( `LibSQLVector: Attempt ${attempts} failed due to ${isTransaction ? "transaction " : ""}database lock. Retrying in ${backoff}ms...` ); await new Promise((resolve) => setTimeout(resolve, backoff)); backoff *= 2; } else { throw error; } } } throw new Error("LibSQLVector: Max retries reached, but no error was re-thrown from the loop."); } transformFilter(filter) { const translator = new LibSQLFilterTranslator(); return translator.translate(filter); } async hasVectorIndex(parsedIndexName) { const indexes = await this.vectorIndexes; return indexes.has(`${parsedIndexName}_vector_idx`); } async queryWithIndex(parsedIndexName, vectorStr, topK, filter, includeVector, minScore) { const translatedFilter = this.transformFilter(filter); const { sql: filterQuery, values: filterValues } = buildFilterQuery(translatedFilter); const hasFilter = filterQuery.length > 0; const fetchCount = hasFilter ? topK * this.overFetchMultiplier : topK * 2; const embeddingSelect = includeVector ? ", vector_extract(t.embedding) as embedding" : ""; const filterCondition = hasFilter ? filterQuery.replace(/^\s*WHERE\s+/i, "") : ""; const whereClause = hasFilter ? `WHERE ${filterCondition} AND score > ?` : "WHERE score > ?"; const query = ` WITH candidates AS ( SELECT t.vector_id AS id, (1 - vector_distance_cos(t.embedding, vector32(?))) AS score, t.metadata ${embeddingSelect} FROM vector_top_k('${parsedIndexName}_vector_idx', vector32(?), ?) AS v JOIN "${parsedIndexName}" AS t ON t.rowid = v.id ) SELECT * FROM candidates ${whereClause} ORDER BY score DESC LIMIT ?`; const args = [vectorStr, vectorStr, fetchCount, ...filterValues, minScore, topK]; const result = await this.turso.execute({ sql: query, args }); return result.rows.map(({ id, score, metadata, embedding }) => ({ id, score, metadata: JSON.parse(metadata ?? "{}"), ...includeVector && embedding && { vector: JSON.parse(embedding) } })); } async query({ indexName, queryVector, topK = 10, filter, includeVector = false, minScore = -1 // Default to -1 to include all results (cosine similarity ranges from -1 to 1) }) { vector.validateTopK("LIBSQL", topK); if (!queryVector) { throw new error.MastraError({ id: storage.createVectorErrorId("LIBSQL", "QUERY", "MISSING_VECTOR"), text: "queryVector is required for LibSQL queries. Metadata-only queries are not supported by this vector store.", domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { indexName } }); } if (!Array.isArray(queryVector) || !queryVector.every((x) => typeof x === "number" && Number.isFinite(x))) { throw new error.MastraError({ id: storage.createVectorErrorId("LIBSQL", "QUERY", "INVALID_ARGS"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { message: "queryVector must be an array of finite numbers" } }); } try { const parsedIndexName = utils.parseSqlIdentifier(indexName, "index name"); const vectorStr = `[${queryVector.join(",")}]`; if (!this.isMemoryDb && await this.hasVectorIndex(parsedIndexName)) { try { const indexedResults = await this.queryWithIndex( parsedIndexName, vectorStr, topK, filter, includeVector, minScore ); if (!filter || indexedResults.length >= topK) { return indexedResults; } } catch (err) { this.logger.warn("LibSQLVector: indexed query failed, falling back to brute-force", err); } } const translatedFilter = this.transformFilter(filter); const { sql: filterQuery, values: filterValues } = buildFilterQuery(translatedFilter); filterValues.push(minScore); filterValues.push(topK); const query = ` WITH vector_scores AS ( SELECT vector_id as id, (1-vector_distance_cos(embedding, '${vectorStr}')) as score, metadata ${includeVector ? ", vector_extract(embedding) as embedding" : ""} FROM ${parsedIndexName} ${filterQuery} ) SELECT * FROM vector_scores WHERE score > ? ORDER BY score DESC LIMIT ?`; const result = await this.turso.execute({ sql: query, args: filterValues }); return result.rows.map(({ id, score, metadata, embedding }) => ({ id, score, metadata: JSON.parse(metadata ?? "{}"), ...includeVector && embedding && { vector: JSON.parse(embedding) } })); } catch (error$1) { throw new error.MastraError( { id: storage.createVectorErrorId("LIBSQL", "QUERY", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } upsert(args) { try { return this.executeWriteOperationWithRetry(() => this.doUpsert(args), true); } catch (error$1) { throw new error.MastraError( { id: storage.createVectorErrorId("LIBSQL", "UPSERT", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async doUpsert({ indexName, vectors, metadata, ids }) { vector.validateUpsertInput("LIBSQL", vectors, metadata, ids); const tx = await this.turso.transaction("write"); try { const parsedIndexName = utils.parseSqlIdentifier(indexName, "index name"); const vectorIds = ids || vectors.map(() => crypto.randomUUID()); for (let i = 0; i < vectors.length; i++) { const query = ` INSERT INTO ${parsedIndexName} (vector_id, embedding, metadata) VALUES (?, vector32(?), ?) ON CONFLICT(vector_id) DO UPDATE SET embedding = vector32(?), metadata = ? `; await tx.execute({ sql: query, args: [ vectorIds[i], JSON.stringify(vectors[i]), JSON.stringify(metadata?.[i] || {}), JSON.stringify(vectors[i]), JSON.stringify(metadata?.[i] || {}) ] }); } await tx.commit(); return vectorIds; } catch (error) { !tx.closed && await tx.rollback(); if (error instanceof Error && error.message?.includes("dimensions are different")) { const match = error.message.match(/dimensions are different: (\d+) != (\d+)/); if (match) { const [, actual, expected] = match; throw new Error( `Vector dimension mismatch: Index "${indexName}" expects ${expected} dimensions but got ${actual} dimensions. Either use a matching embedding model or delete and recreate the index with the new dimension.` ); } } throw error; } } createIndex(args) { try { return this.executeWriteOperationWithRetry(() => this.doCreateIndex(args)); } catch (error$1) { throw new error.MastraError( { id: storage.createVectorErrorId("LIBSQL", "CREATE_INDEX", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { indexName: args.indexName, dimension: args.dimension } }, error$1 ); } } async doCreateIndex({ indexName, dimension }) { if (!Number.isInteger(dimension) || dimension <= 0) { throw new Error("Dimension must be a positive integer"); } const parsedIndexName = utils.parseSqlIdentifier(indexName, "index name"); await this.turso.execute({ sql: ` CREATE TABLE IF NOT EXISTS ${parsedIndexName} ( id SERIAL PRIMARY KEY, vector_id TEXT UNIQUE NOT NULL, embedding F32_BLOB(${dimension}), metadata TEXT DEFAULT '{}' ); `, args: [] }); await this.turso.execute({ sql: ` CREATE INDEX IF NOT EXISTS ${parsedIndexName}_vector_idx ON ${parsedIndexName} (libsql_vector_idx(embedding)) `, args: [] }); void this.vectorIndexes.then((indexes) => indexes.add(`${parsedIndexName}_vector_idx`)); } deleteIndex(args) { try { return this.executeWriteOperationWithRetry(() => this.doDeleteIndex(args)); } catch (error$1) { throw new error.MastraError( { id: storage.createVectorErrorId("LIBSQL", "DELETE_INDEX", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { indexName: args.indexName } }, error$1 ); } } async doDeleteIndex({ indexName }) { const parsedIndexName = utils.parseSqlIdentifier(indexName, "index name"); await this.turso.execute({ sql: `DROP TABLE IF EXISTS ${parsedIndexName}`, args: [] }); void this.vectorIndexes.then((indexes) => indexes.delete(`${parsedIndexName}_vector_idx`)); } async listIndexes() { try { const vectorTablesQuery = ` SELECT name FROM sqlite_master WHERE type='table' AND sql LIKE '%F32_BLOB%'; `; const result = await this.turso.execute({ sql: vectorTablesQuery, args: [] }); return result.rows.map((row) => row.name); } catch (error$1) { throw new error.MastraError( { id: storage.createVectorErrorId("LIBSQL", "LIST_INDEXES", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } /** * Retrieves statistics about a vector index. * * @param {string} indexName - The name of the index to describe * @returns A promise that resolves to the index statistics including dimension, count and metric */ async describeIndex({ indexName }) { try { const parsedIndexName = utils.parseSqlIdentifier(indexName, "index name"); const tableInfoQuery = ` SELECT sql FROM sqlite_master WHERE type='table' AND name = ?; `; const tableInfo = await this.turso.execute({ sql: tableInfoQuery, args: [parsedIndexName] }); if (!tableInfo.rows[0]?.sql) { throw new Error(`Table ${parsedIndexName} not found`); } const dimension = parseInt(tableInfo.rows[0].sql.match(/F32_BLOB\((\d+)\)/)?.[1] || "0"); const countQuery = ` SELECT COUNT(*) as count FROM ${parsedIndexName}; `; const countResult = await this.turso.execute({ sql: countQuery, args: [] }); const metric = "cosine"; return { dimension, count: countResult?.rows?.[0]?.count ?? 0, metric }; } catch (e) { throw new error.MastraError( { id: storage.createVectorErrorId("LIBSQL", "DESCRIBE_INDEX", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { indexName } }, e ); } } /** * Updates a vector by its ID with the provided vector and/or metadata. * * @param indexName - The name of the index containing the vector. * @param id - The ID of the vector to update. * @param update - An object containing the vector and/or metadata to update. * @param update.vector - An optional array of numbers representing the new vector. * @param update.metadata - An optional record containing the new metadata. * @returns A promise that resolves when the update is complete. * @throws Will throw an error if no updates are provided or if the update operation fails. */ updateVector(args) { return this.executeWriteOperationWithRetry(() => this.doUpdateVector(args)); } async doUpdateVector(params) { const { indexName, update } = params; const parsedIndexName = utils.parseSqlIdentifier(indexName, "index name"); if ("id" in params && params.id && "filter" in params && params.filter) { throw new error.MastraError({ id: storage.createVectorErrorId("LIBSQL", "UPDATE_VECTOR", "MUTUALLY_EXCLUSIVE"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { indexName }, text: "id and filter are mutually exclusive - provide only one" }); } if (!update.vector && !update.metadata) { throw new error.MastraError({ id: storage.createVectorErrorId("LIBSQL", "UPDATE_VECTOR", "NO_PAYLOAD"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { indexName }, text: "No updates provided" }); } const updates = []; const args = []; if (update.vector) { updates.push("embedding = vector32(?)"); args.push(JSON.stringify(update.vector)); } if (update.metadata) { updates.push("metadata = ?"); args.push(JSON.stringify(update.metadata)); } if (updates.length === 0) { return; } let whereClause; let whereValues; if ("id" in params && params.id) { whereClause = "vector_id = ?"; whereValues = [params.id]; } else if ("filter" in params && params.filter) { const filter = params.filter; if (!filter || Object.keys(filter).length === 0) { throw new error.MastraError({ id: storage.createVectorErrorId("LIBSQL", "UPDATE_VECTOR", "EMPTY_FILTER"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { indexName }, text: "Cannot update with empty filter" }); } const translatedFilter = this.transformFilter(filter); const { sql: filterSql, values: filterValues } = buildFilterQuery(translatedFilter); if (!filterSql || filterSql.trim() === "") { throw new error.MastraError({ id: storage.createVectorErrorId("LIBSQL", "UPDATE_VECTOR", "INVALID_FILTER"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { indexName }, text: "Filter produced empty WHERE clause" }); } const normalizedCondition = filterSql.replace(/^\s*WHERE\s+/i, "").trim().toLowerCase(); const matchAllPatterns = ["true", "1 = 1", "1=1"]; if (matchAllPatterns.includes(normalizedCondition)) { throw new error.MastraError({ id: storage.createVectorErrorId("LIBSQL", "UPDATE_VECTOR", "MATCH_ALL_FILTER"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { indexName, filterSql: normalizedCondition }, text: "Filter matches all vectors. Provide a specific filter to update targeted vectors." }); } whereClause = filterSql.replace(/^WHERE\s+/i, ""); whereValues = filterValues; } else { throw new error.MastraError({ id: storage.createVectorErrorId("LIBSQL", "UPDATE_VECTOR", "NO_TARGET"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { indexName }, text: "Either id or filter must be provided" }); } const query = ` UPDATE ${parsedIndexName} SET ${updates.join(", ")} WHERE ${whereClause}; `; try { await this.turso.execute({ sql: query, args: [...args, ...whereValues] }); } catch (error$1) { const errorDetails = { indexName }; if ("id" in params && params.id) { errorDetails.id = params.id; } if ("filter" in params && params.filter) { errorDetails.filter = JSON.stringify(params.filter); } throw new error.MastraError( { id: storage.createVectorErrorId("LIBSQL", "UPDATE_VECTOR", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: errorDetails }, error$1 ); } } /** * Deletes a vector by its ID. * @param indexName - The name of the index containing the vector. * @param id - The ID of the vector to delete. * @returns A promise that resolves when the deletion is complete. * @throws Will throw an error if the deletion operation fails. */ deleteVector(args) { try { return this.executeWriteOperationWithRetry(() => this.doDeleteVector(args)); } catch (error$1) { throw new error.MastraError( { id: storage.createVectorErrorId("LIBSQL", "DELETE_VECTOR", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { indexName: args.indexName, ...args.id && { id: args.id } } }, error$1 ); } } async doDeleteVector({ indexName, id }) { const parsedIndexName = utils.parseSqlIdentifier(indexName, "index name"); await this.turso.execute({ sql: `DELETE FROM ${parsedIndexName} WHERE vector_id = ?`, args: [id] }); } deleteVectors(args) { return this.executeWriteOperationWithRetry(() => this.doDeleteVectors(args)); } async doDeleteVectors({ indexName, filter, ids }) { const parsedIndexName = utils.parseSqlIdentifier(indexName, "index name"); if (!filter && !ids) { throw new error.MastraError({ id: storage.createVectorErrorId("LIBSQL", "DELETE_VECTORS", "NO_TARGET"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { indexName }, text: "Either filter or ids must be provided" }); } if (filter && ids) { throw new error.MastraError({ id: storage.createVectorErrorId("LIBSQL", "DELETE_VECTORS", "MUTUALLY_EXCLUSIVE"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { indexName }, text: "Cannot provide both filter and ids - they are mutually exclusive" }); } let query; let values; if (ids) { if (ids.length === 0) { throw new error.MastraError({ id: storage.createVectorErrorId("LIBSQL", "DELETE_VECTORS", "EMPTY_IDS"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { indexName }, text: "Cannot delete with empty ids array" }); } const placeholders = ids.map(() => "?").join(", "); query = `DELETE FROM ${parsedIndexName} WHERE vector_id IN (${placeholders})`; values = ids; } else { if (!filter || Object.keys(filter).length === 0) { throw new error.MastraError({ id: storage.createVectorErrorId("LIBSQL", "DELETE_VECTORS", "EMPTY_FILTER"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { indexName }, text: "Cannot delete with empty filter. Use deleteIndex to delete all vectors." }); } const translatedFilter = this.transformFilter(filter); const { sql: filterSql, values: filterValues } = buildFilterQuery(translatedFilter); if (!filterSql || filterSql.trim() === "") { throw new error.MastraError({ id: storage.createVectorErrorId("LIBSQL", "DELETE_VECTORS", "INVALID_FILTER"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { indexName }, text: "Filter produced empty WHERE clause" }); } const normalizedCondition = filterSql.replace(/^\s*WHERE\s+/i, "").trim().toLowerCase(); const matchAllPatterns = ["true", "1 = 1", "1=1"]; if (matchAllPatterns.includes(normalizedCondition)) { throw new error.MastraError({ id: storage.createVectorErrorId("LIBSQL", "DELETE_VECTORS", "MATCH_ALL_FILTER"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { indexName, filterSql: normalizedCondition }, text: "Filter matches all vectors. Use deleteIndex to delete all vectors from an index." }); } query = `DELETE FROM ${parsedIndexName} ${filterSql}`; values = filterValues; } try { await this.turso.execute({ sql: query, args: values }); } catch (error$1) { throw new error.MastraError( { id: storage.createVectorErrorId("LIBSQL", "DELETE_VECTORS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { indexName, ...filter && { filter: JSON.stringify(filter) }, ...ids && { idsCount: ids.length } } }, error$1 ); } } truncateIndex(args) { try { return this.executeWriteOperationWithRetry(() => this._doTruncateIndex(args)); } catch (error$1) { throw new error.MastraError( { id: storage.createVectorErrorId("LIBSQL", "TRUNCATE_INDEX", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { indexName: args.indexName } }, error$1 ); } } async _doTruncateIndex({ indexName }) { await this.turso.execute({ sql: `DELETE FROM ${utils.parseSqlIdentifier(indexName, "index name")}`, args: [] }); } }; var safeStringify = (value) => { const ancestors = /* @__PURE__ */ new Set(); const sanitize = (val) => { if (val === null || val === void 0) return val; if (typeof val === "function") return void 0; if (typeof val === "symbol") return void 0; if (typeof val === "bigint") return val.toString(); if (typeof val !== "object") return val; if (ancestors.has(val)) return void 0; if (typeof val.toJSON === "function") { return sanitize(val.toJSON()); } ancestors.add(val); try { if (Array.isArray(val)) { return val.map((item) => sanitize(item)); } const result = {}; for (const key of Object.keys(val)) { const sanitized = sanitize(val[key]); if (sanitized !== void 0) { result[key] = sanitized; } } return result; } finally { ancestors.delete(val); } }; return JSON.stringify(sanitize(value)) ?? "null"; }; function buildSelectColumns(tableName) { const schema = storage.TABLE_SCHEMAS[tableName]; return Object.keys(schema).map((col) => { const colDef = schema[col]; const parsedCol = utils.parseSqlIdentifier(col, "column name"); return colDef?.type === "jsonb" ? `json("${parsedCol}") as "${parsedCol}"` : `"${parsedCol}"`; }).join(", "); } function buildSelectColumnsWithAlias(tableName, alias) { const parsedAlias = utils.parseSqlIdentifier(alias, "table alias"); const schema = storage.TABLE_SCHEMAS[tableName]; return Object.keys(schema).map((col) => { const colDef = schema[col]; const parsedCol = utils.parseSqlIdentifier(col, "column name"); return colDef?.type === "jsonb" ? `json(${parsedAlias}."${parsedCol}") as "${parsedCol}"` : `${parsedAlias}."${parsedCol}"`; }).join(", "); } function isLockError(error) { 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"); } function createExecuteWriteOperationWithRetry({ logger, maxRetries, initialBackoffMs }) { return async function executeWriteOperationWithRetry(operationFn, operationDescription) { let attempts = 0; let backoff = initialBackoffMs; while (attempts < maxRetries) { try { return await operationFn(); } catch (error) { logger.debug(`LibSQLStore: Error caught in retry loop for ${operationDescription}`, { errorType: error.constructor.name, errorCode: error.code, errorMessage: error.message, attempts, maxRetries }); if (isLockError(error)) { attempts++; if (attempts >= maxRetries) { logger.error( `LibSQLStore: Operation failed after ${maxRetries} attempts due to database lock: ${error.message}`, { error, attempts, maxRetries } ); throw error; } logger.warn( `LibSQLStore: Attempt ${attempts} failed due to database lock during ${operationDescription}. Retrying in ${backoff}ms...`, { errorMessage: error.message, attempts, backoff, maxRetries } ); await new Promise((resolve) => setTimeout(resolve, backoff)); backoff *= 2; } else { logger.error(`LibSQLStore: Non-lock error during ${operationDescription}, not retrying`, { error }); throw error; } } } throw new Error(`LibSQLStore: Unexpected exit from retry loop for ${operationDescription}`); }; } function prepareStatement({ tableName, record }) { const parsedTableName = utils.parseSqlIdentifier(tableName, "table name"); const schema = storage.TABLE_SCHEMAS[tableName]; const columnNames = Object.keys(record); const columns = columnNames.map((col) => utils.parseSqlIdentifier(col, "column name")); const values = columnNames.map((col) => { const v = record[col]; if (typeof v === `undefined` || v === null) { return null; } const colDef = schema[col]; if (colDef?.type === "jsonb") { return safeStringify(v); } if (v instanceof Date) { return v.toISOString(); } return typeof v === "object" ? safeStringify(v) : v; }); const placeholders = columnNames.map((col) => { const colDef = schema[col]; return colDef?.type === "jsonb" ? "jsonb(?)" : "?"; }).join(", "); return { sql: `INSERT OR REPLACE INTO ${parsedTableName} (${columns.join(", ")}) VALUES (${placeholders})`, args: values }; } function prepareUpdateStatement({ tableName, updates, keys }) { const parsedTableName = utils.parseSqlIdentifier(tableName, "table name"); const schema = storage.TABLE_SCHEMAS[tableName]; const updateColumnNames = Object.keys(updates); const updateColumns = updateColumnNames.map((col) => utils.parseSqlIdentifier(col, "column name")); const updateValues = updateColumnNames.map((col) => { const colDef = schema[col]; const v = updates[col]; if (colDef?.type === "jsonb") { return transformToSqlValue(v, true); } return transformToSqlValue(v, false); }); const setClause = updateColumns.map((col, i) => { const colDef = schema[updateColumnNames[i]]; return colDef?.type === "jsonb" ? `${col} = jsonb(?)` : `${col} = ?`; }).join(", "); const whereClause = prepareWhereClause(keys, schema); return { sql: `UPDATE ${parsedTableName} SET ${setClause}${whereClause.sql}`, args: [...updateValues, ...whereClause.args] }; } function transformToSqlValue(value, forceJsonStringify = false) { if (typeof value === "undefined" || value === null) { return null; } if (forceJsonStringify) { return safeStringify(value); } if (value instanceof Date) { return value.toISOString(); } return typeof value === "object" ? safeStringify(value) : value; } function prepareDeleteStatement({ tableName, keys }) { const parsedTableName = utils.parseSqlIdentifier(tableName, "table name"); const whereClause = prepareWhereClause(keys, storage.TABLE_SCHEMAS[tableName]); return { sql: `DELETE FROM ${parsedTableName}${whereClause.sql}`, args: whereClause.args }; } function prepareWhereClause(filters, schema) { const conditions = []; const args = []; for (const [columnName, filterValue] of Object.entries(filters)) { const column = schema[columnName]; if (!column) { throw new Error(`Unknown column: ${columnName}`); } const parsedColumn = utils.parseSqlIdentifier(columnName, "column name"); const result = buildCondition2(parsedColumn, filterValue); conditions.push(result.condition); args.push(...result.args); } return { sql: conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "", args }; } function buildCondition2(columnName, filterValue) { if (filterValue === null) { return { condition: `${columnName} IS NULL`, args: [] }; } if (typeof filterValue === "object" && filterValue !== null && ("startAt" in filterValue || "endAt" in filterValue)) { return buildDateRangeCondition(columnName, filterValue); } return { condition: `${columnName} = ?`, args: [transformToSqlValue(filterValue)] }; } function buildDateRangeCondition(columnName, range) { const conditions = []; const args = []; if (range.startAt !== void 0) { conditions.push(`${columnName} >= ?`); args.push(transformToSqlValue(range.startAt)); } if (range.endAt !== void 0) { conditions.push(`${columnName} <= ?`); args.push(transformToSqlValue(range.endAt)); } if (conditions.length === 0) { throw new Error("Date range must specify at least startAt or endAt"); } return { condition: conditions.join(" AND "), args }; } function transformFromSqlRow({ tableName, sqlRow }) { const result = {}; const jsonColumns = new Set( Object.keys(storage.TABLE_SCHEMAS[tableName]).filter((key) => storage.TABLE_SCHEMAS[tableName][key].type === "jsonb").map((key) => key) ); const dateColumns = new Set( Object.keys(storage.TABLE_SCHEMAS[tableName]).filter((key) => storage.TABLE_SCHEMAS[tableName][key].type === "timestamp").map((key) => key) ); const booleanColumns = new Set( Object.keys(storage.TABLE_SCHEMAS[tableName]).filter((key) => storage.TABLE_SCHEMAS[tableName][key].type === "boolean").map((key) => key) ); for (const [key, value] of Object.entries(sqlRow)) { if (value === null || value === void 0) { result[key] = value; continue; } if (dateColumns.has(key) && typeof value === "string") { result[key] = new Date(value); continue; } if (jsonColumns.has(key) && typeof value === "string") { result[key] = storage.safelyParseJSON(value); continue; } if (booleanColumns.has(key)) { result[key] = Boolean(value); continue; } result[key] = value; } return result; } // src/storage/db/write-lock.ts var clientWriteChains = /* @__PURE__ */ new WeakMap(); function withClientWriteLock(client, fn) { const previous = clientWriteChains.get(client) ?? Promise.resolve(); const result = previous.then(fn, fn); clientWriteChains.set( client, result.then( () => void 0, () => void 0 ) ); return result; } // src/storage/db/index.ts var DEFAULT_CONNECTION_TIMEOUT_MS = 5e3; function resolveClient(config) { if ("client" in config) { return config.client; } const isLocal = config.url.startsWith("file:") || config.url.includes(":memory:"); const timeout = config.connectionTimeoutMs ?? DEFAULT_CONNECTION_TIMEOUT_MS; return client.createClient({ url: config.url, ...config.authToken ? { authToken: config.authToken } : {}, // Only local sqlite3 connections honor `busy_timeout`; remote contention is // resolved server-side, so passing it there is meaningless. ...isLocal ? { timeout } : {} }); } var LibSQLDB = class extends base.MastraBase { client; maxRetries; initialBackoffMs; executeWriteOperationWithRetry; /** Cache of actual table columns: tableName -> Promise> (stores in-flight promise to coalesce concurrent calls) */ tableColumnsCache = /* @__PURE__ */ new Map(); constructor({ client, maxRetries, initialBackoffMs }) { super({ component: "STORAGE", name: "LIBSQL_DB_LAYER" }); this.client = client; this.maxRetries = maxRetries ?? 5; this.initialBackoffMs = initialBackoffMs ?? 100; this.executeWriteOperationWithRetry = createExecuteWriteOperationWithRetry({ logger: this.logger, maxRetries: this.maxRetries, initialBackoffMs: this.initialBackoffMs }); } /** * Gets the set of column names that actually exist in the database table. * Results are cached; the cache is invalidated when alterTable() adds new columns. */ async getTableColumns(tableName) { const cached = this.tableColumnsCache.get(tableName); if (cached) return cached; const promise = (async () => { try { const sanitizedTable = utils.parseSqlIdentifier(tableName, "table name"); const result = await this.client.execute({ sql: `PRAGMA table_info("${sanitizedTable}")` }); const columns = new Set((result.rows || []).map((row) => row.name)); if (columns.size === 0) { this.tableColumnsCache.delete(tableName); } return columns; } catch (error) { this.tableColumnsCache.delete(tableName); throw error; } })(); this.tableColumnsCache.set(tableName, promise); return promise; } /** * Filters a record to only include columns that exist in the actual database table. * Unknown columns are silently dropped to ensure forward compatibility. */ async filterRecordToKnownColumns(tableName, record) { const knownColumns = await this.getTableColumns(tableName); if (knownColumns.size === 0) return record; const filtered = {}; for (const [key, value] of Object.entries(record)) { if (knownColumns.has(key)) { filtered[key] = value; } } return filtered; } /** * Checks if a column exists in the specified table. * * @param table - The name of the table to check * @param column - The name of the column to look for * @returns `true` if the column exists in the table, `false` otherwise */ async hasColumn(table, column) { const sanitizedTable = utils.parseSqlIdentifier(table, "table name"); const result = await this.client.execute({ sql: `PRAGMA table_info("${sanitizedTable}")` }); return result.rows?.some((row) => row.name === column); } /** * Internal insert implementation without retry logic. */ async doInsert({ tableName, record }) { const filteredRecord = await this.filterRecordToKnownColumns(tableName, record); if (Object.keys(filteredRecord).length === 0) return; await withClientWriteLock( this.client, () => this.client.execute( prepareStatement({ tableName, record: filteredRecord }) ) ); } /** * Inserts or replaces a record in the specified table with automatic retry on lock errors. * * @param args - The insert arguments * @param args.tableName - The name of the table to insert into * @param args.record - The record to insert (key-value pairs) */ insert(args) { return this.executeWriteOperationWithRetry(() => this.doInsert(args), `insert into table ${args.tableName}`); } /** * Internal update implementation without retry logic. */ async doUpdate({ tableName, keys, data }) { const filteredData = await this.filterRecordToKnownColumns(tableName, data); if (Object.keys(filteredData).length === 0) return; await withClientWriteLock( this.client, () => this.client.execute(prepareUpdateStatement({ tableName, updates: filteredData, keys })) ); } /** * Updates a record in the specified table with automatic retry on lock errors. * * @param args - The update arguments * @param args.tableName - The name of the table to update * @param args.keys - The key(s) identifying the record to update * @param args.data - The fields to update (key-value pairs) */ update(args) { return this.executeWriteOperationWithRetry(() => this.doUpdate(args), `update table ${args.tableName}`); } /** * Internal batch insert implementation without retry logic. */ async doBatchInsert({ tableName, records }) { if (records.length === 0) return; const filteredRecords = await Promise.all(records.map((r) => this.filterRecordToKnownColumns(tableName, r))); const nonEmptyRecords = filteredRecords.filter((r) => Object.keys(r).length > 0); if (nonEmptyRecords.length === 0) return; const batchStatements = nonEmptyRecords.map((r) => prepareStatement({ tableName, record: r })); await withClientWriteLock(this.client, () => this.client.batch(batchStatements, "write")); } /** * Inserts multiple records in a single batch transaction with automatic retry on lock errors. * * @param args - The batch insert arguments * @param args.tableName - The name of the table to insert into * @param args.records - Array of records to insert * @throws {MastraError} When the batch insert fails after retries */ async batchInsert(args) { return this.executeWriteOperationWithRetry( () => this.doBatchInsert(args), `batch insert into table ${args.tableName}` ).catch((error$1) => { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "BATCH_INSERT", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { tableName: args.tableName } }, error$1 ); }); } /** * Internal batch update implementation without retry logic. * Each record can be updated based on single or composite keys. */ async doBatchUpdate({ tableName, updates }) { if (updates.length === 0) return; const filteredUpdates = []; for (const { keys, data } of updates) { const filteredData = await this.filterRecordToKnownColumns(tableName, data); if (Object.keys(filteredData).length > 0) { filteredUpdates.push({ keys, data: filteredData }); } } if (filteredUpdates.length === 0) return; const batchStatements = filteredUpdates.map( ({ keys, data }) => prepareUpdateStatement({ tableName, updates: data, keys }) ); await withClientWriteLock(this.client, () => this.client.batch(batchStatements, "write")); } /** * Updates multiple records in a single batch transaction with automatic retry on lock errors. * Each record can be updated based on single or composite keys. * * @param args - The batch update arguments * @param args.tableName - The name of the table to update * @param args.updates - Array of update operations, each containing keys and data * @throws {MastraError} When the batch update fails after retries */ async batchUpdate(args) { return this.executeWriteOperationWithRetry( () => this.doBatchUpdate(args), `batch update in table ${args.tableName}` ).catch((error$1) => { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "BATCH_UPDATE", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { tableName: args.tableName } }, error$1 ); }); } /** * Internal batch delete implementation without retry logic. * Each record can be deleted based on single or composite keys. */ async doBatchDelete({ tableName, keys }) { if (keys.length === 0) return; const batchStatements = keys.map( (keyObj) => prepareDeleteStatement({ tableName, keys: keyObj }) ); await withClientWriteLock(this.client, () => this.client.batch(batchStatements, "write")); } /** * Deletes multiple records in a single batch transaction with automatic retry on lock errors. * Each record can be deleted based on single or composite keys. * * @param args - The batch delete arguments * @param args.tableName - The name of the table to delete from * @param args.keys - Array of key objects identifying records to delete * @throws {MastraError} When the batch delete fails after retries */ async batchDelete({ tableName, keys }) { return this.executeWriteOperationWithRetry( () => this.doBatchDelete({ tableName, keys }), `batch delete from table ${tableName}` ).catch((error$1) => { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "BATCH_DELETE", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { tableName } }, error$1 ); }); } /** * Internal single-record delete implementation without retry logic. */ async doDelete({ tableName, keys }) { await withClientWriteLock(this.client, () => this.client.execute(prepareDeleteStatement({ tableName, keys }))); } /** * Deletes a single record from the specified table with automatic retry on lock errors. * * @param args - The delete arguments * @param args.tableName - The name of the table to delete from * @param args.keys - The key(s) identifying the record to delete * @throws {MastraError} When the delete fails after retries */ async delete(args) { return this.executeWriteOperationWithRetry(() => this.doDelete(args), `delete from table ${args.tableName}`).catch( (error$1) => { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "DELETE", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { tableName: args.tableName } }, error$1 ); } ); } /** * Selects a single record from the specified table by key(s). * Returns the most recently created record if multiple matches exist. * Automatically parses JSON string values back to objects/arrays. * * @typeParam R - The expected return type of the record * @param args - The select arguments * @param args.tableName - The name of the table to select from * @param args.keys - The key(s) identifying the record to select * @returns The matching record or `null` if not found */ async select({ tableName, keys }) { const parsedTableName = utils.parseSqlIdentifier(tableName, "table name"); const columns = buildSelectColumns(tableName); const parsedKeys = Object.keys(keys).map((key) => utils.parseSqlIdentifier(key, "column name")); const conditions = parsedKeys.map((key) => `${key} = ?`).join(" AND "); const values = Object.values(keys); const result = await this.client.execute({ sql: `SELECT ${columns} FROM ${parsedTableName} WHERE ${conditions} ORDER BY createdAt DESC LIMIT 1`, args: values }); if (!result.rows || result.rows.length === 0) { return null; } const row = result.rows[0]; const parsed = Object.fromEntries( Object.entries(row || {}).map(([k, v]) => { try { return [k, typeof v === "string" ? v.startsWith("{") || v.startsWith("[") ? JSON.parse(v) : v : v]; } catch { return [k, v]; } }) ); return parsed; } /** * Selects multiple records from the specified table with optional filtering, ordering, and pagination. * * @typeParam R - The expected return type of each record * @param args - The select arguments * @param args.tableName - The name of the table to select from * @param args.whereClause - Optional WHERE clause with SQL string and arguments * @param args.orderBy - Optional ORDER BY clause (e.g., "createdAt DESC") * @param args.offset - Optional offset for pagination * @param args.limit - Optional limit for pagination * @param args.args - Optional additional query arguments * @returns Array of matching records */ async selectMany({ tableName, whereClause, orderBy, offset, limit, args }) { const parsedTableName = utils.parseSqlIdentifier(tableName, "table name"); const columns = buildSelectColumns(tableName); let statement = `SELECT ${columns} FROM ${parsedTableName}`; if (whereClause?.sql) { statement += ` ${whereClause.sql}`; } if (orderBy) { statement += ` ORDER BY ${orderBy}`; } if (limit) { statement += ` LIMIT ${limit}`; } if (offset) { statement += ` OFFSET ${offset}`; } const result = await this.client.execute({ sql: statement, args: [...whereClause?.args ?? [], ...args ?? []] }); return (result.rows ?? []).map((row) => { return Object.fromEntries( Object.entries(row || {}).map(([k, v]) => { try { return [k, typeof v === "string" ? v.startsWith("{") || v.startsWith("[") ? JSON.parse(v) : v : v]; } catch { return [k, v]; } }) ); }); } /** * Returns the total count of records matching the optional WHERE clause. * * @param args - The count arguments * @param args.tableName - The name of the table to count from * @param args.whereClause - Optional WHERE clause with SQL string and arguments * @returns The total count of matching records */ async selectTotalCount({ tableName, whereClause }) { const parsedTableName = utils.parseSqlIdentifier(tableName, "table name"); const statement = `SELECT COUNT(*) as count FROM ${parsedTableName} ${whereClause ? `${whereClause.sql}` : ""}`; const result = await this.client.execute({ sql: statement, args: whereClause?.args ?? [] }); if (!result.rows || result.rows.length === 0) { return 0; } return result.rows[0]?.count ?? 0; } /** * Maps a storage column type to its SQLite equivalent. */ getSqlType(type) { switch (type) { case "bigint": return "INTEGER"; // SQLite uses INTEGER for all integer sizes case "timestamp": return "TEXT"; // Store timestamps as ISO strings in SQLite case "float": return "REAL"; // SQLite's floating point type case "boolean": return "INTEGER"; // SQLite uses 0/1 for booleans case "jsonb": return "TEXT"; // SQLite: column stores TEXT, we use jsonb()/json() functions for binary optimization default: return storage.getSqlType(type); } } /** * Creates a table if it doesn't exist based on the provided schema. * * @param args - The create table arguments * @param args.tableName - The name of the table to create * @param args.schema - The schema definition for the table columns */ async createTable({ tableName, schema, compositePrimaryKey }) { try { const parsedTableName = utils.parseSqlIdentifier(tableName, "table name"); if (compositePrimaryKey) { for (const col of compositePrimaryKey) { if (!(col in schema)) { throw new Error(`compositePrimaryKey column "${col}" does not exist in schema for table "${tableName}"`); } } } const compositePKSet = compositePrimaryKey ? new Set(compositePrimaryKey) : null; const columnDefinitions = Object.entries(schema).map(([colName, colDef]) => { const type = this.getSqlType(colDef.type); const nullable = colDef.nullable === false ? "NOT NULL" : ""; const primaryKey = colDef.primaryKey && !compositePKSet?.has(colName) ? "PRIMARY KEY" : ""; return `"${colName}" ${type} ${nullable} ${primaryKey}`.trim(); }); const tableConstraints = []; if (compositePrimaryKey) { const pkCols = compositePrimaryKey.map((c) => `"${c}"`).join(", "); tableConstraints.push(`PRIMARY KEY (${pkCols})`); } if (tableName === storage.TABLE_WORKFLOW_SNAPSHOT) { tableConstraints.push("UNIQUE (workflow_name, run_id)"); } if (tableName === storage.TABLE_SPANS) { tableConstraints.push("UNIQUE (spanId, traceId)"); } const allDefinitions = [...columnDefinitions, ...tableConstraints].join(",\n "); const sql = `CREATE TABLE IF NOT EXISTS ${parsedTableName} ( ${allDefinitions} )`; await this.client.execute(sql); this.logger.debug(`LibSQLDB: Created table ${tableName}`); if (tableName === storage.TABLE_SPANS) { await this.migrateSpansTable(); } } catch (error$1) { if (error$1 instanceof error.MastraError) { throw error$1; } throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "CREATE_TABLE", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { tableName } }, error$1 ); } finally { this.tableColumnsCache.delete(tableName); } } /** * Migrates the spans table schema from OLD_SPAN_SCHEMA to current SPAN_SCHEMA. * This adds new columns that don't exist in old schema and ensures required indexes exist. */ async migrateSpansTable() { const schema = storage.TABLE_SCHEMAS[storage.TABLE_SPANS]; try { const existingColumnsRaw = await this.getTableColumns(storage.TABLE_SPANS); const existingColumns = new Set([...existingColumnsRaw].map((column) => column.toLowerCase())); let addedColumns = false; for (const [columnName, columnDef] of Object.entries(schema)) { if (!existingColumns.has(columnName.toLowerCase())) { const sqlType = this.getSqlType(columnDef.type); const alterSql = `ALTER TABLE "${storage.TABLE_SPANS}" ADD COLUMN "${columnName}" ${sqlType}`; await this.client.execute(alterSql); addedColumns = true; this.logger.debug(`LibSQLDB: Added column '${columnName}' to ${storage.TABLE_SPANS}`); } } if (addedColumns) { this.tableColumnsCache.delete(storage.TABLE_SPANS); } const indexExists = await this.spansUniqueIndexExists(); if (!indexExists) { const duplicateInfo = await this.checkForDuplicateSpans(); if (duplicateInfo.hasDuplicates) { const errorMessage = ` =========================================================================== MIGRATION REQUIRED: Duplicate spans detected in ${storage.TABLE_SPANS} =========================================================================== Found ${duplicateInfo.duplicateCount} duplicate (traceId, spanId) combinations. The spans table requires a unique constraint on (traceId, spanId), but your database contains duplicate entries that must be resolved first. To fix this, run the manual migration command: npx mastra migrate This command will: 1. Remove duplicate spans (keeping the most complete/recent version) 2. Add the required unique constraint Note: This migration may take some time for large tables. =========================================================================== `; throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "MIGRATION_REQUIRED", "DUPLICATE_SPANS"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, text: errorMessage }); } else { await this.client.execute( `CREATE UNIQUE INDEX IF NOT EXISTS "mastra_ai_spans_spanid_traceid_idx" ON "${storage.TABLE_SPANS}" ("spanId", "traceId")` ); this.logger.debug(`LibSQLDB: Created unique index on (spanId, traceId) for ${storage.TABLE_SPANS}`); } } this.logger.info(`LibSQLDB: Migration completed for ${storage.TABLE_SPANS}`); } catch (error$1) { if (error$1 instanceof error.MastraError) { throw error$1; } this.logger.warn(`LibSQLDB: Failed to migrate spans table ${storage.TABLE_SPANS}:`, error$1); } } /** * Checks if the unique index on (spanId, traceId) already exists on the spans table. * Used to skip deduplication when the index already exists (migration already complete). */ async spansUniqueIndexExists() { try { const result = await this.client.execute( `SELECT 1 FROM sqlite_master WHERE type = 'index' AND name = 'mastra_ai_spans_spanid_traceid_idx'` ); return (result.rows?.length ?? 0) > 0; } catch { return false; } } /** * Checks for duplicate (traceId, spanId) combinations in the spans table. * Returns information about duplicates for logging/CLI purposes. */ async checkForDuplicateSpans() { try { const result = await this.client.execute(` SELECT COUNT(*) as duplicate_count FROM ( SELECT "spanId", "traceId" FROM "${storage.TABLE_SPANS}" GROUP BY "spanId", "traceId" HAVING COUNT(*) > 1 ) `); const duplicateCount = Number(result.rows?.[0]?.duplicate_count ?? 0); return { hasDuplicates: duplicateCount > 0, duplicateCount }; } catch (error) { this.logger.debug(`LibSQLDB: Could not check for duplicates: ${error}`); return { hasDuplicates: false, duplicateCount: 0 }; } } /** * Manually run the spans migration to deduplicate and add the unique constraint. * This is intended to be called from the CLI when duplicates are detected. * * @returns Migration result with status and details */ async migrateSpans() { const indexExists = await this.spansUniqueIndexExists(); if (indexExists) { return { success: true, alreadyMigrated: true, duplicatesRemoved: 0, message: `Migration already complete. Unique index exists on ${storage.TABLE_SPANS}.` }; } const duplicateInfo = await this.checkForDuplicateSpans(); if (duplicateInfo.hasDuplicates) { this.logger.info( `Found ${duplicateInfo.duplicateCount} duplicate (traceId, spanId) combinations. Starting deduplication...` ); await this.deduplicateSpans(); } else { this.logger.info(`No duplicate spans found.`); } await this.client.execute( `CREATE UNIQUE INDEX IF NOT EXISTS "mastra_ai_spans_spanid_traceid_idx" ON "${storage.TABLE_SPANS}" ("spanId", "traceId")` ); return { success: true, alreadyMigrated: false, duplicatesRemoved: duplicateInfo.duplicateCount, message: duplicateInfo.hasDuplicates ? `Migration complete. Removed duplicates and added unique index to ${storage.TABLE_SPANS}.` : `Migration complete. Added unique index to ${storage.TABLE_SPANS}.` }; } /** * Check migration status for the spans table. * Returns information about whether migration is needed. */ async checkSpansMigrationStatus() { const indexExists = await this.spansUniqueIndexExists(); if (indexExists) { return { needsMigration: false, hasDuplicates: false, duplicateCount: 0, constraintExists: true, tableName: storage.TABLE_SPANS }; } const duplicateInfo = await this.checkForDuplicateSpans(); return { needsMigration: true, hasDuplicates: duplicateInfo.hasDuplicates, duplicateCount: duplicateInfo.duplicateCount, constraintExists: false, tableName: storage.TABLE_SPANS }; } /** * Deduplicates spans table by removing duplicate (spanId, traceId) combinations. * Keeps the "best" record for each duplicate group based on: * 1. Completed spans (endedAt IS NOT NULL) over incomplete ones * 2. Most recently updated (updatedAt DESC) * 3. Most recently created (createdAt DESC) as tiebreaker */ async deduplicateSpans() { try { const duplicateCheck = await this.client.execute(` SELECT COUNT(*) as duplicate_count FROM ( SELECT "spanId", "traceId" FROM "${storage.TABLE_SPANS}" GROUP BY "spanId", "traceId" HAVING COUNT(*) > 1 ) `); const duplicateCount = Number(duplicateCheck.rows?.[0]?.duplicate_count ?? 0); if (duplicateCount === 0) { this.logger.debug(`LibSQLDB: No duplicate spans found, skipping deduplication`); return; } this.logger.warn(`LibSQLDB: Found ${duplicateCount} duplicate (spanId, traceId) combinations, deduplicating...`); const deleteResult = await this.client.execute(` DELETE FROM "${storage.TABLE_SPANS}" WHERE rowid NOT IN ( SELECT MIN(best_rowid) FROM ( SELECT rowid as best_rowid, "spanId", "traceId", ROW_NUMBER() OVER ( PARTITION BY "spanId", "traceId" ORDER BY CASE WHEN "endedAt" IS NOT NULL THEN 0 ELSE 1 END, "updatedAt" DESC, "createdAt" DESC ) as rn FROM "${storage.TABLE_SPANS}" ) ranked WHERE rn = 1 GROUP BY "spanId", "traceId" ) AND ("spanId", "traceId") IN ( SELECT "spanId", "traceId" FROM "${storage.TABLE_SPANS}" GROUP BY "spanId", "traceId" HAVING COUNT(*) > 1 ) `); const deletedCount = deleteResult.rowsAffected ?? 0; this.logger.warn(`LibSQLDB: Deleted ${deletedCount} duplicate span records`); } catch (error) { this.logger.warn(`LibSQLDB: Failed to deduplicate spans:`, error); } } /** * Gets a default value for a column type (used when adding NOT NULL columns). */ getDefaultValue(type) { switch (type) { case "text": case "uuid": return "DEFAULT ''"; case "integer": case "bigint": case "float": return "DEFAULT 0"; case "boolean": return "DEFAULT 0"; case "jsonb": return "DEFAULT '{}'"; case "timestamp": return "DEFAULT CURRENT_TIMESTAMP"; default: return "DEFAULT ''"; } } /** * Alters an existing table to add missing columns. * Used for schema migrations when new columns are added. * * @param args - The alter table arguments * @param args.tableName - The name of the table to alter * @param args.schema - The full schema definition for the table * @param args.ifNotExists - Array of column names to add if they don't exist */ async alterTable({ tableName, schema, ifNotExists }) { const parsedTableName = utils.parseSqlIdentifier(tableName, "table name"); try { const tableInfo = await this.client.execute({ sql: `PRAGMA table_info("${parsedTableName}")` }); const existingColumns = new Set((tableInfo.rows || []).map((row) => row.name?.toLowerCase())); for (const columnName of ifNotExists) { if (!existingColumns.has(columnName.toLowerCase()) && schema[columnName]) { const columnDef = schema[columnName]; const sqlType = this.getSqlType(columnDef.type); const defaultValue = columnDef.nullable ? "DEFAULT NULL" : this.getDefaultValue(columnDef.type); const alterSql = `ALTER TABLE ${parsedTableName} ADD COLUMN "${columnName}" ${sqlType} ${defaultValue}`; await this.client.execute(alterSql); this.logger.debug(`LibSQLDB: Added column ${columnName} to table ${tableName}`); } } } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "ALTER_TABLE", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { tableName } }, error$1 ); } finally { this.tableColumnsCache.delete(tableName); } } /** * Deletes all records from the specified table. * Errors are logged but not thrown. * * @param args - The delete arguments * @param args.tableName - The name of the table to clear */ async deleteData({ tableName }) { const parsedTableName = utils.parseSqlIdentifier(tableName, "table name"); try { await withClientWriteLock(this.client, () => this.client.execute(`DELETE FROM ${parsedTableName}`)); } catch (e) { const mastraError = new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "CLEAR_TABLE", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { tableName } }, e ); this.logger?.trackException?.(mastraError); this.logger?.error?.(mastraError.toString()); } } }; var AgentsLibSQL = class extends storage.AgentsStorage { #db; #client; constructor(config) { super(); const client = resolveClient(config); this.#client = client; this.#db = new LibSQLDB({ client, maxRetries: config.maxRetries, initialBackoffMs: config.initialBackoffMs }); } async init() { await this.#migrateFromLegacySchema(); await this.#migrateVersionsSchema(); await this.#db.createTable({ tableName: storage.TABLE_AGENTS, schema: storage.AGENTS_SCHEMA }); await this.#db.createTable({ tableName: storage.TABLE_AGENT_VERSIONS, schema: storage.AGENT_VERSIONS_SCHEMA }); await this.#db.alterTable({ tableName: storage.TABLE_AGENTS, schema: storage.AGENTS_SCHEMA, ifNotExists: ["status", "authorId", "visibility", "favoriteCount"] }); await this.#db.alterTable({ tableName: storage.TABLE_AGENT_VERSIONS, schema: storage.AGENT_VERSIONS_SCHEMA, ifNotExists: [ "mcpClients", "requestContextSchema", "workspace", "skills", "skillsFormat", "browser", "toolProviders" ] }); await this.#migrateToolsToJsonbFormat(); await this.#cleanupStaleDrafts(); } /** * Migrates from the legacy flat agent schema (where config fields like name, instructions, model * were stored directly on mastra_agents) to the new versioned schema (thin agent record + versions table). * SQLite cannot drop columns or alter NOT NULL constraints, so we must recreate the table. */ async #migrateFromLegacySchema() { const legacyTable = `${storage.TABLE_AGENTS}_legacy`; const hasLegacyColumns = await this.#db.hasColumn(storage.TABLE_AGENTS, "name"); if (hasLegacyColumns) { await this.#client.execute({ sql: `ALTER TABLE "${storage.TABLE_AGENTS}" RENAME TO "${legacyTable}"` }); await this.#client.execute({ sql: `DROP TABLE IF EXISTS "${storage.TABLE_AGENT_VERSIONS}"` }); } const legacyExists = await this.#db.hasColumn(legacyTable, "name"); if (!legacyExists) return; const result = await this.#client.execute({ sql: `SELECT * FROM "${legacyTable}"` }); const oldAgents = result.rows || []; await this.#db.createTable({ tableName: storage.TABLE_AGENTS, schema: storage.AGENTS_SCHEMA }); await this.#db.createTable({ tableName: storage.TABLE_AGENT_VERSIONS, schema: storage.AGENT_VERSIONS_SCHEMA }); for (const row of oldAgents) { const agentId = row.id; if (!agentId) continue; const versionId = crypto.randomUUID(); const now = /* @__PURE__ */ new Date(); await this.#db.insert({ tableName: storage.TABLE_AGENTS, record: { id: agentId, status: "published", activeVersionId: versionId, authorId: row.ownerId ?? row.authorId ?? null, metadata: row.metadata ?? null, createdAt: row.createdAt ?? now, updatedAt: row.updatedAt ?? now } }); await this.#db.insert({ tableName: storage.TABLE_AGENT_VERSIONS, record: { id: versionId, agentId, versionNumber: 1, name: row.name ?? agentId, description: row.description ?? null, instructions: this.serializeInstructions(row.instructions ?? ""), model: row.model ?? "{}", tools: row.tools ?? null, defaultOptions: row.defaultOptions ?? null, workflows: row.workflows ?? null, agents: row.agents ?? null, integrationTools: row.integrationTools ?? null, toolProviders: row.toolProviders ?? null, inputProcessors: row.inputProcessors ?? null, outputProcessors: row.outputProcessors ?? null, memory: row.memory ?? null, scorers: row.scorers ?? null, changedFields: null, changeMessage: "Migrated from legacy schema", createdAt: row.createdAt ?? now } }); } await this.#client.execute({ sql: `DROP TABLE IF EXISTS "${legacyTable}"` }); } /** * Migrates the agent_versions table from the old snapshot-based schema (single `snapshot` JSON column) * to the new flat schema (individual config columns). This handles the case where the agents table * was already migrated but the versions table still has the old schema. */ async #migrateVersionsSchema() { const hasSnapshotColumn = await this.#db.hasColumn(storage.TABLE_AGENT_VERSIONS, "snapshot"); if (!hasSnapshotColumn) return; await this.#client.execute({ sql: `DROP TABLE IF EXISTS "${storage.TABLE_AGENT_VERSIONS}"` }); await this.#client.execute({ sql: `DROP TABLE IF EXISTS "${storage.TABLE_AGENTS}_legacy"` }); } /** * Removes stale draft agent records that have no versions at all. * These are left behind when createAgent partially fails (inserts thin record * but fails to create the version due to schema mismatch). * * A legitimate draft (never published) will have rows in the versions table, * so we must only delete records with zero associated versions. */ async #cleanupStaleDrafts() { try { await this.#client.execute({ sql: `DELETE FROM "${storage.TABLE_AGENTS}" WHERE status = 'draft' AND activeVersionId IS NULL AND NOT EXISTS ( SELECT 1 FROM "${storage.TABLE_AGENT_VERSIONS}" WHERE "${storage.TABLE_AGENT_VERSIONS}".agentId = "${storage.TABLE_AGENTS}".id )` }); } catch { } } /** * Migrates the tools field from string[] format to JSONB format { "tool-key": { "description": "..." } }. * This handles the transition from the old format where tools were stored as an array of string keys * to the new format where tools can have per-agent description overrides. */ async #migrateToolsToJsonbFormat() { try { const result = await this.#client.execute({ sql: `SELECT id, tools FROM "${storage.TABLE_AGENT_VERSIONS}" WHERE tools IS NOT NULL` }); if (!result.rows || result.rows.length === 0) { return; } for (const row of result.rows) { const toolsValue = row.tools; let parsedTools; try { if (typeof toolsValue === "string") { parsedTools = JSON.parse(toolsValue); } else if (toolsValue instanceof ArrayBuffer) { const decoder = new TextDecoder(); parsedTools = JSON.parse(decoder.decode(toolsValue)); } else { parsedTools = toolsValue; } } catch { continue; } if (Array.isArray(parsedTools)) { const toolsObject = {}; for (const toolKey of parsedTools) { if (typeof toolKey === "string") { toolsObject[toolKey] = {}; } } await this.#client.execute({ sql: `UPDATE "${storage.TABLE_AGENT_VERSIONS}" SET tools = ? WHERE id = ?`, args: [JSON.stringify(toolsObject), row.id] }); } } this.logger?.info?.(`Migrated agent version tools from array to object format`); } catch (error) { this.logger?.warn?.("Failed to migrate tools to JSONB format:", error); } } async dangerouslyClearAll() { await this.#db.deleteData({ tableName: storage.TABLE_AGENT_VERSIONS }); await this.#db.deleteData({ tableName: storage.TABLE_AGENTS }); } parseJson(value, fieldName) { if (!value) return void 0; if (value instanceof ArrayBuffer || value && value.constructor && value.constructor.name === "ArrayBuffer") { try { const decoder = new TextDecoder(); const jsonString = decoder.decode(value); return JSON.parse(jsonString); } catch (error) { console.error(`Failed to parse ArrayBuffer for ${fieldName}:`, error); return void 0; } } if (typeof value !== "string") return value; try { return JSON.parse(value); } catch (error$1) { const details = { value: value.length > 100 ? value.substring(0, 100) + "..." : value }; if (fieldName) { details.field = fieldName; } throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "PARSE_JSON", "INVALID_JSON"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.SYSTEM, text: `Failed to parse JSON${fieldName ? ` for field "${fieldName}"` : ""}: ${error$1 instanceof Error ? error$1.message : "Unknown error"}`, details }, error$1 ); } } parseRow(row) { return { id: row.id, status: row.status, activeVersionId: row.activeVersionId, authorId: row.authorId, visibility: row.visibility ?? void 0, metadata: this.parseJson(row.metadata, "metadata"), favoriteCount: row.favoriteCount === null || row.favoriteCount === void 0 ? 0 : Number(row.favoriteCount), createdAt: new Date(row.createdAt), updatedAt: new Date(row.updatedAt) }; } async getById(id) { try { const result = await this.#db.select({ tableName: storage.TABLE_AGENTS, keys: { id } }); return result ? this.parseRow(result) : null; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_AGENT_BY_ID", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { agentId: id } }, error$1 ); } } async create(input) { const { agent } = input; try { const now = /* @__PURE__ */ new Date(); const visibility = agent.visibility ?? (agent.authorId ? "private" : null); await this.#db.insert({ tableName: storage.TABLE_AGENTS, record: { id: agent.id, status: "draft", activeVersionId: null, authorId: agent.authorId ?? null, visibility, metadata: agent.metadata ?? null, favoriteCount: 0, createdAt: now, updatedAt: now } }); const { id: _id, authorId: _authorId, visibility: _visibility, metadata: _metadata, ...snapshotConfig } = agent; const versionId = crypto.randomUUID(); await this.createVersion({ id: versionId, agentId: agent.id, versionNumber: 1, ...snapshotConfig, changedFields: Object.keys(snapshotConfig), changeMessage: "Initial version" }); const created = await this.getById(agent.id); if (!created) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "CREATE_AGENT", "NOT_FOUND_AFTER_CREATE"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.SYSTEM, text: `Agent ${agent.id} not found after creation`, details: { agentId: agent.id } }); } return created; } catch (error$1) { if (error$1 instanceof error.MastraError) { throw error$1; } throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "CREATE_AGENT", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { agentId: agent.id } }, error$1 ); } } async update(input) { const { id, ...updates } = input; try { const existing = await this.getById(id); if (!existing) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "UPDATE_AGENT", "NOT_FOUND"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, text: `Agent ${id} not found`, details: { agentId: id } }); } const { authorId, activeVersionId, metadata, status, visibility } = updates; const updateData = { updatedAt: (/* @__PURE__ */ new Date()).toISOString() }; if (authorId !== void 0) updateData.authorId = authorId; if (activeVersionId !== void 0) updateData.activeVersionId = activeVersionId; if (status !== void 0) updateData.status = status; if (visibility !== void 0) updateData.visibility = visibility; if (metadata !== void 0) { updateData.metadata = { ...existing.metadata, ...metadata }; } await this.#db.update({ tableName: storage.TABLE_AGENTS, keys: { id }, data: updateData }); const updatedAgent = await this.getById(id); if (!updatedAgent) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "UPDATE_AGENT", "NOT_FOUND_AFTER_UPDATE"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.SYSTEM, text: `Agent ${id} not found after update`, details: { agentId: id } }); } return updatedAgent; } catch (error$1) { if (error$1 instanceof error.MastraError) { throw error$1; } throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "UPDATE_AGENT", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { agentId: id } }, error$1 ); } } async delete(id) { try { await this.deleteVersionsByParentId(id); await this.#db.delete({ tableName: storage.TABLE_AGENTS, keys: { id } }); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "DELETE_AGENT", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { agentId: id } }, error$1 ); } } async list(args) { const { page = 0, perPage: perPageInput, orderBy, authorId, metadata, status, visibility, entityIds, pinFavoritedFor, favoritedOnly } = args || {}; const { field, direction } = this.parseOrderBy(orderBy); if (page < 0) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_AGENTS", "INVALID_PAGE"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { page } }, new Error("page must be >= 0") ); } const perPage = storage.normalizePerPage(perPageInput, 100); const { offset, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage); try { if (entityIds && entityIds.length === 0) { return { agents: [], total: 0, page, perPage: perPageForResponse, hasMore: false }; } const conditions = []; const queryParams = []; if (status) { conditions.push("a.status = ?"); queryParams.push(status); } if (authorId !== void 0) { conditions.push("a.authorId = ?"); queryParams.push(authorId); } if (visibility !== void 0) { conditions.push("a.visibility = ?"); queryParams.push(visibility); } if (metadata && Object.keys(metadata).length > 0) { for (const [key, value] of Object.entries(metadata)) { if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key)) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "LIST_AGENTS", "INVALID_METADATA_KEY"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, text: `Invalid metadata key: ${key}. Keys must be alphanumeric with underscores.`, details: { key } }); } conditions.push(`json_extract(a.metadata, '$.${key}') = ?`); queryParams.push(typeof value === "string" ? value : JSON.stringify(value)); } } if (entityIds && entityIds.length > 0) { const placeholders = entityIds.map(() => "?").join(", "); conditions.push(`a.id IN (${placeholders})`); queryParams.push(...entityIds); } const joinUserId = pinFavoritedFor; const useJoin = Boolean(joinUserId); let joinClause = ""; const joinParams = []; if (useJoin && joinUserId) { joinClause = `LEFT JOIN "${storage.TABLE_FAVORITES}" s ON s."entityType" = 'agent' AND s."entityId" = a.id AND s."userId" = ?`; joinParams.push(joinUserId); if (favoritedOnly) { conditions.push('s."userId" IS NOT NULL'); } } else if (favoritedOnly) { conditions.push("1=0"); } const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : ""; const countResult = await this.#client.execute({ sql: `SELECT COUNT(*) as count FROM "${storage.TABLE_AGENTS}" a ${joinClause} ${whereClause}`, args: [...joinParams, ...queryParams] }); const total = Number(countResult.rows?.[0]?.count ?? 0); if (total === 0) { return { agents: [], total: 0, page, perPage: perPageForResponse, hasMore: false }; } const orderByParts = []; if (useJoin && joinUserId) { orderByParts.push(`(s."userId" IS NOT NULL) DESC`); } orderByParts.push(`a."${field}" ${direction}`); orderByParts.push(`a."id" ASC`); const orderByClause = `ORDER BY ${orderByParts.join(", ")}`; const limitValue = perPageInput === false ? total : perPage; const selectCols = buildSelectColumnsWithAlias(storage.TABLE_AGENTS, "a"); const result = await this.#client.execute({ sql: `SELECT ${selectCols} FROM "${storage.TABLE_AGENTS}" a ${joinClause} ${whereClause} ${orderByClause} LIMIT ? OFFSET ?`, args: [...joinParams, ...queryParams, limitValue, offset] }); const rows = result.rows ?? []; const agents = rows.map((row) => this.parseRow(row)); return { agents, total, page, perPage: perPageForResponse, hasMore: perPageInput === false ? false : offset + perPage < total }; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_AGENTS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } // ========================================================================== // Agent Version Methods // ========================================================================== async createVersion(input) { try { const now = /* @__PURE__ */ new Date(); await this.#db.insert({ tableName: storage.TABLE_AGENT_VERSIONS, record: { id: input.id, agentId: input.agentId, versionNumber: input.versionNumber, name: input.name ?? null, description: input.description ?? null, instructions: this.serializeInstructions(input.instructions), model: input.model, tools: input.tools ?? null, defaultOptions: input.defaultOptions ?? null, workflows: input.workflows ?? null, agents: input.agents ?? null, integrationTools: input.integrationTools ?? null, toolProviders: input.toolProviders ?? null, inputProcessors: input.inputProcessors ?? null, outputProcessors: input.outputProcessors ?? null, memory: input.memory ?? null, scorers: input.scorers ?? null, mcpClients: input.mcpClients ?? null, requestContextSchema: input.requestContextSchema ?? null, workspace: input.workspace ?? null, skills: input.skills ?? null, skillsFormat: input.skillsFormat ?? null, browser: input.browser ?? null, changedFields: input.changedFields ?? null, changeMessage: input.changeMessage ?? null, createdAt: now } }); return { ...input, createdAt: now }; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "CREATE_VERSION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { versionId: input.id, agentId: input.agentId } }, error$1 ); } } async getVersion(id) { try { const result = await this.#db.select({ tableName: storage.TABLE_AGENT_VERSIONS, keys: { id } }); if (!result) { return null; } return this.parseVersionRow(result); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_VERSION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { versionId: id } }, error$1 ); } } async getVersionByNumber(agentId, versionNumber) { try { const rows = await this.#db.selectMany({ tableName: storage.TABLE_AGENT_VERSIONS, whereClause: { sql: "WHERE agentId = ? AND versionNumber = ?", args: [agentId, versionNumber] }, limit: 1 }); if (!rows || rows.length === 0) { return null; } return this.parseVersionRow(rows[0]); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_VERSION_BY_NUMBER", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { agentId, versionNumber } }, error$1 ); } } async getLatestVersion(agentId) { try { const rows = await this.#db.selectMany({ tableName: storage.TABLE_AGENT_VERSIONS, whereClause: { sql: "WHERE agentId = ?", args: [agentId] }, orderBy: "versionNumber DESC", limit: 1 }); if (!rows || rows.length === 0) { return null; } return this.parseVersionRow(rows[0]); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_LATEST_VERSION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { agentId } }, error$1 ); } } async listVersions(input) { const { agentId, page = 0, perPage: perPageInput, orderBy } = input; if (page < 0) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_VERSIONS", "INVALID_PAGE"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { page } }, new Error("page must be >= 0") ); } const perPage = storage.normalizePerPage(perPageInput, 20); const { offset, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage); try { const { field, direction } = this.parseVersionOrderBy(orderBy); const total = await this.#db.selectTotalCount({ tableName: storage.TABLE_AGENT_VERSIONS, whereClause: { sql: "WHERE agentId = ?", args: [agentId] } }); if (total === 0) { return { versions: [], total: 0, page, perPage: perPageForResponse, hasMore: false }; } const limitValue = perPageInput === false ? total : perPage; const rows = await this.#db.selectMany({ tableName: storage.TABLE_AGENT_VERSIONS, whereClause: { sql: "WHERE agentId = ?", args: [agentId] }, orderBy: `"${field}" ${direction}`, limit: limitValue, offset }); const versions = rows.map((row) => this.parseVersionRow(row)); return { versions, total, page, perPage: perPageForResponse, hasMore: perPageInput === false ? false : offset + perPage < total }; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_VERSIONS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { agentId } }, error$1 ); } } async deleteVersion(id) { try { await this.#db.delete({ tableName: storage.TABLE_AGENT_VERSIONS, keys: { id } }); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "DELETE_VERSION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { versionId: id } }, error$1 ); } } async deleteVersionsByParentId(entityId) { try { const versions = await this.#db.selectMany({ tableName: storage.TABLE_AGENT_VERSIONS, whereClause: { sql: "WHERE agentId = ?", args: [entityId] } }); for (const version of versions) { await this.#db.delete({ tableName: storage.TABLE_AGENT_VERSIONS, keys: { id: version.id } }); } } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "DELETE_VERSIONS_BY_AGENT_ID", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { agentId: entityId } }, error$1 ); } } async countVersions(agentId) { try { const count = await this.#db.selectTotalCount({ tableName: storage.TABLE_AGENT_VERSIONS, whereClause: { sql: "WHERE agentId = ?", args: [agentId] } }); return count; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "COUNT_VERSIONS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { agentId } }, error$1 ); } } // ========================================================================== // Private Helper Methods // ========================================================================== serializeInstructions(instructions) { return Array.isArray(instructions) ? JSON.stringify(instructions) : instructions; } deserializeInstructions(raw) { if (!raw) return raw; try { const parsed = JSON.parse(raw); if (Array.isArray(parsed)) return parsed; } catch { } return raw; } parseVersionRow(row) { return { id: row.id, agentId: row.agentId, versionNumber: row.versionNumber, name: row.name, description: row.description, instructions: this.deserializeInstructions(row.instructions), model: this.parseJson(row.model, "model"), tools: this.parseJson(row.tools, "tools"), defaultOptions: this.parseJson(row.defaultOptions, "defaultOptions"), workflows: this.parseJson(row.workflows, "workflows"), agents: this.parseJson(row.agents, "agents"), integrationTools: this.parseJson(row.integrationTools, "integrationTools"), toolProviders: this.parseJson(row.toolProviders, "toolProviders"), inputProcessors: this.parseJson(row.inputProcessors, "inputProcessors"), outputProcessors: this.parseJson(row.outputProcessors, "outputProcessors"), memory: this.parseJson(row.memory, "memory"), scorers: this.parseJson(row.scorers, "scorers"), mcpClients: this.parseJson(row.mcpClients, "mcpClients"), requestContextSchema: this.parseJson(row.requestContextSchema, "requestContextSchema"), workspace: this.parseJson(row.workspace, "workspace"), skills: this.parseJson(row.skills, "skills"), skillsFormat: row.skillsFormat, browser: this.parseJson(row.browser, "browser"), changedFields: this.parseJson(row.changedFields, "changedFields"), changeMessage: row.changeMessage, createdAt: new Date(row.createdAt) }; } }; function serializeJson(v) { if (typeof v === "object" && v != null) return JSON.stringify(v); return v ?? null; } function parseJson(val) { if (val == null) return void 0; if (typeof val === "string") { try { return JSON.parse(val); } catch { return val; } } return val; } function rowToTask(row) { return { id: String(row.id), status: String(row.status), toolName: String(row.tool_name), toolCallId: String(row.tool_call_id), args: parseJson(row.args) ?? {}, agentId: String(row.agent_id), threadId: row.thread_id != null ? String(row.thread_id) : void 0, resourceId: row.resource_id != null ? String(row.resource_id) : void 0, runId: String(row.run_id), result: parseJson(row.result), error: parseJson(row.error), suspendPayload: parseJson(row.suspend_payload), retryCount: Number(row.retry_count), maxRetries: Number(row.max_retries), timeoutMs: Number(row.timeout_ms), createdAt: new Date(String(row.createdAt)), startedAt: row.startedAt ? new Date(String(row.startedAt)) : void 0, suspendedAt: row.suspendedAt ? new Date(String(row.suspendedAt)) : void 0, completedAt: row.completedAt ? new Date(String(row.completedAt)) : void 0 }; } var BackgroundTasksLibSQL = class extends storage.BackgroundTasksStorage { #db; #client; constructor(config) { super(); const client = resolveClient(config); this.#client = client; this.#db = new LibSQLDB({ client, maxRetries: config.maxRetries, initialBackoffMs: config.initialBackoffMs }); } async init() { await this.#db.createTable({ tableName: storage.TABLE_BACKGROUND_TASKS, schema: storage.TABLE_SCHEMAS[storage.TABLE_BACKGROUND_TASKS] }); await this.#db.alterTable({ tableName: storage.TABLE_BACKGROUND_TASKS, schema: storage.TABLE_SCHEMAS[storage.TABLE_BACKGROUND_TASKS], ifNotExists: ["suspend_payload", "suspendedAt"] }); } async dangerouslyClearAll() { await this.#db.deleteData({ tableName: storage.TABLE_BACKGROUND_TASKS }); } async createTask(task) { await this.#db.insert({ tableName: storage.TABLE_BACKGROUND_TASKS, record: { id: task.id, tool_call_id: task.toolCallId, tool_name: task.toolName, agent_id: task.agentId, thread_id: task.threadId ?? null, resource_id: task.resourceId ?? null, run_id: task.runId, status: task.status, args: task.args, result: task.result ?? null, error: task.error ?? null, suspend_payload: task.suspendPayload ?? null, retry_count: task.retryCount, max_retries: task.maxRetries, timeout_ms: task.timeoutMs, createdAt: task.createdAt.toISOString(), startedAt: task.startedAt?.toISOString() ?? null, suspendedAt: task.suspendedAt?.toISOString() ?? null, completedAt: task.completedAt?.toISOString() ?? null } }); } async updateTask(taskId, update) { const setClauses = []; const params = []; if ("status" in update) { setClauses.push("status = ?"); params.push(update.status); } if ("result" in update) { setClauses.push("result = jsonb(?)"); params.push(serializeJson(update.result)); } if ("error" in update) { setClauses.push("error = jsonb(?)"); params.push(serializeJson(update.error)); } if ("suspendPayload" in update) { setClauses.push("suspend_payload = jsonb(?)"); params.push(serializeJson(update.suspendPayload)); } if ("retryCount" in update) { setClauses.push("retry_count = ?"); params.push(update.retryCount); } if ("startedAt" in update) { setClauses.push("startedAt = ?"); params.push(update.startedAt?.toISOString() ?? null); } if ("suspendedAt" in update) { setClauses.push("suspendedAt = ?"); params.push(update.suspendedAt?.toISOString() ?? null); } if ("completedAt" in update) { setClauses.push("completedAt = ?"); params.push(update.completedAt?.toISOString() ?? null); } if (setClauses.length === 0) return; params.push(taskId); await this.#client.execute({ sql: `UPDATE ${storage.TABLE_BACKGROUND_TASKS} SET ${setClauses.join(", ")} WHERE id = ?`, args: params }); } async getTask(taskId) { const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_BACKGROUND_TASKS)} FROM ${storage.TABLE_BACKGROUND_TASKS} WHERE id = ?`, args: [taskId] }); const row = result.rows[0]; return row ? rowToTask(row) : null; } async listTasks(filter) { const conditions = []; const params = []; if (filter.status) { const statuses = Array.isArray(filter.status) ? filter.status : [filter.status]; conditions.push(`status IN (${statuses.map(() => "?").join(", ")})`); params.push(...statuses); } if (filter.agentId) { conditions.push("agent_id = ?"); params.push(filter.agentId); } if (filter.threadId) { conditions.push("thread_id = ?"); params.push(filter.threadId); } if (filter.runId) { conditions.push("run_id = ?"); params.push(filter.runId); } if (filter.resourceId) { conditions.push("resource_id = ?"); params.push(filter.resourceId); } if (filter.toolName) { conditions.push("tool_name = ?"); params.push(filter.toolName); } if (filter.toolCallId) { conditions.push("tool_call_id = ?"); params.push(filter.toolCallId); } const dateCol = filter.dateFilterBy === "startedAt" ? "startedAt" : filter.dateFilterBy === "suspendedAt" ? "suspendedAt" : filter.dateFilterBy === "completedAt" ? "completedAt" : "createdAt"; if (filter.fromDate) { conditions.push(`${dateCol} >= ?`); params.push(filter.fromDate.toISOString()); } if (filter.toDate) { conditions.push(`${dateCol} < ?`); params.push(filter.toDate.toISOString()); } const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : ""; const countResult = await this.#client.execute({ sql: `SELECT COUNT(*) as count FROM ${storage.TABLE_BACKGROUND_TASKS} ${where}`, args: [...params] }); const total = Number(countResult.rows[0]?.count ?? 0); const orderCol = filter.orderBy === "startedAt" ? "startedAt" : filter.orderBy === "suspendedAt" ? "suspendedAt" : filter.orderBy === "completedAt" ? "completedAt" : "createdAt"; const direction = filter.orderDirection === "desc" ? "DESC" : "ASC"; let sql = `SELECT ${buildSelectColumns(storage.TABLE_BACKGROUND_TASKS)} FROM ${storage.TABLE_BACKGROUND_TASKS} ${where} ORDER BY ${orderCol} ${direction}`; if (filter.perPage != null) { sql += " LIMIT ?"; params.push(filter.perPage); if (filter.page != null) { sql += " OFFSET ?"; params.push(filter.page * filter.perPage); } } const result = await this.#client.execute({ sql, args: params }); return { tasks: result.rows.map((row) => rowToTask(row)), total }; } async deleteTask(taskId) { await this.#client.execute({ sql: `DELETE FROM ${storage.TABLE_BACKGROUND_TASKS} WHERE id = ?`, args: [taskId] }); } async deleteTasks(filter) { const conditions = []; const params = []; if (filter.status) { const statuses = Array.isArray(filter.status) ? filter.status : [filter.status]; conditions.push(`status IN (${statuses.map(() => "?").join(", ")})`); params.push(...statuses); } const dateCol = filter.dateFilterBy === "startedAt" ? "startedAt" : filter.dateFilterBy === "suspendedAt" ? "suspendedAt" : filter.dateFilterBy === "completedAt" ? "completedAt" : "createdAt"; if (filter.fromDate) { conditions.push(`${dateCol} >= ?`); params.push(filter.fromDate.toISOString()); } if (filter.toDate) { conditions.push(`${dateCol} < ?`); params.push(filter.toDate.toISOString()); } if (filter.agentId) { conditions.push("agent_id = ?"); params.push(filter.agentId); } if (filter.runId) { conditions.push("run_id = ?"); params.push(filter.runId); } if (conditions.length === 0) return; await this.#client.execute({ sql: `DELETE FROM ${storage.TABLE_BACKGROUND_TASKS} WHERE ${conditions.join(" AND ")}`, args: params }); } async getRunningCount() { const result = await this.#client.execute({ sql: `SELECT COUNT(*) as count FROM ${storage.TABLE_BACKGROUND_TASKS} WHERE status = 'running'`, args: [] }); return Number(result.rows[0]?.count ?? 0); } async getRunningCountByAgent(agentId) { const result = await this.#client.execute({ sql: `SELECT COUNT(*) as count FROM ${storage.TABLE_BACKGROUND_TASKS} WHERE status = 'running' AND agent_id = ?`, args: [agentId] }); return Number(result.rows[0]?.count ?? 0); } }; var BlobsLibSQL = class extends storage.BlobStore { #db; #client; static MANAGED_TABLES = [storage.TABLE_SKILL_BLOBS]; constructor(config) { super(); const client = resolveClient(config); this.#client = client; this.#db = new LibSQLDB({ client, maxRetries: config.maxRetries, initialBackoffMs: config.initialBackoffMs }); } async init() { await this.#db.createTable({ tableName: storage.TABLE_SKILL_BLOBS, schema: storage.SKILL_BLOBS_SCHEMA }); } async put(entry) { const now = entry.createdAt ?? /* @__PURE__ */ new Date(); await this.#client.execute({ sql: `INSERT OR IGNORE INTO "${storage.TABLE_SKILL_BLOBS}" ("hash", "content", "size", "mimeType", "createdAt") VALUES (?, ?, ?, ?, ?)`, args: [entry.hash, entry.content, entry.size, entry.mimeType ?? null, now.toISOString()] }); } async get(hash) { const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_SKILL_BLOBS)} FROM "${storage.TABLE_SKILL_BLOBS}" WHERE "hash" = ?`, args: [hash] }); if (!result.rows.length) return null; return this.#parseRow(result.rows[0]); } async has(hash) { const result = await this.#client.execute({ sql: `SELECT 1 FROM "${storage.TABLE_SKILL_BLOBS}" WHERE "hash" = ? LIMIT 1`, args: [hash] }); return result.rows.length > 0; } async delete(hash) { const result = await this.#client.execute({ sql: `DELETE FROM "${storage.TABLE_SKILL_BLOBS}" WHERE "hash" = ?`, args: [hash] }); return result.rowsAffected > 0; } async putMany(entries) { if (entries.length === 0) return; await this.#db.batchInsert({ tableName: storage.TABLE_SKILL_BLOBS, records: entries.map((entry) => ({ hash: entry.hash, content: entry.content, size: entry.size, mimeType: entry.mimeType ?? null, createdAt: (entry.createdAt ?? /* @__PURE__ */ new Date()).toISOString() })) }); } async getMany(hashes) { const result = /* @__PURE__ */ new Map(); if (hashes.length === 0) return result; const batchSize = 500; for (let i = 0; i < hashes.length; i += batchSize) { const batch = hashes.slice(i, i + batchSize); const placeholders = batch.map(() => "?").join(", "); const queryResult = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_SKILL_BLOBS)} FROM "${storage.TABLE_SKILL_BLOBS}" WHERE "hash" IN (${placeholders})`, args: batch }); for (const row of queryResult.rows) { const entry = this.#parseRow(row); result.set(entry.hash, entry); } } return result; } async dangerouslyClearAll() { await this.#db.deleteData({ tableName: storage.TABLE_SKILL_BLOBS }); } #parseRow(row) { return { hash: row.hash, content: row.content, size: Number(row.size), mimeType: row.mimeType || void 0, createdAt: new Date(row.createdAt) }; } }; var ChannelsLibSQL = class extends storage.ChannelsStorage { #db; #client; static MANAGED_TABLES = [storage.TABLE_CHANNEL_INSTALLATIONS, storage.TABLE_CHANNEL_CONFIG]; constructor(config) { super(); const client = resolveClient(config); this.#client = client; this.#db = new LibSQLDB({ client, maxRetries: config.maxRetries, initialBackoffMs: config.initialBackoffMs }); } async init() { await this.#db.createTable({ tableName: storage.TABLE_CHANNEL_INSTALLATIONS, schema: storage.TABLE_SCHEMAS[storage.TABLE_CHANNEL_INSTALLATIONS] }); await this.#db.createTable({ tableName: storage.TABLE_CHANNEL_CONFIG, schema: storage.TABLE_SCHEMAS[storage.TABLE_CHANNEL_CONFIG] }); await this.#client.batch( [ { sql: `CREATE UNIQUE INDEX IF NOT EXISTS idx_channel_installations_webhook ON "${storage.TABLE_CHANNEL_INSTALLATIONS}" ("webhookId")`, args: [] }, { sql: `CREATE INDEX IF NOT EXISTS idx_channel_installations_platform_agent ON "${storage.TABLE_CHANNEL_INSTALLATIONS}" ("platform", "agentId")`, args: [] } ], "write" ); } async dangerouslyClearAll() { await this.#db.deleteData({ tableName: storage.TABLE_CHANNEL_INSTALLATIONS }); await this.#db.deleteData({ tableName: storage.TABLE_CHANNEL_CONFIG }); } async saveInstallation(installation) { const now = (/* @__PURE__ */ new Date()).toISOString(); await this.#client.execute({ sql: ` INSERT INTO "${storage.TABLE_CHANNEL_INSTALLATIONS}" (id, platform, agentId, status, webhookId, data, configHash, error, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET platform = excluded.platform, agentId = excluded.agentId, status = excluded.status, webhookId = excluded.webhookId, data = excluded.data, configHash = excluded.configHash, error = excluded.error, updatedAt = excluded.updatedAt `, args: [ installation.id, installation.platform, installation.agentId, installation.status, installation.webhookId ?? null, JSON.stringify(installation.data), installation.configHash ?? null, installation.error ?? null, installation.createdAt?.toISOString() ?? now, now ] }); } async getInstallation(id) { const result = await this.#client.execute({ sql: `SELECT * FROM "${storage.TABLE_CHANNEL_INSTALLATIONS}" WHERE id = ?`, args: [id] }); const row = result.rows?.[0]; return row ? this.#parseInstallationRow(row) : null; } async getInstallationByAgent(platform, agentId) { const result = await this.#client.execute({ sql: `SELECT * FROM "${storage.TABLE_CHANNEL_INSTALLATIONS}" WHERE platform = ? AND agentId = ? ORDER BY CASE status WHEN 'active' THEN 0 WHEN 'pending' THEN 1 ELSE 2 END, updatedAt DESC LIMIT 1`, args: [platform, agentId] }); const row = result.rows?.[0]; return row ? this.#parseInstallationRow(row) : null; } async getInstallationByWebhookId(webhookId) { const result = await this.#client.execute({ sql: `SELECT * FROM "${storage.TABLE_CHANNEL_INSTALLATIONS}" WHERE webhookId = ?`, args: [webhookId] }); const row = result.rows?.[0]; return row ? this.#parseInstallationRow(row) : null; } async listInstallations(platform) { const result = await this.#client.execute({ sql: `SELECT * FROM "${storage.TABLE_CHANNEL_INSTALLATIONS}" WHERE platform = ? ORDER BY createdAt DESC`, args: [platform] }); return result.rows.map((row) => this.#parseInstallationRow(row)); } async deleteInstallation(id) { await this.#client.execute({ sql: `DELETE FROM "${storage.TABLE_CHANNEL_INSTALLATIONS}" WHERE id = ?`, args: [id] }); } async saveConfig(config) { await this.#client.execute({ sql: ` INSERT INTO "${storage.TABLE_CHANNEL_CONFIG}" (platform, data, updatedAt) VALUES (?, ?, ?) ON CONFLICT(platform) DO UPDATE SET data = excluded.data, updatedAt = excluded.updatedAt `, args: [config.platform, JSON.stringify(config.data), config.updatedAt.toISOString()] }); } async getConfig(platform) { const result = await this.#client.execute({ sql: `SELECT * FROM "${storage.TABLE_CHANNEL_CONFIG}" WHERE platform = ?`, args: [platform] }); const row = result.rows?.[0]; if (!row) return null; return { platform: row.platform, data: JSON.parse(row.data || "{}"), updatedAt: new Date(row.updatedAt) }; } async deleteConfig(platform) { await this.#client.execute({ sql: `DELETE FROM "${storage.TABLE_CHANNEL_CONFIG}" WHERE platform = ?`, args: [platform] }); } #parseInstallationRow(row) { return { id: row.id, platform: row.platform, agentId: row.agentId, status: row.status, webhookId: row.webhookId || void 0, data: JSON.parse(row.data || "{}"), configHash: row.configHash || void 0, error: row.error || void 0, createdAt: new Date(row.createdAt), updatedAt: new Date(row.updatedAt) }; } }; function jsonbArg(value) { return value === void 0 || value === null ? null : JSON.stringify(value); } var DatasetsLibSQL = class extends storage.DatasetsStorage { #db; #client; constructor(config) { super(); const client = resolveClient(config); this.#client = client; this.#db = new LibSQLDB({ client, maxRetries: config.maxRetries, initialBackoffMs: config.initialBackoffMs }); } async init() { await this.#db.createTable({ tableName: storage.TABLE_DATASETS, schema: storage.DATASETS_SCHEMA }); await this.#db.createTable({ tableName: storage.TABLE_DATASET_ITEMS, schema: storage.DATASET_ITEMS_SCHEMA }); await this.#db.createTable({ tableName: storage.TABLE_DATASET_VERSIONS, schema: storage.DATASET_VERSIONS_SCHEMA }); await this.#addColumnIfNotExists(storage.TABLE_DATASETS, "requestContextSchema", "TEXT"); await this.#addColumnIfNotExists(storage.TABLE_DATASETS, "tags", "TEXT"); await this.#addColumnIfNotExists(storage.TABLE_DATASETS, "targetType", "TEXT"); await this.#addColumnIfNotExists(storage.TABLE_DATASETS, "targetIds", "TEXT"); await this.#addColumnIfNotExists(storage.TABLE_DATASETS, "scorerIds", "TEXT"); await this.#addColumnIfNotExists(storage.TABLE_DATASET_ITEMS, "requestContext", "TEXT"); await this.#addColumnIfNotExists(storage.TABLE_DATASET_ITEMS, "source", "TEXT"); await this.#addColumnIfNotExists(storage.TABLE_DATASET_ITEMS, "expectedTrajectory", "TEXT"); await this.#client.batch( [ { sql: `CREATE INDEX IF NOT EXISTS idx_dataset_items_dataset_validto ON "${storage.TABLE_DATASET_ITEMS}" ("datasetId", "validTo")`, args: [] }, { sql: `CREATE INDEX IF NOT EXISTS idx_dataset_items_dataset_version ON "${storage.TABLE_DATASET_ITEMS}" ("datasetId", "datasetVersion")`, args: [] }, { sql: `CREATE INDEX IF NOT EXISTS idx_dataset_items_dataset_validto_deleted ON "${storage.TABLE_DATASET_ITEMS}" ("datasetId", "validTo", "isDeleted")`, args: [] }, { sql: `CREATE INDEX IF NOT EXISTS idx_dataset_versions_dataset_version ON "${storage.TABLE_DATASET_VERSIONS}" ("datasetId", "version")`, args: [] }, { sql: `CREATE UNIQUE INDEX IF NOT EXISTS idx_dataset_versions_dataset_version_unique ON "${storage.TABLE_DATASET_VERSIONS}" ("datasetId", "version")`, args: [] } ], "write" ); } async #addColumnIfNotExists(table, column, sqlType) { const exists = await this.#db.hasColumn(table, column); if (!exists) { await this.#client.execute({ sql: `ALTER TABLE "${table}" ADD COLUMN "${column}" ${sqlType}`, args: [] }); } } async dangerouslyClearAll() { await this.#db.deleteData({ tableName: storage.TABLE_DATASET_VERSIONS }); await this.#db.deleteData({ tableName: storage.TABLE_DATASET_ITEMS }); await this.#db.deleteData({ tableName: storage.TABLE_DATASETS }); } // --- Row transformers --- transformDatasetRow(row) { return { id: row.id, name: row.name, description: row.description, metadata: row.metadata ? storage.safelyParseJSON(row.metadata) : void 0, inputSchema: row.inputSchema ? storage.safelyParseJSON(row.inputSchema) : void 0, groundTruthSchema: row.groundTruthSchema ? storage.safelyParseJSON(row.groundTruthSchema) : void 0, requestContextSchema: row.requestContextSchema ? storage.safelyParseJSON(row.requestContextSchema) : void 0, tags: row.tags ? storage.safelyParseJSON(row.tags) : void 0, targetType: row.targetType || void 0, targetIds: row.targetIds ? storage.safelyParseJSON(row.targetIds) : void 0, scorerIds: row.scorerIds ? storage.safelyParseJSON(row.scorerIds) : void 0, version: row.version, createdAt: storage.ensureDate(row.createdAt), updatedAt: storage.ensureDate(row.updatedAt) }; } transformItemRow(row) { return { id: row.id, datasetId: row.datasetId, datasetVersion: row.datasetVersion, input: storage.safelyParseJSON(row.input), groundTruth: row.groundTruth ? storage.safelyParseJSON(row.groundTruth) : void 0, expectedTrajectory: row.expectedTrajectory ? storage.safelyParseJSON(row.expectedTrajectory) : void 0, requestContext: row.requestContext ? storage.safelyParseJSON(row.requestContext) : void 0, metadata: row.metadata ? storage.safelyParseJSON(row.metadata) : void 0, source: row.source ? storage.safelyParseJSON(row.source) : void 0, createdAt: storage.ensureDate(row.createdAt), updatedAt: storage.ensureDate(row.updatedAt) }; } transformItemRowFull(row) { return { id: row.id, datasetId: row.datasetId, datasetVersion: row.datasetVersion, validTo: row.validTo, isDeleted: Boolean(row.isDeleted), input: storage.safelyParseJSON(row.input), groundTruth: row.groundTruth ? storage.safelyParseJSON(row.groundTruth) : void 0, expectedTrajectory: row.expectedTrajectory ? storage.safelyParseJSON(row.expectedTrajectory) : void 0, requestContext: row.requestContext ? storage.safelyParseJSON(row.requestContext) : void 0, metadata: row.metadata ? storage.safelyParseJSON(row.metadata) : void 0, source: row.source ? storage.safelyParseJSON(row.source) : void 0, createdAt: storage.ensureDate(row.createdAt), updatedAt: storage.ensureDate(row.updatedAt) }; } transformDatasetVersionRow(row) { return { id: row.id, datasetId: row.datasetId, version: row.version, createdAt: storage.ensureDate(row.createdAt) }; } // --- Dataset CRUD --- async createDataset(input) { try { const id = crypto.randomUUID(); const now = /* @__PURE__ */ new Date(); const nowIso = now.toISOString(); await this.#db.insert({ tableName: storage.TABLE_DATASETS, record: { id, name: input.name, description: input.description ?? null, metadata: input.metadata, inputSchema: input.inputSchema ?? null, groundTruthSchema: input.groundTruthSchema ?? null, requestContextSchema: input.requestContextSchema ?? null, targetType: input.targetType ?? null, targetIds: input.targetIds ? JSON.stringify(input.targetIds) : null, scorerIds: input.scorerIds ? JSON.stringify(input.scorerIds) : null, version: 0, createdAt: nowIso, updatedAt: nowIso } }); return { id, name: input.name, description: input.description, metadata: input.metadata, inputSchema: input.inputSchema ?? void 0, groundTruthSchema: input.groundTruthSchema ?? void 0, requestContextSchema: input.requestContextSchema ?? void 0, targetType: input.targetType ?? void 0, targetIds: input.targetIds ?? void 0, scorerIds: input.scorerIds ?? void 0, version: 0, createdAt: now, updatedAt: now }; } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "CREATE_DATASET", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async getDatasetById({ id }) { try { const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_DATASETS)} FROM ${storage.TABLE_DATASETS} WHERE id = ?`, args: [id] }); return result.rows?.[0] ? this.transformDatasetRow(result.rows[0]) : null; } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_DATASET", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async _doUpdateDataset(args) { try { const existing = await this.getDatasetById({ id: args.id }); if (!existing) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "UPDATE_DATASET", "NOT_FOUND"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { datasetId: args.id } }); } const now = (/* @__PURE__ */ new Date()).toISOString(); const updates = ["updatedAt = ?"]; const values = [now]; if (args.name !== void 0) { updates.push("name = ?"); values.push(args.name); } if (args.description !== void 0) { updates.push("description = ?"); values.push(args.description); } if (args.metadata !== void 0) { updates.push("metadata = ?"); values.push(JSON.stringify(args.metadata)); } if (args.inputSchema !== void 0) { updates.push("inputSchema = ?"); values.push(args.inputSchema === null ? null : JSON.stringify(args.inputSchema)); } if (args.groundTruthSchema !== void 0) { updates.push("groundTruthSchema = ?"); values.push(args.groundTruthSchema === null ? null : JSON.stringify(args.groundTruthSchema)); } if (args.requestContextSchema !== void 0) { updates.push("requestContextSchema = ?"); values.push(args.requestContextSchema === null ? null : JSON.stringify(args.requestContextSchema)); } if (args.tags !== void 0) { updates.push("tags = ?"); values.push(args.tags === null ? null : JSON.stringify(args.tags)); } if (args.targetType !== void 0) { updates.push("targetType = ?"); values.push(args.targetType === null ? null : args.targetType); } if (args.targetIds !== void 0) { updates.push("targetIds = ?"); values.push(args.targetIds === null ? null : JSON.stringify(args.targetIds)); } if (args.scorerIds !== void 0) { updates.push("scorerIds = ?"); values.push(args.scorerIds === null ? null : JSON.stringify(args.scorerIds)); } values.push(args.id); await this.#client.execute({ sql: `UPDATE ${storage.TABLE_DATASETS} SET ${updates.join(", ")} WHERE id = ?`, args: values }); return { ...existing, name: args.name ?? existing.name, description: args.description ?? existing.description, metadata: args.metadata ?? existing.metadata, inputSchema: (args.inputSchema !== void 0 ? args.inputSchema : existing.inputSchema) ?? void 0, groundTruthSchema: (args.groundTruthSchema !== void 0 ? args.groundTruthSchema : existing.groundTruthSchema) ?? void 0, requestContextSchema: (args.requestContextSchema !== void 0 ? args.requestContextSchema : existing.requestContextSchema) ?? void 0, tags: (args.tags !== void 0 ? args.tags : existing.tags) ?? void 0, targetType: (args.targetType !== void 0 ? args.targetType : existing.targetType) ?? void 0, targetIds: (args.targetIds !== void 0 ? args.targetIds : existing.targetIds) ?? void 0, scorerIds: (args.scorerIds !== void 0 ? args.scorerIds : existing.scorerIds) ?? void 0, updatedAt: new Date(now) }; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "UPDATE_DATASET", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async deleteDataset({ id }) { try { try { await this.#client.execute({ sql: `DELETE FROM ${storage.TABLE_EXPERIMENT_RESULTS} WHERE experimentId IN (SELECT id FROM ${storage.TABLE_EXPERIMENTS} WHERE datasetId = ?)`, args: [id] }); } catch { } try { await this.#client.execute({ sql: `UPDATE ${storage.TABLE_EXPERIMENTS} SET datasetId = NULL, datasetVersion = NULL WHERE datasetId = ?`, args: [id] }); } catch { } await this.#client.batch( [ { sql: `DELETE FROM ${storage.TABLE_DATASET_VERSIONS} WHERE datasetId = ?`, args: [id] }, { sql: `DELETE FROM ${storage.TABLE_DATASET_ITEMS} WHERE datasetId = ?`, args: [id] }, { sql: `DELETE FROM ${storage.TABLE_DATASETS} WHERE id = ?`, args: [id] } ], "write" ); } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "DELETE_DATASET", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async listDatasets(args) { try { const { page, perPage: perPageInput } = args.pagination; const countResult = await this.#client.execute({ sql: `SELECT COUNT(*) as count FROM ${storage.TABLE_DATASETS}`, args: [] }); const total = Number(countResult.rows?.[0]?.count ?? 0); if (total === 0) { return { datasets: [], pagination: { total: 0, page, perPage: perPageInput, hasMore: false } }; } const perPage = storage.normalizePerPage(perPageInput, 100); const { offset: start, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage); const limitValue = perPageInput === false ? total : perPage; const end = perPageInput === false ? total : start + perPage; const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_DATASETS)} FROM ${storage.TABLE_DATASETS} ORDER BY createdAt DESC, id ASC LIMIT ? OFFSET ?`, args: [limitValue, start] }); return { datasets: result.rows?.map((row) => this.transformDatasetRow(row)) ?? [], pagination: { total, page, perPage: perPageForResponse, hasMore: end < total } }; } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_DATASETS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } // --- SCD-2 item mutations --- async _doAddItem(args) { try { const id = crypto.randomUUID(); const versionId = crypto.randomUUID(); const now = /* @__PURE__ */ new Date(); const nowIso = now.toISOString(); const results = await this.#client.batch( [ { sql: `UPDATE ${storage.TABLE_DATASETS} SET version = version + 1 WHERE id = ? RETURNING version`, args: [args.datasetId] }, { sql: `INSERT INTO ${storage.TABLE_DATASET_ITEMS} (id, datasetId, datasetVersion, validTo, isDeleted, input, groundTruth, expectedTrajectory, requestContext, metadata, source, createdAt, updatedAt) VALUES (?, ?, (SELECT version FROM ${storage.TABLE_DATASETS} WHERE id = ?), NULL, 0, jsonb(?), jsonb(?), jsonb(?), jsonb(?), jsonb(?), jsonb(?), ?, ?)`, args: [ id, args.datasetId, args.datasetId, jsonbArg(args.input), jsonbArg(args.groundTruth), jsonbArg(args.expectedTrajectory), jsonbArg(args.requestContext), jsonbArg(args.metadata), jsonbArg(args.source), nowIso, nowIso ] }, { sql: `INSERT INTO ${storage.TABLE_DATASET_VERSIONS} (id, datasetId, version, createdAt) VALUES (?, ?, (SELECT version FROM ${storage.TABLE_DATASETS} WHERE id = ?), ?)`, args: [versionId, args.datasetId, args.datasetId, nowIso] } ], "write" ); const newVersion = Number(results[0].rows[0].version); return { id, datasetId: args.datasetId, datasetVersion: newVersion, input: args.input, groundTruth: args.groundTruth, expectedTrajectory: args.expectedTrajectory, requestContext: args.requestContext, metadata: args.metadata, source: args.source, createdAt: now, updatedAt: now }; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "ADD_ITEM", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async _doUpdateItem(args) { try { const existing = await this.getItemById({ id: args.id }); if (!existing) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "UPDATE_ITEM", "NOT_FOUND"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { itemId: args.id } }); } if (existing.datasetId !== args.datasetId) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "UPDATE_ITEM", "DATASET_MISMATCH"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { itemId: args.id, expectedDatasetId: args.datasetId, actualDatasetId: existing.datasetId } }); } const versionId = crypto.randomUUID(); const now = /* @__PURE__ */ new Date(); const nowIso = now.toISOString(); const mergedInput = args.input !== void 0 ? args.input : existing.input; const mergedGroundTruth = args.groundTruth !== void 0 ? args.groundTruth : existing.groundTruth; const mergedExpectedTrajectory = args.expectedTrajectory !== void 0 ? args.expectedTrajectory : existing.expectedTrajectory; const mergedRequestContext = args.requestContext !== void 0 ? args.requestContext : existing.requestContext; const mergedMetadata = args.metadata !== void 0 ? args.metadata : existing.metadata; const mergedSource = args.source !== void 0 ? args.source : existing.source; const results = await this.#client.batch( [ { sql: `UPDATE ${storage.TABLE_DATASETS} SET version = version + 1 WHERE id = ? RETURNING version`, args: [args.datasetId] }, { sql: `UPDATE ${storage.TABLE_DATASET_ITEMS} SET validTo = (SELECT version FROM ${storage.TABLE_DATASETS} WHERE id = ?) WHERE id = ? AND validTo IS NULL AND isDeleted = 0`, args: [args.datasetId, args.id] }, { sql: `INSERT INTO ${storage.TABLE_DATASET_ITEMS} (id, datasetId, datasetVersion, validTo, isDeleted, input, groundTruth, expectedTrajectory, requestContext, metadata, source, createdAt, updatedAt) VALUES (?, ?, (SELECT version FROM ${storage.TABLE_DATASETS} WHERE id = ?), NULL, 0, jsonb(?), jsonb(?), jsonb(?), jsonb(?), jsonb(?), jsonb(?), ?, ?)`, args: [ args.id, args.datasetId, args.datasetId, jsonbArg(mergedInput), jsonbArg(mergedGroundTruth), jsonbArg(mergedExpectedTrajectory), jsonbArg(mergedRequestContext), jsonbArg(mergedMetadata), jsonbArg(mergedSource), existing.createdAt.toISOString(), nowIso ] }, { sql: `INSERT INTO ${storage.TABLE_DATASET_VERSIONS} (id, datasetId, version, createdAt) VALUES (?, ?, (SELECT version FROM ${storage.TABLE_DATASETS} WHERE id = ?), ?)`, args: [versionId, args.datasetId, args.datasetId, nowIso] } ], "write" ); const newVersion = Number(results[0].rows[0].version); return { ...existing, datasetVersion: newVersion, input: mergedInput, groundTruth: mergedGroundTruth, expectedTrajectory: mergedExpectedTrajectory, requestContext: mergedRequestContext, metadata: mergedMetadata, source: mergedSource, updatedAt: now }; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "UPDATE_ITEM", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async _doDeleteItem({ id, datasetId }) { try { const existing = await this.getItemById({ id }); if (!existing) return; if (existing.datasetId !== datasetId) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "DELETE_ITEM", "DATASET_MISMATCH"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { itemId: id, expectedDatasetId: datasetId, actualDatasetId: existing.datasetId } }); } const versionId = crypto.randomUUID(); const nowIso = (/* @__PURE__ */ new Date()).toISOString(); await this.#client.batch( [ { sql: `UPDATE ${storage.TABLE_DATASETS} SET version = version + 1 WHERE id = ? RETURNING version`, args: [datasetId] }, { sql: `UPDATE ${storage.TABLE_DATASET_ITEMS} SET validTo = (SELECT version FROM ${storage.TABLE_DATASETS} WHERE id = ?) WHERE id = ? AND validTo IS NULL AND isDeleted = 0`, args: [datasetId, id] }, { sql: `INSERT INTO ${storage.TABLE_DATASET_ITEMS} (id, datasetId, datasetVersion, validTo, isDeleted, input, groundTruth, requestContext, metadata, source, createdAt, updatedAt) VALUES (?, ?, (SELECT version FROM ${storage.TABLE_DATASETS} WHERE id = ?), NULL, 1, jsonb(?), jsonb(?), jsonb(?), jsonb(?), jsonb(?), ?, ?)`, args: [ id, datasetId, datasetId, jsonbArg(existing.input), jsonbArg(existing.groundTruth), jsonbArg(existing.requestContext), jsonbArg(existing.metadata), jsonbArg(existing.source), existing.createdAt.toISOString(), nowIso ] }, { sql: `INSERT INTO ${storage.TABLE_DATASET_VERSIONS} (id, datasetId, version, createdAt) VALUES (?, ?, (SELECT version FROM ${storage.TABLE_DATASETS} WHERE id = ?), ?)`, args: [versionId, datasetId, datasetId, nowIso] } ], "write" ); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "DELETE_ITEM", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } // --- SCD-2 queries --- async getItemById(args) { try { let result; if (args.datasetVersion !== void 0) { result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_DATASET_ITEMS)} FROM ${storage.TABLE_DATASET_ITEMS} WHERE id = ? AND datasetVersion = ? AND isDeleted = 0`, args: [args.id, args.datasetVersion] }); } else { result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_DATASET_ITEMS)} FROM ${storage.TABLE_DATASET_ITEMS} WHERE id = ? AND validTo IS NULL AND isDeleted = 0`, args: [args.id] }); } return result.rows?.[0] ? this.transformItemRow(result.rows[0]) : null; } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_ITEM", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async getItemsByVersion({ datasetId, version }) { try { const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_DATASET_ITEMS)} FROM ${storage.TABLE_DATASET_ITEMS} WHERE datasetId = ? AND datasetVersion <= ? AND (validTo IS NULL OR validTo > ?) AND isDeleted = 0 ORDER BY createdAt DESC, id ASC`, args: [datasetId, version, version] }); return result.rows?.map((row) => this.transformItemRow(row)) ?? []; } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_ITEMS_BY_VERSION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async getItemHistory(itemId) { try { const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_DATASET_ITEMS)} FROM ${storage.TABLE_DATASET_ITEMS} WHERE id = ? ORDER BY datasetVersion DESC`, args: [itemId] }); return result.rows?.map((row) => this.transformItemRowFull(row)) ?? []; } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_ITEM_HISTORY", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async listItems(args) { try { const { page, perPage: perPageInput } = args.pagination; if (args.version !== void 0) { const conditions2 = [ "datasetId = ?", "datasetVersion <= ?", "(validTo IS NULL OR validTo > ?)", "isDeleted = 0" ]; const queryParams2 = [args.datasetId, args.version, args.version]; if (args.search) { conditions2.push(`(LOWER(json(input)) LIKE ? OR LOWER(COALESCE(json(groundTruth), '')) LIKE ?)`); const searchPattern = `%${args.search.toLowerCase()}%`; queryParams2.push(searchPattern, searchPattern); } const whereClause2 = `WHERE ${conditions2.join(" AND ")}`; const countResult2 = await this.#client.execute({ sql: `SELECT COUNT(*) as count FROM ${storage.TABLE_DATASET_ITEMS} ${whereClause2}`, args: queryParams2 }); const total2 = Number(countResult2.rows?.[0]?.count ?? 0); if (total2 === 0) { return { items: [], pagination: { total: 0, page, perPage: perPageInput, hasMore: false } }; } const perPage2 = storage.normalizePerPage(perPageInput, 100); const { offset: start2, perPage: perPageForResponse2 } = storage.calculatePagination(page, perPageInput, perPage2); const limitValue2 = perPageInput === false ? total2 : perPage2; const end2 = perPageInput === false ? total2 : start2 + perPage2; const result2 = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_DATASET_ITEMS)} FROM ${storage.TABLE_DATASET_ITEMS} ${whereClause2} ORDER BY createdAt DESC, id ASC LIMIT ? OFFSET ?`, args: [...queryParams2, limitValue2, start2] }); return { items: result2.rows?.map((row) => this.transformItemRow(row)) ?? [], pagination: { total: total2, page, perPage: perPageForResponse2, hasMore: end2 < total2 } }; } const conditions = ["datasetId = ?", "validTo IS NULL", "isDeleted = 0"]; const queryParams = [args.datasetId]; if (args.search) { conditions.push(`(LOWER(json(input)) LIKE ? OR LOWER(COALESCE(json(groundTruth), '')) LIKE ?)`); const searchPattern = `%${args.search.toLowerCase()}%`; queryParams.push(searchPattern, searchPattern); } const whereClause = `WHERE ${conditions.join(" AND ")}`; const countResult = await this.#client.execute({ sql: `SELECT COUNT(*) as count FROM ${storage.TABLE_DATASET_ITEMS} ${whereClause}`, args: queryParams }); const total = Number(countResult.rows?.[0]?.count ?? 0); if (total === 0) { return { items: [], pagination: { total: 0, page, perPage: perPageInput, hasMore: false } }; } const perPage = storage.normalizePerPage(perPageInput, 100); const { offset: start, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage); const limitValue = perPageInput === false ? total : perPage; const end = perPageInput === false ? total : start + perPage; const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_DATASET_ITEMS)} FROM ${storage.TABLE_DATASET_ITEMS} ${whereClause} ORDER BY createdAt DESC, id ASC LIMIT ? OFFSET ?`, args: [...queryParams, limitValue, start] }); return { items: result.rows?.map((row) => this.transformItemRow(row)) ?? [], pagination: { total, page, perPage: perPageForResponse, hasMore: end < total } }; } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_ITEMS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } // --- Dataset version methods --- async createDatasetVersion(datasetId, version) { try { const id = crypto.randomUUID(); const now = /* @__PURE__ */ new Date(); const nowIso = now.toISOString(); await this.#db.insert({ tableName: storage.TABLE_DATASET_VERSIONS, record: { id, datasetId, version, createdAt: nowIso } }); return { id, datasetId, version, createdAt: now }; } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "CREATE_DATASET_VERSION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async listDatasetVersions(input) { try { const { page, perPage: perPageInput } = input.pagination; const countResult = await this.#client.execute({ sql: `SELECT COUNT(*) as count FROM ${storage.TABLE_DATASET_VERSIONS} WHERE datasetId = ?`, args: [input.datasetId] }); const total = Number(countResult.rows?.[0]?.count ?? 0); if (total === 0) { return { versions: [], pagination: { total: 0, page, perPage: perPageInput, hasMore: false } }; } const perPage = storage.normalizePerPage(perPageInput, 100); const { offset: start, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage); const limitValue = perPageInput === false ? total : perPage; const end = perPageInput === false ? total : start + perPage; const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_DATASET_VERSIONS)} FROM ${storage.TABLE_DATASET_VERSIONS} WHERE datasetId = ? ORDER BY version DESC LIMIT ? OFFSET ?`, args: [input.datasetId, limitValue, start] }); return { versions: result.rows?.map((row) => this.transformDatasetVersionRow(row)) ?? [], pagination: { total, page, perPage: perPageForResponse, hasMore: end < total } }; } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_DATASET_VERSIONS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } // --- Bulk operations (SCD-2 internally) --- async _doBatchInsertItems(input) { try { const dataset = await this.getDatasetById({ id: input.datasetId }); if (!dataset) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "BULK_ADD_ITEMS", "DATASET_NOT_FOUND"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { datasetId: input.datasetId } }); } const now = /* @__PURE__ */ new Date(); const nowIso = now.toISOString(); const versionId = crypto.randomUUID(); const statements = [ { sql: `UPDATE ${storage.TABLE_DATASETS} SET version = version + 1 WHERE id = ? RETURNING version`, args: [input.datasetId] } ]; const items = []; for (const itemInput of input.items) { const id = crypto.randomUUID(); items.push({ id, input: itemInput }); statements.push({ sql: `INSERT INTO ${storage.TABLE_DATASET_ITEMS} (id, datasetId, datasetVersion, validTo, isDeleted, input, groundTruth, expectedTrajectory, requestContext, metadata, source, createdAt, updatedAt) VALUES (?, ?, (SELECT version FROM ${storage.TABLE_DATASETS} WHERE id = ?), NULL, 0, jsonb(?), jsonb(?), jsonb(?), jsonb(?), jsonb(?), jsonb(?), ?, ?)`, args: [ id, input.datasetId, input.datasetId, jsonbArg(itemInput.input), jsonbArg(itemInput.groundTruth), jsonbArg(itemInput.expectedTrajectory), jsonbArg(itemInput.requestContext), jsonbArg(itemInput.metadata), jsonbArg(itemInput.source), nowIso, nowIso ] }); } statements.push({ sql: `INSERT INTO ${storage.TABLE_DATASET_VERSIONS} (id, datasetId, version, createdAt) VALUES (?, ?, (SELECT version FROM ${storage.TABLE_DATASETS} WHERE id = ?), ?)`, args: [versionId, input.datasetId, input.datasetId, nowIso] }); const results = await this.#client.batch(statements, "write"); const newVersion = Number(results[0].rows[0].version); return items.map(({ id, input: itemInput }) => ({ id, datasetId: input.datasetId, datasetVersion: newVersion, input: itemInput.input, groundTruth: itemInput.groundTruth, expectedTrajectory: itemInput.expectedTrajectory, requestContext: itemInput.requestContext, metadata: itemInput.metadata, source: itemInput.source, createdAt: now, updatedAt: now })); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "BULK_ADD_ITEMS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async _doBatchDeleteItems(input) { try { const dataset = await this.getDatasetById({ id: input.datasetId }); if (!dataset) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "BULK_DELETE_ITEMS", "DATASET_NOT_FOUND"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { datasetId: input.datasetId } }); } const currentItems = []; for (const itemId of input.itemIds) { const item = await this.getItemById({ id: itemId }); if (item && item.datasetId === input.datasetId) { currentItems.push(item); } } if (currentItems.length === 0) return; const nowIso = (/* @__PURE__ */ new Date()).toISOString(); const versionId = crypto.randomUUID(); const statements = [ { sql: `UPDATE ${storage.TABLE_DATASETS} SET version = version + 1 WHERE id = ? RETURNING version`, args: [input.datasetId] } ]; for (const item of currentItems) { statements.push({ sql: `UPDATE ${storage.TABLE_DATASET_ITEMS} SET validTo = (SELECT version FROM ${storage.TABLE_DATASETS} WHERE id = ?) WHERE id = ? AND validTo IS NULL AND isDeleted = 0`, args: [input.datasetId, item.id] }); statements.push({ sql: `INSERT INTO ${storage.TABLE_DATASET_ITEMS} (id, datasetId, datasetVersion, validTo, isDeleted, input, groundTruth, requestContext, metadata, source, createdAt, updatedAt) VALUES (?, ?, (SELECT version FROM ${storage.TABLE_DATASETS} WHERE id = ?), NULL, 1, jsonb(?), jsonb(?), jsonb(?), jsonb(?), jsonb(?), ?, ?)`, args: [ item.id, input.datasetId, input.datasetId, jsonbArg(item.input), jsonbArg(item.groundTruth), jsonbArg(item.requestContext), jsonbArg(item.metadata), jsonbArg(item.source), item.createdAt.toISOString(), nowIso ] }); } statements.push({ sql: `INSERT INTO ${storage.TABLE_DATASET_VERSIONS} (id, datasetId, version, createdAt) VALUES (?, ?, (SELECT version FROM ${storage.TABLE_DATASETS} WHERE id = ?), ?)`, args: [versionId, input.datasetId, input.datasetId, nowIso] }); await this.#client.batch(statements, "write"); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "BULK_DELETE_ITEMS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } }; var ExperimentsLibSQL = class extends storage.ExperimentsStorage { #db; #client; constructor(config) { super(); const client = resolveClient(config); this.#client = client; this.#db = new LibSQLDB({ client, maxRetries: config.maxRetries, initialBackoffMs: config.initialBackoffMs }); } async init() { await this.#db.createTable({ tableName: storage.TABLE_EXPERIMENTS, schema: storage.EXPERIMENTS_SCHEMA }); await this.#db.createTable({ tableName: storage.TABLE_EXPERIMENT_RESULTS, schema: storage.EXPERIMENT_RESULTS_SCHEMA }); await this.#db.alterTable({ tableName: storage.TABLE_EXPERIMENTS, schema: storage.EXPERIMENTS_SCHEMA, ifNotExists: ["agentVersion"] }); await this.#db.alterTable({ tableName: storage.TABLE_EXPERIMENT_RESULTS, schema: storage.EXPERIMENT_RESULTS_SCHEMA, ifNotExists: ["status", "tags"] }); await this.#client.batch( [ { sql: `CREATE INDEX IF NOT EXISTS idx_experiments_datasetid ON "${storage.TABLE_EXPERIMENTS}" ("datasetId")`, args: [] }, { sql: `CREATE INDEX IF NOT EXISTS idx_experiment_results_experimentid ON "${storage.TABLE_EXPERIMENT_RESULTS}" ("experimentId")`, args: [] }, { sql: `CREATE UNIQUE INDEX IF NOT EXISTS idx_experiment_results_exp_item ON "${storage.TABLE_EXPERIMENT_RESULTS}" ("experimentId", "itemId")`, args: [] } ], "write" ); } async dangerouslyClearAll() { await this.#db.deleteData({ tableName: storage.TABLE_EXPERIMENT_RESULTS }); await this.#db.deleteData({ tableName: storage.TABLE_EXPERIMENTS }); } // Helper to transform row to Experiment transformExperimentRow(row) { return { id: row.id, datasetId: row.datasetId ?? null, datasetVersion: row.datasetVersion != null ? row.datasetVersion : null, agentVersion: row.agentVersion ?? null, targetType: row.targetType, targetId: row.targetId, name: row.name ?? void 0, description: row.description ?? void 0, metadata: row.metadata ? storage.safelyParseJSON(row.metadata) : void 0, status: row.status, totalItems: row.totalItems, succeededCount: row.succeededCount, failedCount: row.failedCount, skippedCount: row.skippedCount ?? 0, startedAt: row.startedAt ? storage.ensureDate(row.startedAt) : null, completedAt: row.completedAt ? storage.ensureDate(row.completedAt) : null, createdAt: storage.ensureDate(row.createdAt), updatedAt: storage.ensureDate(row.updatedAt) }; } // Helper to transform row to ExperimentResult transformExperimentResultRow(row) { return { id: row.id, experimentId: row.experimentId, itemId: row.itemId, itemDatasetVersion: row.itemDatasetVersion != null ? row.itemDatasetVersion : null, input: storage.safelyParseJSON(row.input), output: row.output ? storage.safelyParseJSON(row.output) : null, groundTruth: row.groundTruth ? storage.safelyParseJSON(row.groundTruth) : null, error: row.error ? storage.safelyParseJSON(row.error) : null, startedAt: storage.ensureDate(row.startedAt), completedAt: storage.ensureDate(row.completedAt), retryCount: row.retryCount, traceId: row.traceId ?? null, status: row.status ?? null, tags: row.tags ? storage.safelyParseJSON(row.tags) : null, createdAt: storage.ensureDate(row.createdAt) }; } // Experiment lifecycle async createExperiment(input) { try { const id = input.id ?? crypto.randomUUID(); const now = /* @__PURE__ */ new Date(); const nowIso = now.toISOString(); await this.#db.insert({ tableName: storage.TABLE_EXPERIMENTS, record: { id, datasetId: input.datasetId ?? null, datasetVersion: input.datasetVersion ?? null, agentVersion: input.agentVersion ?? null, targetType: input.targetType, targetId: input.targetId, name: input.name ?? null, description: input.description ?? null, metadata: input.metadata ?? null, status: "pending", totalItems: input.totalItems, succeededCount: 0, failedCount: 0, skippedCount: 0, startedAt: null, completedAt: null, createdAt: nowIso, updatedAt: nowIso } }); return { id, datasetId: input.datasetId, datasetVersion: input.datasetVersion, agentVersion: input.agentVersion ?? null, targetType: input.targetType, targetId: input.targetId, name: input.name, description: input.description, metadata: input.metadata, status: "pending", totalItems: input.totalItems, succeededCount: 0, failedCount: 0, skippedCount: 0, startedAt: null, completedAt: null, createdAt: now, updatedAt: now }; } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "CREATE_EXPERIMENT", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async updateExperiment(input) { try { const existing = await this.getExperimentById({ id: input.id }); if (!existing) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "UPDATE_EXPERIMENT", "NOT_FOUND"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { experimentId: input.id } }); } const now = (/* @__PURE__ */ new Date()).toISOString(); const updates = ["updatedAt = ?"]; const values = [now]; if (input.status !== void 0) { updates.push("status = ?"); values.push(input.status); } if (input.succeededCount !== void 0) { updates.push("succeededCount = ?"); values.push(input.succeededCount); } if (input.failedCount !== void 0) { updates.push("failedCount = ?"); values.push(input.failedCount); } if (input.totalItems !== void 0) { updates.push("totalItems = ?"); values.push(input.totalItems); } if (input.startedAt !== void 0) { updates.push("startedAt = ?"); values.push(input.startedAt?.toISOString() ?? null); } if (input.completedAt !== void 0) { updates.push("completedAt = ?"); values.push(input.completedAt?.toISOString() ?? null); } if (input.skippedCount !== void 0) { updates.push("skippedCount = ?"); values.push(input.skippedCount); } if (input.name !== void 0) { updates.push("name = ?"); values.push(input.name); } if (input.description !== void 0) { updates.push("description = ?"); values.push(input.description); } if (input.metadata !== void 0) { updates.push("metadata = ?"); values.push(JSON.stringify(input.metadata)); } values.push(input.id); await this.#client.execute({ sql: `UPDATE ${storage.TABLE_EXPERIMENTS} SET ${updates.join(", ")} WHERE id = ?`, args: values }); const updated = await this.getExperimentById({ id: input.id }); return updated; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "UPDATE_EXPERIMENT", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async getExperimentById(args) { try { const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_EXPERIMENTS)} FROM ${storage.TABLE_EXPERIMENTS} WHERE id = ?`, args: [args.id] }); return result.rows?.[0] ? this.transformExperimentRow(result.rows[0]) : null; } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_EXPERIMENT", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async listExperiments(args) { try { const { page, perPage: perPageInput } = args.pagination; const conditions = []; const queryParams = []; if (args.datasetId) { conditions.push("datasetId = ?"); queryParams.push(args.datasetId); } if (args.targetType) { conditions.push("targetType = ?"); queryParams.push(args.targetType); } if (args.targetId) { conditions.push("targetId = ?"); queryParams.push(args.targetId); } if (args.agentVersion) { conditions.push("agentVersion = ?"); queryParams.push(args.agentVersion); } if (args.status) { conditions.push("status = ?"); queryParams.push(args.status); } const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : ""; const countResult = await this.#client.execute({ sql: `SELECT COUNT(*) as count FROM ${storage.TABLE_EXPERIMENTS} ${whereClause}`, args: queryParams }); const total = Number(countResult.rows?.[0]?.count ?? 0); if (total === 0) { return { experiments: [], pagination: { total: 0, page, perPage: perPageInput, hasMore: false } }; } const perPage = storage.normalizePerPage(perPageInput, 100); const { offset: start, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage); const limitValue = perPageInput === false ? total : perPage; const end = perPageInput === false ? total : start + perPage; const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_EXPERIMENTS)} FROM ${storage.TABLE_EXPERIMENTS} ${whereClause} ORDER BY createdAt DESC LIMIT ? OFFSET ?`, args: [...queryParams, limitValue, start] }); return { experiments: result.rows?.map((row) => this.transformExperimentRow(row)) ?? [], pagination: { total, page, perPage: perPageForResponse, hasMore: end < total } }; } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_EXPERIMENTS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async deleteExperiment(args) { try { await this.#client.execute({ sql: `DELETE FROM ${storage.TABLE_EXPERIMENT_RESULTS} WHERE experimentId = ?`, args: [args.id] }); await this.#client.execute({ sql: `DELETE FROM ${storage.TABLE_EXPERIMENTS} WHERE id = ?`, args: [args.id] }); } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "DELETE_EXPERIMENT", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } // Results (per-item) async addExperimentResult(input) { try { const id = input.id ?? crypto.randomUUID(); const now = /* @__PURE__ */ new Date(); const nowIso = now.toISOString(); await this.#db.insert({ tableName: storage.TABLE_EXPERIMENT_RESULTS, record: { id, experimentId: input.experimentId, itemId: input.itemId, itemDatasetVersion: input.itemDatasetVersion ?? null, input: input.input, output: input.output, groundTruth: input.groundTruth, error: input.error ?? null, startedAt: input.startedAt.toISOString(), completedAt: input.completedAt.toISOString(), retryCount: input.retryCount, traceId: input.traceId ?? null, status: input.status ?? null, tags: input.tags !== void 0 && input.tags !== null ? JSON.stringify(input.tags) : null, createdAt: nowIso } }); return { id, experimentId: input.experimentId, itemId: input.itemId, itemDatasetVersion: input.itemDatasetVersion, input: input.input, output: input.output, groundTruth: input.groundTruth, error: input.error, startedAt: input.startedAt, completedAt: input.completedAt, retryCount: input.retryCount, traceId: input.traceId ?? null, status: input.status ?? null, tags: input.tags ?? null, createdAt: now }; } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "ADD_EXPERIMENT_RESULT", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async updateExperimentResult(input) { try { const setClauses = []; const values = []; if (input.status !== void 0) { setClauses.push(`"status" = ?`); values.push(input.status); } if (input.tags !== void 0) { setClauses.push(`"tags" = ?`); values.push(JSON.stringify(input.tags)); } if (setClauses.length === 0) { const existing = await this.getExperimentResultById({ id: input.id }); if (!existing) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "UPDATE_EXPERIMENT_RESULT", "NOT_FOUND"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { resultId: input.id } }); } return existing; } values.push(input.id); let whereClause = `"id" = ?`; if (input.experimentId) { values.push(input.experimentId); whereClause += ` AND "experimentId" = ?`; } const updateResult = await this.#client.execute({ sql: `UPDATE ${storage.TABLE_EXPERIMENT_RESULTS} SET ${setClauses.join(", ")} WHERE ${whereClause}`, args: values }); if (updateResult.rowsAffected === 0) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "UPDATE_EXPERIMENT_RESULT", "NOT_FOUND"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { resultId: input.id, ...input.experimentId ? { experimentId: input.experimentId } : {} } }); } const result = await this.getExperimentResultById({ id: input.id }); if (!result) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "UPDATE_EXPERIMENT_RESULT", "NOT_FOUND"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { resultId: input.id } }); } return result; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "UPDATE_EXPERIMENT_RESULT", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async getExperimentResultById(args) { try { const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_EXPERIMENT_RESULTS)} FROM ${storage.TABLE_EXPERIMENT_RESULTS} WHERE id = ?`, args: [args.id] }); return result.rows?.[0] ? this.transformExperimentResultRow(result.rows[0]) : null; } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_EXPERIMENT_RESULT", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async listExperimentResults(args) { try { const { page, perPage: perPageInput } = args.pagination; const conditions = ["experimentId = ?"]; const queryParams = [args.experimentId]; if (args.traceId) { conditions.push("traceId = ?"); queryParams.push(args.traceId); } if (args.status) { conditions.push("status = ?"); queryParams.push(args.status); } const whereClause = `WHERE ${conditions.join(" AND ")}`; const countResult = await this.#client.execute({ sql: `SELECT COUNT(*) as count FROM ${storage.TABLE_EXPERIMENT_RESULTS} ${whereClause}`, args: queryParams }); const total = Number(countResult.rows?.[0]?.count ?? 0); if (total === 0) { return { results: [], pagination: { total: 0, page, perPage: perPageInput, hasMore: false } }; } const perPage = storage.normalizePerPage(perPageInput, 100); const { offset: start, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage); const limitValue = perPageInput === false ? total : perPage; const end = perPageInput === false ? total : start + perPage; const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_EXPERIMENT_RESULTS)} FROM ${storage.TABLE_EXPERIMENT_RESULTS} ${whereClause} ORDER BY startedAt ASC LIMIT ? OFFSET ?`, args: [...queryParams, limitValue, start] }); return { results: result.rows?.map((row) => this.transformExperimentResultRow(row)) ?? [], pagination: { total, page, perPage: perPageForResponse, hasMore: end < total } }; } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_EXPERIMENT_RESULTS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async deleteExperimentResults(args) { try { await this.#client.execute({ sql: `DELETE FROM ${storage.TABLE_EXPERIMENT_RESULTS} WHERE experimentId = ?`, args: [args.experimentId] }); } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "DELETE_EXPERIMENT_RESULTS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async getReviewSummary() { try { const result = await this.#client.execute({ sql: `SELECT "experimentId", COUNT(*) as total, SUM(CASE WHEN status = 'needs-review' THEN 1 ELSE 0 END) as "needsReview", SUM(CASE WHEN status = 'reviewed' THEN 1 ELSE 0 END) as reviewed, SUM(CASE WHEN status = 'complete' THEN 1 ELSE 0 END) as complete FROM ${storage.TABLE_EXPERIMENT_RESULTS} GROUP BY "experimentId"`, args: [] }); return (result.rows ?? []).map((row) => ({ experimentId: row.experimentId, total: Number(row.total ?? 0), needsReview: Number(row.needsReview ?? 0), reviewed: Number(row.reviewed ?? 0), complete: Number(row.complete ?? 0) })); } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_REVIEW_SUMMARY", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } }; var ENTITY_TABLE = { agent: storage.TABLE_AGENTS, skill: storage.TABLE_SKILLS }; var FavoritesLibSQL = class extends storage.FavoritesStorage { #db; #client; constructor(config) { super(); const client = resolveClient(config); this.#client = client; this.#db = new LibSQLDB({ client, maxRetries: config.maxRetries, initialBackoffMs: config.initialBackoffMs }); } async init() { await this.#db.createTable({ tableName: storage.TABLE_FAVORITES, schema: storage.FAVORITES_SCHEMA, compositePrimaryKey: ["userId", "entityType", "entityId"] }); await this.#client.execute( `CREATE INDEX IF NOT EXISTS idx_favorites_entity ON "${storage.TABLE_FAVORITES}" ("entityType", "entityId")` ); } async dangerouslyClearAll() { const tx = await this.#client.transaction("write"); try { await tx.execute(`DELETE FROM "${storage.TABLE_FAVORITES}"`); await tx.execute(`UPDATE "${storage.TABLE_AGENTS}" SET "favoriteCount" = 0 WHERE "favoriteCount" > 0`); await tx.execute(`UPDATE "${storage.TABLE_SKILLS}" SET "favoriteCount" = 0 WHERE "favoriteCount" > 0`); await tx.commit(); } catch (error) { if (!tx.closed) { await tx.rollback(); } throw error; } } async favorite(input) { const { userId, entityType, entityId } = input; const entityTable = ENTITY_TABLE[entityType]; try { const tx = await this.#client.transaction("write"); try { const entityRow = await tx.execute({ sql: `SELECT "favoriteCount" FROM "${entityTable}" WHERE id = ?`, args: [entityId] }); if (!entityRow.rows?.[0]) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "FAVORITE", "ENTITY_NOT_FOUND"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, text: `${entityType} ${entityId} not found`, details: { entityType, entityId } }); } const inserted = await tx.execute({ sql: `INSERT OR IGNORE INTO "${storage.TABLE_FAVORITES}" ("userId", "entityType", "entityId", "createdAt") VALUES (?, ?, ?, ?)`, args: [userId, entityType, entityId, (/* @__PURE__ */ new Date()).toISOString()] }); if ((inserted.rowsAffected ?? 0) > 0) { await tx.execute({ sql: `UPDATE "${entityTable}" SET "favoriteCount" = COALESCE("favoriteCount", 0) + 1 WHERE id = ?`, args: [entityId] }); } const after = await tx.execute({ sql: `SELECT "favoriteCount" FROM "${entityTable}" WHERE id = ?`, args: [entityId] }); const favoriteCount = Number(after.rows?.[0]?.favoriteCount ?? 0); await tx.commit(); return { favorited: true, favoriteCount }; } catch (error) { if (!tx.closed) { await tx.rollback(); } throw error; } } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "FAVORITE", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { entityType, entityId } }, error$1 ); } } async unfavorite(input) { const { userId, entityType, entityId } = input; const entityTable = ENTITY_TABLE[entityType]; try { const tx = await this.#client.transaction("write"); try { const entityRow = await tx.execute({ sql: `SELECT "favoriteCount" FROM "${entityTable}" WHERE id = ?`, args: [entityId] }); if (!entityRow.rows?.[0]) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "UNFAVORITE", "ENTITY_NOT_FOUND"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, text: `${entityType} ${entityId} not found`, details: { entityType, entityId } }); } const deleted = await tx.execute({ sql: `DELETE FROM "${storage.TABLE_FAVORITES}" WHERE "userId" = ? AND "entityType" = ? AND "entityId" = ?`, args: [userId, entityType, entityId] }); if ((deleted.rowsAffected ?? 0) > 0) { await tx.execute({ sql: `UPDATE "${entityTable}" SET "favoriteCount" = MAX(COALESCE("favoriteCount", 0) - 1, 0) WHERE id = ?`, args: [entityId] }); } const after = await tx.execute({ sql: `SELECT "favoriteCount" FROM "${entityTable}" WHERE id = ?`, args: [entityId] }); const favoriteCount = Number(after.rows?.[0]?.favoriteCount ?? 0); await tx.commit(); return { favorited: false, favoriteCount }; } catch (error) { if (!tx.closed) { await tx.rollback(); } throw error; } } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "UNFAVORITE", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { entityType, entityId } }, error$1 ); } } async isFavorited(input) { try { const result = await this.#client.execute({ sql: `SELECT 1 FROM "${storage.TABLE_FAVORITES}" WHERE "userId" = ? AND "entityType" = ? AND "entityId" = ? LIMIT 1`, args: [input.userId, input.entityType, input.entityId] }); return (result.rows?.length ?? 0) > 0; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "IS_FAVORITED", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async isFavoritedBatch(input) { const { userId, entityType, entityIds } = input; if (entityIds.length === 0) { return /* @__PURE__ */ new Set(); } try { const placeholders = entityIds.map(() => "?").join(", "); const args = [userId, entityType, ...entityIds]; const result = await this.#client.execute({ sql: `SELECT "entityId" FROM "${storage.TABLE_FAVORITES}" WHERE "userId" = ? AND "entityType" = ? AND "entityId" IN (${placeholders})`, args }); const set = /* @__PURE__ */ new Set(); for (const row of result.rows ?? []) { set.add(row.entityId); } return set; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "IS_FAVORITED_BATCH", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async listFavoritedIds(input) { try { const result = await this.#client.execute({ sql: `SELECT "entityId" FROM "${storage.TABLE_FAVORITES}" WHERE "userId" = ? AND "entityType" = ? ORDER BY "createdAt" DESC, "entityId" ASC`, args: [input.userId, input.entityType] }); return (result.rows ?? []).map((row) => row.entityId); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_FAVORITED_IDS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async deleteFavoritesForEntity(input) { const entityTable = ENTITY_TABLE[input.entityType]; try { const tx = await this.#client.transaction("write"); try { const result = await tx.execute({ sql: `DELETE FROM "${storage.TABLE_FAVORITES}" WHERE "entityType" = ? AND "entityId" = ?`, args: [input.entityType, input.entityId] }); await tx.execute({ sql: `UPDATE "${entityTable}" SET "favoriteCount" = 0 WHERE id = ?`, args: [input.entityId] }); await tx.commit(); return Number(result.rowsAffected ?? 0); } catch (txError) { if (!tx.closed) { await tx.rollback(); } throw txError; } } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "DELETE_FAVORITES_FOR_ENTITY", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { entityType: input.entityType, entityId: input.entityId } }, error$1 ); } } }; var toDate = (value) => new Date(value); var toOptionalDate = (value) => { if (value == null) return void 0; return toDate(value); }; function cloneJson(value) { return value === void 0 ? void 0 : structuredClone(value); } function hydratePendingItem(item) { const record = { id: item.id, kind: item.kind, status: item.status, sessionId: item.sessionId, createdAt: toDate(item.createdAt), updatedAt: toDate(item.updatedAt) }; if (item.runId !== void 0) record.runId = item.runId; if (item.traceId !== void 0) record.traceId = item.traceId; if (item.runtimeCompatibilityGeneration !== void 0) { record.runtimeCompatibilityGeneration = item.runtimeCompatibilityGeneration; } if (item.payload !== void 0) record.payload = cloneJson(item.payload); if (item.response !== void 0) record.response = cloneJson(item.response); return record; } function rowToSession(row) { const record = { id: row.id, ownerId: row.ownerId, resourceId: row.resourceId, threadId: row.threadId, origin: row.origin, modeId: row.modeId, modelId: row.modelId, createdAt: toDate(row.createdAt), lastActivityAt: toDate(row.lastActivityAt) }; if (row.parentSessionId != null) record.parentSessionId = row.parentSessionId; if (row.subagentDepth != null) record.subagentDepth = row.subagentDepth; if (row.source != null) record.source = { ...row.source, type: row.source.type }; if (row.runtimeCompatibilityGeneration != null) { record.runtimeCompatibilityGeneration = row.runtimeCompatibilityGeneration; } if (row.title != null) record.title = row.title; if (row.metadata != null) record.metadata = cloneJson(row.metadata); if (row.state != null) record.state = cloneJson(row.state); if (row.pending != null) record.pending = row.pending.map(hydratePendingItem); const closingAt = toOptionalDate(row.closingAt); const closeDeadlineAt = toOptionalDate(row.closeDeadlineAt); const closedAt = toOptionalDate(row.closedAt); const deletedAt = toOptionalDate(row.deletedAt); if (closingAt) record.closingAt = closingAt; if (closeDeadlineAt) record.closeDeadlineAt = closeDeadlineAt; if (closedAt) record.closedAt = closedAt; if (deletedAt) record.deletedAt = deletedAt; return record; } function sessionToRecord(record) { return { id: record.id, ownerId: record.ownerId, resourceId: record.resourceId, threadId: record.threadId, parentSessionId: record.parentSessionId ?? null, subagentDepth: record.subagentDepth ?? null, source: record.source ?? null, origin: record.origin, runtimeCompatibilityGeneration: record.runtimeCompatibilityGeneration ?? null, modeId: record.modeId, modelId: record.modelId, title: record.title ?? null, metadata: record.metadata ?? null, state: record.state ?? null, pending: record.pending ?? null, createdAt: record.createdAt, lastActivityAt: record.lastActivityAt, closingAt: record.closingAt ?? null, closeDeadlineAt: record.closeDeadlineAt ?? null, closedAt: record.closedAt ?? null, deletedAt: record.deletedAt ?? null }; } var HarnessLibSQL = class extends storage.HarnessStorage { #db; constructor(config) { super(); const client = resolveClient(config); this.#db = new LibSQLDB({ client, maxRetries: config.maxRetries, initialBackoffMs: config.initialBackoffMs }); } async init() { await this.#db.createTable({ tableName: storage.TABLE_HARNESS_SESSIONS, schema: storage.TABLE_SCHEMAS[storage.TABLE_HARNESS_SESSIONS] }); } async dangerouslyClearAll() { await this.#db.deleteData({ tableName: storage.TABLE_HARNESS_SESSIONS }); } async loadSession(sessionId) { const row = await this.#db.select({ tableName: storage.TABLE_HARNESS_SESSIONS, keys: { id: sessionId } }); return row ? rowToSession(row) : null; } async saveSession(record) { await this.#db.insert({ tableName: storage.TABLE_HARNESS_SESSIONS, record: sessionToRecord(record) }); } async listSessions() { const rows = await this.#db.selectMany({ tableName: storage.TABLE_HARNESS_SESSIONS, orderBy: '"lastActivityAt" DESC' }); return rows.map(rowToSession); } }; var MCPClientsLibSQL = class extends storage.MCPClientsStorage { #db; #client; constructor(config) { super(); const client = resolveClient(config); this.#client = client; this.#db = new LibSQLDB({ client, maxRetries: config.maxRetries, initialBackoffMs: config.initialBackoffMs }); } async init() { await this.#db.createTable({ tableName: storage.TABLE_MCP_CLIENTS, schema: storage.MCP_CLIENTS_SCHEMA }); await this.#db.createTable({ tableName: storage.TABLE_MCP_CLIENT_VERSIONS, schema: storage.MCP_CLIENT_VERSIONS_SCHEMA }); await this.#client.execute( `CREATE UNIQUE INDEX IF NOT EXISTS idx_mcp_client_versions_client_version ON "${storage.TABLE_MCP_CLIENT_VERSIONS}" ("mcpClientId", "versionNumber")` ); } async dangerouslyClearAll() { await this.#db.deleteData({ tableName: storage.TABLE_MCP_CLIENTS }); await this.#db.deleteData({ tableName: storage.TABLE_MCP_CLIENT_VERSIONS }); } // ========================================================================== // MCP Client CRUD // ========================================================================== async getById(id) { try { const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_MCP_CLIENTS)} FROM "${storage.TABLE_MCP_CLIENTS}" WHERE id = ?`, args: [id] }); const row = result.rows?.[0]; return row ? this.#parseMCPClientRow(row) : null; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_MCP_CLIENT", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async create(input) { const { mcpClient } = input; try { const now = /* @__PURE__ */ new Date(); await this.#db.insert({ tableName: storage.TABLE_MCP_CLIENTS, record: { id: mcpClient.id, status: "draft", activeVersionId: null, authorId: mcpClient.authorId ?? null, metadata: mcpClient.metadata ?? null, createdAt: now.toISOString(), updatedAt: now.toISOString() } }); const { id: _id, authorId: _authorId, metadata: _metadata, ...snapshotConfig } = mcpClient; const versionId = crypto.randomUUID(); try { await this.createVersion({ id: versionId, mcpClientId: mcpClient.id, versionNumber: 1, ...snapshotConfig, changedFields: Object.keys(snapshotConfig), changeMessage: "Initial version" }); } catch (versionError) { await this.#db.delete({ tableName: storage.TABLE_MCP_CLIENTS, keys: { id: mcpClient.id } }); throw versionError; } return { id: mcpClient.id, status: "draft", activeVersionId: void 0, authorId: mcpClient.authorId, metadata: mcpClient.metadata, createdAt: now, updatedAt: now }; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "CREATE_MCP_CLIENT", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async update(input) { const { id, ...updates } = input; try { const existing = await this.getById(id); if (!existing) { throw new Error(`MCP client with id ${id} not found`); } const { authorId, activeVersionId, metadata, status } = updates; const updateData = { updatedAt: (/* @__PURE__ */ new Date()).toISOString() }; if (authorId !== void 0) updateData.authorId = authorId; if (activeVersionId !== void 0) updateData.activeVersionId = activeVersionId; if (status !== void 0) updateData.status = status; if (metadata !== void 0) { updateData.metadata = { ...existing.metadata, ...metadata }; } await this.#db.update({ tableName: storage.TABLE_MCP_CLIENTS, keys: { id }, data: updateData }); const updated = await this.getById(id); if (!updated) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "UPDATE_MCP_CLIENT", "NOT_FOUND_AFTER_UPDATE"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.SYSTEM, text: `MCP client ${id} not found after update`, details: { id } }); } return updated; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "UPDATE_MCP_CLIENT", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async delete(id) { try { await this.deleteVersionsByParentId(id); await this.#client.execute({ sql: `DELETE FROM "${storage.TABLE_MCP_CLIENTS}" WHERE "id" = ?`, args: [id] }); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "DELETE_MCP_CLIENT", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async list(args) { try { const { page = 0, perPage: perPageInput, orderBy, authorId, metadata, status = "published" } = args || {}; const { field, direction } = this.parseOrderBy(orderBy); const conditions = []; const queryParams = []; conditions.push("status = ?"); queryParams.push(status); if (authorId !== void 0) { conditions.push("authorId = ?"); queryParams.push(authorId); } if (metadata && Object.keys(metadata).length > 0) { for (const [key, value] of Object.entries(metadata)) { if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key)) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "LIST_MCP_CLIENTS", "INVALID_METADATA_KEY"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, text: `Invalid metadata key: ${key}. Keys must be alphanumeric with underscores.`, details: { key } }); } conditions.push(`json_extract(metadata, '$.${key}') = ?`); queryParams.push(typeof value === "string" ? value : JSON.stringify(value)); } } const whereClause = `WHERE ${conditions.join(" AND ")}`; const countResult = await this.#client.execute({ sql: `SELECT COUNT(*) as count FROM "${storage.TABLE_MCP_CLIENTS}" ${whereClause}`, args: queryParams }); const total = Number(countResult.rows?.[0]?.count ?? 0); if (total === 0) { return { mcpClients: [], total: 0, page, perPage: perPageInput ?? 100, hasMore: false }; } const perPage = storage.normalizePerPage(perPageInput, 100); const { offset: start, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage); const limitValue = perPageInput === false ? total : perPage; const end = perPageInput === false ? total : start + perPage; const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_MCP_CLIENTS)} FROM "${storage.TABLE_MCP_CLIENTS}" ${whereClause} ORDER BY ${field} ${direction} LIMIT ? OFFSET ?`, args: [...queryParams, limitValue, start] }); const mcpClients = result.rows?.map((row) => this.#parseMCPClientRow(row)) ?? []; return { mcpClients, total, page, perPage: perPageForResponse, hasMore: end < total }; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_MCP_CLIENTS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } // ========================================================================== // MCP Client Version Methods // ========================================================================== async createVersion(input) { try { const now = /* @__PURE__ */ new Date(); await this.#db.insert({ tableName: storage.TABLE_MCP_CLIENT_VERSIONS, record: { id: input.id, mcpClientId: input.mcpClientId, versionNumber: input.versionNumber, name: input.name, description: input.description ?? null, servers: input.servers ?? null, changedFields: input.changedFields ?? null, changeMessage: input.changeMessage ?? null, createdAt: now.toISOString() } }); return { ...input, createdAt: now }; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "CREATE_MCP_CLIENT_VERSION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async getVersion(id) { try { const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_MCP_CLIENT_VERSIONS)} FROM "${storage.TABLE_MCP_CLIENT_VERSIONS}" WHERE id = ?`, args: [id] }); const row = result.rows?.[0]; return row ? this.#parseVersionRow(row) : null; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_MCP_CLIENT_VERSION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async getVersionByNumber(mcpClientId, versionNumber) { try { const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_MCP_CLIENT_VERSIONS)} FROM "${storage.TABLE_MCP_CLIENT_VERSIONS}" WHERE mcpClientId = ? AND versionNumber = ?`, args: [mcpClientId, versionNumber] }); const row = result.rows?.[0]; return row ? this.#parseVersionRow(row) : null; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_MCP_CLIENT_VERSION_BY_NUMBER", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async getLatestVersion(mcpClientId) { try { const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_MCP_CLIENT_VERSIONS)} FROM "${storage.TABLE_MCP_CLIENT_VERSIONS}" WHERE mcpClientId = ? ORDER BY versionNumber DESC LIMIT 1`, args: [mcpClientId] }); const row = result.rows?.[0]; return row ? this.#parseVersionRow(row) : null; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_LATEST_MCP_CLIENT_VERSION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async listVersions(input) { try { const { mcpClientId, page = 0, perPage: perPageInput, orderBy } = input; const { field, direction } = this.parseVersionOrderBy(orderBy); const countResult = await this.#client.execute({ sql: `SELECT COUNT(*) as count FROM "${storage.TABLE_MCP_CLIENT_VERSIONS}" WHERE mcpClientId = ?`, args: [mcpClientId] }); const total = Number(countResult.rows?.[0]?.count ?? 0); if (total === 0) { return { versions: [], total: 0, page, perPage: perPageInput ?? 20, hasMore: false }; } const perPage = storage.normalizePerPage(perPageInput, 20); const { offset: start, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage); const limitValue = perPageInput === false ? total : perPage; const end = perPageInput === false ? total : start + perPage; const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_MCP_CLIENT_VERSIONS)} FROM "${storage.TABLE_MCP_CLIENT_VERSIONS}" WHERE mcpClientId = ? ORDER BY ${field} ${direction} LIMIT ? OFFSET ?`, args: [mcpClientId, limitValue, start] }); const versions = result.rows?.map((row) => this.#parseVersionRow(row)) ?? []; return { versions, total, page, perPage: perPageForResponse, hasMore: end < total }; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_MCP_CLIENT_VERSIONS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async deleteVersion(id) { try { await this.#client.execute({ sql: `DELETE FROM "${storage.TABLE_MCP_CLIENT_VERSIONS}" WHERE "id" = ?`, args: [id] }); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "DELETE_MCP_CLIENT_VERSION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async deleteVersionsByParentId(entityId) { try { await this.#client.execute({ sql: `DELETE FROM "${storage.TABLE_MCP_CLIENT_VERSIONS}" WHERE "mcpClientId" = ?`, args: [entityId] }); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "DELETE_MCP_CLIENT_VERSIONS_BY_CLIENT", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async countVersions(mcpClientId) { try { const result = await this.#client.execute({ sql: `SELECT COUNT(*) as count FROM "${storage.TABLE_MCP_CLIENT_VERSIONS}" WHERE mcpClientId = ?`, args: [mcpClientId] }); return Number(result.rows?.[0]?.count ?? 0); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "COUNT_MCP_CLIENT_VERSIONS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } // ========================================================================== // Private Helpers // ========================================================================== #parseMCPClientRow(row) { const safeParseJSON = (val) => { if (val === null || val === void 0) return void 0; if (typeof val === "string") { try { return JSON.parse(val); } catch { return val; } } return val; }; return { id: row.id, status: row.status ?? "draft", activeVersionId: row.activeVersionId ?? void 0, authorId: row.authorId ?? void 0, metadata: safeParseJSON(row.metadata), createdAt: new Date(row.createdAt), updatedAt: new Date(row.updatedAt) }; } #parseVersionRow(row) { const safeParseJSON = (val) => { if (val === null || val === void 0) return void 0; if (typeof val === "string") { try { return JSON.parse(val); } catch { return val; } } return val; }; return { id: row.id, mcpClientId: row.mcpClientId, versionNumber: Number(row.versionNumber), name: row.name, description: row.description ?? void 0, servers: safeParseJSON(row.servers), changedFields: safeParseJSON(row.changedFields), changeMessage: row.changeMessage ?? void 0, createdAt: new Date(row.createdAt) }; } }; var MCPServersLibSQL = class extends storage.MCPServersStorage { #db; #client; constructor(config) { super(); const client = resolveClient(config); this.#client = client; this.#db = new LibSQLDB({ client, maxRetries: config.maxRetries, initialBackoffMs: config.initialBackoffMs }); } async init() { await this.#db.createTable({ tableName: storage.TABLE_MCP_SERVERS, schema: storage.MCP_SERVERS_SCHEMA }); await this.#db.createTable({ tableName: storage.TABLE_MCP_SERVER_VERSIONS, schema: storage.MCP_SERVER_VERSIONS_SCHEMA }); await this.#client.execute( `CREATE UNIQUE INDEX IF NOT EXISTS idx_mcp_server_versions_server_version ON "${storage.TABLE_MCP_SERVER_VERSIONS}" ("mcpServerId", "versionNumber")` ); } async dangerouslyClearAll() { await this.#db.deleteData({ tableName: storage.TABLE_MCP_SERVERS }); await this.#db.deleteData({ tableName: storage.TABLE_MCP_SERVER_VERSIONS }); } // ========================================================================== // MCP Server CRUD // ========================================================================== async getById(id) { try { const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_MCP_SERVERS)} FROM "${storage.TABLE_MCP_SERVERS}" WHERE id = ?`, args: [id] }); const row = result.rows?.[0]; return row ? this.#parseMCPServerRow(row) : null; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_MCP_SERVER", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async create(input) { const { mcpServer } = input; try { const now = /* @__PURE__ */ new Date(); await this.#db.insert({ tableName: storage.TABLE_MCP_SERVERS, record: { id: mcpServer.id, status: "draft", activeVersionId: null, authorId: mcpServer.authorId ?? null, metadata: mcpServer.metadata ?? null, createdAt: now.toISOString(), updatedAt: now.toISOString() } }); const { id: _id, authorId: _authorId, metadata: _metadata, ...snapshotConfig } = mcpServer; const versionId = crypto.randomUUID(); try { await this.createVersion({ id: versionId, mcpServerId: mcpServer.id, versionNumber: 1, ...snapshotConfig, changedFields: Object.keys(snapshotConfig), changeMessage: "Initial version" }); } catch (versionError) { await this.#db.delete({ tableName: storage.TABLE_MCP_SERVERS, keys: { id: mcpServer.id } }); throw versionError; } return { id: mcpServer.id, status: "draft", activeVersionId: void 0, authorId: mcpServer.authorId, metadata: mcpServer.metadata, createdAt: now, updatedAt: now }; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "CREATE_MCP_SERVER", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async update(input) { const { id, ...updates } = input; try { const existing = await this.getById(id); if (!existing) { throw new Error(`MCP server with id ${id} not found`); } const { authorId, activeVersionId, metadata, status } = updates; const updateData = { updatedAt: (/* @__PURE__ */ new Date()).toISOString() }; if (authorId !== void 0) updateData.authorId = authorId; if (activeVersionId !== void 0) updateData.activeVersionId = activeVersionId; if (status !== void 0) updateData.status = status; if (metadata !== void 0) { updateData.metadata = { ...existing.metadata, ...metadata }; } await this.#db.update({ tableName: storage.TABLE_MCP_SERVERS, keys: { id }, data: updateData }); const updated = await this.getById(id); if (!updated) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "UPDATE_MCP_SERVER", "NOT_FOUND_AFTER_UPDATE"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.SYSTEM, text: `MCP server ${id} not found after update`, details: { id } }); } return updated; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "UPDATE_MCP_SERVER", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async delete(id) { try { await this.deleteVersionsByParentId(id); await this.#client.execute({ sql: `DELETE FROM "${storage.TABLE_MCP_SERVERS}" WHERE "id" = ?`, args: [id] }); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "DELETE_MCP_SERVER", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async list(args) { try { const { page = 0, perPage: perPageInput, orderBy, authorId, metadata, status = "published" } = args || {}; const { field, direction } = this.parseOrderBy(orderBy); const conditions = []; const queryParams = []; conditions.push("status = ?"); queryParams.push(status); if (authorId !== void 0) { conditions.push("authorId = ?"); queryParams.push(authorId); } if (metadata && Object.keys(metadata).length > 0) { for (const [key, value] of Object.entries(metadata)) { if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key)) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "LIST_MCP_SERVERS", "INVALID_METADATA_KEY"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, text: `Invalid metadata key: ${key}. Keys must be alphanumeric with underscores.`, details: { key } }); } conditions.push(`json_extract(metadata, '$.${key}') = ?`); queryParams.push(typeof value === "string" ? value : JSON.stringify(value)); } } const whereClause = `WHERE ${conditions.join(" AND ")}`; const countResult = await this.#client.execute({ sql: `SELECT COUNT(*) as count FROM "${storage.TABLE_MCP_SERVERS}" ${whereClause}`, args: queryParams }); const total = Number(countResult.rows?.[0]?.count ?? 0); if (total === 0) { return { mcpServers: [], total: 0, page, perPage: perPageInput ?? 100, hasMore: false }; } const perPage = storage.normalizePerPage(perPageInput, 100); const { offset: start, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage); const limitValue = perPageInput === false ? total : perPage; const end = perPageInput === false ? total : start + perPage; const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_MCP_SERVERS)} FROM "${storage.TABLE_MCP_SERVERS}" ${whereClause} ORDER BY ${field} ${direction} LIMIT ? OFFSET ?`, args: [...queryParams, limitValue, start] }); const mcpServers = result.rows?.map((row) => this.#parseMCPServerRow(row)) ?? []; return { mcpServers, total, page, perPage: perPageForResponse, hasMore: end < total }; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_MCP_SERVERS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } // ========================================================================== // MCP Server Version Methods // ========================================================================== async createVersion(input) { try { const now = /* @__PURE__ */ new Date(); await this.#db.insert({ tableName: storage.TABLE_MCP_SERVER_VERSIONS, record: { id: input.id, mcpServerId: input.mcpServerId, versionNumber: input.versionNumber, name: input.name, version: input.version, description: input.description ?? null, instructions: input.instructions ?? null, repository: input.repository ?? null, releaseDate: input.releaseDate ?? null, isLatest: input.isLatest ?? null, packageCanonical: input.packageCanonical ?? null, tools: input.tools ?? null, agents: input.agents ?? null, workflows: input.workflows ?? null, changedFields: input.changedFields ?? null, changeMessage: input.changeMessage ?? null, createdAt: now.toISOString() } }); return { ...input, createdAt: now }; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "CREATE_MCP_SERVER_VERSION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async getVersion(id) { try { const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_MCP_SERVER_VERSIONS)} FROM "${storage.TABLE_MCP_SERVER_VERSIONS}" WHERE id = ?`, args: [id] }); const row = result.rows?.[0]; return row ? this.#parseVersionRow(row) : null; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_MCP_SERVER_VERSION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async getVersionByNumber(mcpServerId, versionNumber) { try { const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_MCP_SERVER_VERSIONS)} FROM "${storage.TABLE_MCP_SERVER_VERSIONS}" WHERE mcpServerId = ? AND versionNumber = ?`, args: [mcpServerId, versionNumber] }); const row = result.rows?.[0]; return row ? this.#parseVersionRow(row) : null; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_MCP_SERVER_VERSION_BY_NUMBER", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async getLatestVersion(mcpServerId) { try { const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_MCP_SERVER_VERSIONS)} FROM "${storage.TABLE_MCP_SERVER_VERSIONS}" WHERE mcpServerId = ? ORDER BY versionNumber DESC LIMIT 1`, args: [mcpServerId] }); const row = result.rows?.[0]; return row ? this.#parseVersionRow(row) : null; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_LATEST_MCP_SERVER_VERSION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async listVersions(input) { try { const { mcpServerId, page = 0, perPage: perPageInput, orderBy } = input; const { field, direction } = this.parseVersionOrderBy(orderBy); const countResult = await this.#client.execute({ sql: `SELECT COUNT(*) as count FROM "${storage.TABLE_MCP_SERVER_VERSIONS}" WHERE mcpServerId = ?`, args: [mcpServerId] }); const total = Number(countResult.rows?.[0]?.count ?? 0); if (total === 0) { return { versions: [], total: 0, page, perPage: perPageInput ?? 20, hasMore: false }; } const perPage = storage.normalizePerPage(perPageInput, 20); const { offset: start, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage); const limitValue = perPageInput === false ? total : perPage; const end = perPageInput === false ? total : start + perPage; const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_MCP_SERVER_VERSIONS)} FROM "${storage.TABLE_MCP_SERVER_VERSIONS}" WHERE mcpServerId = ? ORDER BY ${field} ${direction} LIMIT ? OFFSET ?`, args: [mcpServerId, limitValue, start] }); const versions = result.rows?.map((row) => this.#parseVersionRow(row)) ?? []; return { versions, total, page, perPage: perPageForResponse, hasMore: end < total }; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_MCP_SERVER_VERSIONS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async deleteVersion(id) { try { await this.#client.execute({ sql: `DELETE FROM "${storage.TABLE_MCP_SERVER_VERSIONS}" WHERE "id" = ?`, args: [id] }); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "DELETE_MCP_SERVER_VERSION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async deleteVersionsByParentId(entityId) { try { await this.#client.execute({ sql: `DELETE FROM "${storage.TABLE_MCP_SERVER_VERSIONS}" WHERE "mcpServerId" = ?`, args: [entityId] }); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "DELETE_MCP_SERVER_VERSIONS_BY_SERVER", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async countVersions(mcpServerId) { try { const result = await this.#client.execute({ sql: `SELECT COUNT(*) as count FROM "${storage.TABLE_MCP_SERVER_VERSIONS}" WHERE mcpServerId = ?`, args: [mcpServerId] }); return Number(result.rows?.[0]?.count ?? 0); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "COUNT_MCP_SERVER_VERSIONS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } // ========================================================================== // Private Helpers // ========================================================================== #safeParseJSON(val) { if (val === null || val === void 0) return void 0; if (typeof val === "string") { try { return JSON.parse(val); } catch { return val; } } return val; } #parseMCPServerRow(row) { return { id: row.id, status: row.status ?? "draft", activeVersionId: row.activeVersionId ?? void 0, authorId: row.authorId ?? void 0, metadata: this.#safeParseJSON(row.metadata), createdAt: new Date(row.createdAt), updatedAt: new Date(row.updatedAt) }; } #parseVersionRow(row) { return { id: row.id, mcpServerId: row.mcpServerId, versionNumber: Number(row.versionNumber), name: row.name, version: row.version, description: row.description ?? void 0, instructions: row.instructions ?? void 0, repository: this.#safeParseJSON(row.repository), releaseDate: row.releaseDate ?? void 0, isLatest: row.isLatest === null || row.isLatest === void 0 ? void 0 : Boolean(row.isLatest), packageCanonical: row.packageCanonical ?? void 0, tools: this.#safeParseJSON(row.tools), agents: this.#safeParseJSON(row.agents), workflows: this.#safeParseJSON(row.workflows), changedFields: this.#safeParseJSON(row.changedFields), changeMessage: row.changeMessage ?? void 0, createdAt: new Date(row.createdAt) }; } }; var OM_TABLE = "mastra_observational_memory"; var MemoryLibSQL = class extends storage.MemoryStorage { supportsObservationalMemory = true; #client; #db; constructor(config) { super(); const client = resolveClient(config); this.#client = client; this.#db = new LibSQLDB({ client, maxRetries: config.maxRetries, initialBackoffMs: config.initialBackoffMs }); } async init() { await this.#db.createTable({ tableName: storage.TABLE_THREADS, schema: storage.TABLE_SCHEMAS[storage.TABLE_THREADS] }); await this.#db.createTable({ tableName: storage.TABLE_MESSAGES, schema: storage.TABLE_SCHEMAS[storage.TABLE_MESSAGES] }); await this.#db.createTable({ tableName: storage.TABLE_RESOURCES, schema: storage.TABLE_SCHEMAS[storage.TABLE_RESOURCES] }); let omSchema; try { const { OBSERVATIONAL_MEMORY_TABLE_SCHEMA } = await import('@mastra/core/storage'); omSchema = OBSERVATIONAL_MEMORY_TABLE_SCHEMA?.[OM_TABLE]; } catch { } if (omSchema) { await this.#db.createTable({ tableName: OM_TABLE, schema: omSchema }); await this.#db.alterTable({ tableName: OM_TABLE, schema: omSchema, ifNotExists: [ "observedMessageIds", "observedTimezone", "bufferedObservations", "bufferedObservationTokens", "bufferedMessageIds", "bufferedReflection", "bufferedReflectionTokens", "bufferedReflectionInputTokens", "reflectedObservationLineCount", "bufferedObservationChunks", "isBufferingObservation", "isBufferingReflection", "lastBufferedAtTokens", "lastBufferedAtTime", "metadata" ] }); } await this.#db.alterTable({ tableName: storage.TABLE_MESSAGES, schema: storage.TABLE_SCHEMAS[storage.TABLE_MESSAGES], ifNotExists: ["resourceId"] }); await this.#client.batch( [ { sql: `CREATE INDEX IF NOT EXISTS idx_messages_thread_created_at ON ${storage.TABLE_MESSAGES} (thread_id, "createdAt")`, args: [] }, { sql: `CREATE INDEX IF NOT EXISTS idx_messages_thread_resource_created_at ON ${storage.TABLE_MESSAGES} (thread_id, "resourceId", "createdAt")`, args: [] } ], "write" ); if (omSchema) { await this.#client.execute({ sql: `CREATE INDEX IF NOT EXISTS idx_om_lookup_key ON "${OM_TABLE}" ("lookupKey")`, args: [] }); } } async dangerouslyClearAll() { await this.init(); await this.#db.deleteData({ tableName: storage.TABLE_MESSAGES }); await this.#db.deleteData({ tableName: storage.TABLE_THREADS }); await this.#db.deleteData({ tableName: storage.TABLE_RESOURCES }); { await this.#db.deleteData({ tableName: OM_TABLE }); } } parseRow(row) { let content = row.content; try { content = JSON.parse(row.content); } catch { } const result = { id: row.id, content, role: row.role, createdAt: new Date(row.createdAt), threadId: row.thread_id, resourceId: row.resourceId }; if (row.type && row.type !== `v2`) result.type = row.type; return result; } _sortMessages(messages, field, direction) { return messages.sort((a, b) => { const isDateField = field === "createdAt" || field === "updatedAt"; const aValue = isDateField ? new Date(a[field]).getTime() : a[field]; const bValue = isDateField ? new Date(b[field]).getTime() : b[field]; if (typeof aValue === "number" && typeof bValue === "number") { return direction === "ASC" ? aValue - bValue : bValue - aValue; } return direction === "ASC" ? String(aValue).localeCompare(String(bValue)) : String(bValue).localeCompare(String(aValue)); }); } async _getIncludedMessages({ include }) { if (!include || include.length === 0) return null; const targetIds = include.map((inc) => inc.id).filter(Boolean); if (targetIds.length === 0) return null; const idPlaceholders = targetIds.map(() => "?").join(", "); const targetResult = await this.#client.execute({ sql: `SELECT id, thread_id, "createdAt" FROM "${storage.TABLE_MESSAGES}" WHERE id IN (${idPlaceholders})`, args: targetIds }); if (!targetResult.rows || targetResult.rows.length === 0) return null; const targetMap = new Map( targetResult.rows.map((r) => [r.id, { threadId: r.thread_id, createdAt: r.createdAt }]) ); const unionQueries = []; const params = []; for (const inc of include) { const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc; const target = targetMap.get(id); if (!target) continue; unionQueries.push(`SELECT * FROM ( SELECT id, content, role, type, "createdAt", thread_id, "resourceId" FROM "${storage.TABLE_MESSAGES}" WHERE thread_id = ? AND "createdAt" <= ? ORDER BY "createdAt" DESC, id DESC LIMIT ? )`); params.push(target.threadId, target.createdAt, withPreviousMessages + 1); if (withNextMessages > 0) { unionQueries.push(`SELECT * FROM ( SELECT id, content, role, type, "createdAt", thread_id, "resourceId" FROM "${storage.TABLE_MESSAGES}" WHERE thread_id = ? AND "createdAt" > ? ORDER BY "createdAt" ASC, id ASC LIMIT ? )`); params.push(target.threadId, target.createdAt, withNextMessages); } } if (unionQueries.length === 0) return null; let finalQuery; if (unionQueries.length === 1) { finalQuery = unionQueries[0]; } else { finalQuery = `${unionQueries.join(" UNION ALL ")} ORDER BY "createdAt" ASC, id ASC`; } const includedResult = await this.#client.execute({ sql: finalQuery, args: params }); const includedRows = includedResult.rows?.map((row) => this.parseRow(row)); const seen = /* @__PURE__ */ new Set(); const dedupedRows = includedRows.filter((row) => { if (seen.has(row.id)) return false; seen.add(row.id); return true; }); return dedupedRows; } async listMessagesById({ messageIds }) { if (messageIds.length === 0) return { messages: [] }; try { const sql = ` SELECT id, content, role, type, "createdAt", thread_id, "resourceId" FROM "${storage.TABLE_MESSAGES}" WHERE id IN (${messageIds.map(() => "?").join(", ")}) ORDER BY "createdAt" DESC `; const result = await this.#client.execute({ sql, args: messageIds }); if (!result.rows) return { messages: [] }; const list = new agent.MessageList().add(result.rows.map(this.parseRow), "memory"); return { messages: list.get.all.db() }; } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_MESSAGES_BY_ID", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { messageIds: JSON.stringify(messageIds) } }, error$1 ); } } async listMessages(args) { const { threadId, resourceId, include, filter, perPage: perPageInput, page = 0, orderBy } = args; const threadIds = Array.isArray(threadId) ? threadId : [threadId]; if (threadIds.length === 0 || threadIds.some((id) => !id.trim())) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_MESSAGES", "INVALID_THREAD_ID"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { threadId: Array.isArray(threadId) ? threadId.join(",") : threadId } }, new Error("threadId must be a non-empty string or array of non-empty strings") ); } if (page < 0) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_MESSAGES", "INVALID_PAGE"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { page } }, new Error("page must be >= 0") ); } const perPage = storage.normalizePerPage(perPageInput, 40); const { offset, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage); try { const { field, direction } = this.parseOrderBy(orderBy, "ASC"); const orderByStatement = `ORDER BY "${field}" ${direction}`; const threadPlaceholders = threadIds.map(() => "?").join(", "); const conditions = [`thread_id IN (${threadPlaceholders})`]; const queryParams = [...threadIds]; if (resourceId) { conditions.push(`"resourceId" = ?`); queryParams.push(resourceId); } if (filter?.dateRange?.start) { const startOp = filter.dateRange.startExclusive ? ">" : ">="; conditions.push(`"createdAt" ${startOp} ?`); queryParams.push( filter.dateRange.start instanceof Date ? filter.dateRange.start.toISOString() : filter.dateRange.start ); } if (filter?.dateRange?.end) { const endOp = filter.dateRange.endExclusive ? "<" : "<="; conditions.push(`"createdAt" ${endOp} ?`); queryParams.push( filter.dateRange.end instanceof Date ? filter.dateRange.end.toISOString() : filter.dateRange.end ); } const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : ""; if (perPage === 0 && (!include || include.length === 0)) { return { messages: [], total: 0, page, perPage: perPageForResponse, hasMore: false }; } if (perPage === 0 && include && include.length > 0) { const includeMessages = await this._getIncludedMessages({ include }); if (!includeMessages || includeMessages.length === 0) { return { messages: [], total: 0, page, perPage: perPageForResponse, hasMore: false }; } const list2 = new agent.MessageList().add(includeMessages, "memory"); return { messages: this._sortMessages(list2.get.all.db(), field, direction), total: 0, page, perPage: perPageForResponse, hasMore: false }; } const countResult = await this.#client.execute({ sql: `SELECT COUNT(*) as count FROM ${storage.TABLE_MESSAGES} ${whereClause}`, args: queryParams }); const total = Number(countResult.rows?.[0]?.count ?? 0); const limitValue = perPageInput === false ? total : perPage; const dataResult = await this.#client.execute({ sql: `SELECT id, content, role, type, "createdAt", "resourceId", "thread_id" FROM ${storage.TABLE_MESSAGES} ${whereClause} ${orderByStatement} LIMIT ? OFFSET ?`, args: [...queryParams, limitValue, offset] }); const messages = (dataResult.rows || []).map((row) => this.parseRow(row)); if (total === 0 && messages.length === 0 && (!include || include.length === 0)) { return { messages: [], total: 0, page, perPage: perPageForResponse, hasMore: false }; } const messageIds = new Set(messages.map((m) => m.id)); if (include && include.length > 0) { const includeMessages = await this._getIncludedMessages({ include }); if (includeMessages) { for (const includeMsg of includeMessages) { if (!messageIds.has(includeMsg.id)) { messages.push(includeMsg); messageIds.add(includeMsg.id); } } } } const list = new agent.MessageList().add(messages, "memory"); const finalMessages = this._sortMessages(list.get.all.db(), field, direction); const threadIdSet = new Set(threadIds); const returnedThreadMessageIds = new Set( finalMessages.filter((m) => m.threadId && threadIdSet.has(m.threadId)).map((m) => m.id) ); const allThreadMessagesReturned = returnedThreadMessageIds.size >= total; const hasMore = perPageInput !== false && !allThreadMessagesReturned && offset + perPage < total; return { messages: finalMessages, total, page, perPage: perPageForResponse, hasMore }; } catch (error$1) { const mastraError = new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_MESSAGES", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { threadId: Array.isArray(threadId) ? threadId.join(",") : threadId, resourceId: resourceId ?? "" } }, error$1 ); this.logger?.error?.(mastraError.toString()); this.logger?.trackException?.(mastraError); return { messages: [], total: 0, page, perPage: perPageForResponse, hasMore: false }; } } async listMessagesByResourceId(args) { const { resourceId, include, filter, perPage: perPageInput, page = 0, orderBy } = args; if (!resourceId || typeof resourceId !== "string" || resourceId.trim().length === 0) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_MESSAGES", "INVALID_QUERY"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { resourceId: resourceId ?? "" } }, new Error("resourceId is required") ); } if (page < 0) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_MESSAGES", "INVALID_PAGE"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { page } }, new Error("page must be >= 0") ); } const perPage = storage.normalizePerPage(perPageInput, 40); const { offset, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage); try { const { field, direction } = this.parseOrderBy(orderBy, "ASC"); const orderByStatement = `ORDER BY "${field}" ${direction}`; const conditions = []; const queryParams = []; conditions.push(`"resourceId" = ?`); queryParams.push(resourceId); if (filter?.dateRange?.start) { const startOp = filter.dateRange.startExclusive ? ">" : ">="; conditions.push(`"createdAt" ${startOp} ?`); queryParams.push( filter.dateRange.start instanceof Date ? filter.dateRange.start.toISOString() : filter.dateRange.start ); } if (filter?.dateRange?.end) { const endOp = filter.dateRange.endExclusive ? "<" : "<="; conditions.push(`"createdAt" ${endOp} ?`); queryParams.push( filter.dateRange.end instanceof Date ? filter.dateRange.end.toISOString() : filter.dateRange.end ); } const whereClause = `WHERE ${conditions.join(" AND ")}`; if (perPage === 0 && (!include || include.length === 0)) { return { messages: [], total: 0, page, perPage: perPageForResponse, hasMore: false }; } if (perPage === 0 && include && include.length > 0) { const includeMessages = await this._getIncludedMessages({ include }); if (!includeMessages || includeMessages.length === 0) { return { messages: [], total: 0, page, perPage: perPageForResponse, hasMore: false }; } const list2 = new agent.MessageList().add(includeMessages, "memory"); return { messages: this._sortMessages(list2.get.all.db(), field, direction), total: 0, page, perPage: perPageForResponse, hasMore: false }; } const countResult = await this.#client.execute({ sql: `SELECT COUNT(*) as count FROM ${storage.TABLE_MESSAGES} ${whereClause}`, args: queryParams }); const total = Number(countResult.rows?.[0]?.count ?? 0); const limitValue = perPageInput === false ? total : perPage; const dataResult = await this.#client.execute({ sql: `SELECT id, content, role, type, "createdAt", "resourceId", "thread_id" FROM ${storage.TABLE_MESSAGES} ${whereClause} ${orderByStatement} LIMIT ? OFFSET ?`, args: [...queryParams, limitValue, offset] }); const messages = (dataResult.rows || []).map((row) => this.parseRow(row)); if (total === 0 && messages.length === 0 && (!include || include.length === 0)) { return { messages: [], total: 0, page, perPage: perPageForResponse, hasMore: false }; } const messageIds = new Set(messages.map((m) => m.id)); if (include && include.length > 0) { const includeMessages = await this._getIncludedMessages({ include }); if (includeMessages) { for (const includeMsg of includeMessages) { if (!messageIds.has(includeMsg.id)) { messages.push(includeMsg); messageIds.add(includeMsg.id); } } } } const list = new agent.MessageList().add(messages, "memory"); const finalMessages = this._sortMessages(list.get.all.db(), field, direction); const hasMore = perPageInput !== false && offset + perPage < total; return { messages: finalMessages, total, page, perPage: perPageForResponse, hasMore }; } catch (error$1) { const mastraError = new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_MESSAGES", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { resourceId } }, error$1 ); this.logger?.error?.(mastraError.toString()); this.logger?.trackException?.(mastraError); return { messages: [], total: 0, page, perPage: perPageForResponse, hasMore: false }; } } async saveMessages({ messages }) { if (messages.length === 0) return { messages }; try { const threadId = messages[0]?.threadId; if (!threadId) { throw new Error("Thread ID is required"); } const batchStatements = messages.map((message) => { const time = message.createdAt || /* @__PURE__ */ new Date(); if (!message.threadId) { throw new Error( `Expected to find a threadId for message, but couldn't find one. An unexpected error has occurred.` ); } if (!message.resourceId) { throw new Error( `Expected to find a resourceId for message, but couldn't find one. An unexpected error has occurred.` ); } return { sql: `INSERT INTO "${storage.TABLE_MESSAGES}" (id, thread_id, content, role, type, "createdAt", "resourceId") VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET thread_id=excluded.thread_id, content=excluded.content, role=excluded.role, type=excluded.type, "resourceId"=excluded."resourceId" `, args: [ message.id, message.threadId, typeof message.content === "object" ? JSON.stringify(message.content) : message.content, message.role, message.type || "v2", time instanceof Date ? time.toISOString() : time, message.resourceId ] }; }); const now = (/* @__PURE__ */ new Date()).toISOString(); batchStatements.push({ sql: `UPDATE "${storage.TABLE_THREADS}" SET "updatedAt" = ? WHERE id = ?`, args: [now, threadId] }); const BATCH_SIZE = 50; const messageStatements = batchStatements.slice(0, -1); const threadUpdateStatement = batchStatements[batchStatements.length - 1]; for (let i = 0; i < messageStatements.length; i += BATCH_SIZE) { const batch = messageStatements.slice(i, i + BATCH_SIZE); if (batch.length > 0) { await this.#client.batch(batch, "write"); } } if (threadUpdateStatement) { await this.#client.execute(threadUpdateStatement); } const list = new agent.MessageList().add(messages, "memory"); return { messages: list.get.all.db() }; } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "SAVE_MESSAGES", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async updateMessages({ messages }) { if (messages.length === 0) { return []; } const messageIds = messages.map((m) => m.id); const placeholders = messageIds.map(() => "?").join(","); const selectSql = `SELECT * FROM ${storage.TABLE_MESSAGES} WHERE id IN (${placeholders})`; const existingResult = await this.#client.execute({ sql: selectSql, args: messageIds }); const existingMessages = existingResult.rows.map((row) => this.parseRow(row)); if (existingMessages.length === 0) { return []; } const batchStatements = []; const threadIdsToUpdate = /* @__PURE__ */ new Set(); const columnMapping = { threadId: "thread_id" }; for (const existingMessage of existingMessages) { const updatePayload = messages.find((m) => m.id === existingMessage.id); if (!updatePayload) continue; const { id, ...fieldsToUpdate } = updatePayload; if (Object.keys(fieldsToUpdate).length === 0) continue; threadIdsToUpdate.add(existingMessage.threadId); if (updatePayload.threadId && updatePayload.threadId !== existingMessage.threadId) { threadIdsToUpdate.add(updatePayload.threadId); } const setClauses = []; const args = []; const updatableFields = { ...fieldsToUpdate }; if (updatableFields.content) { const newContent = { ...existingMessage.content, ...updatableFields.content, // Deep merge metadata if it exists on both ...existingMessage.content?.metadata && updatableFields.content.metadata ? { metadata: { ...existingMessage.content.metadata, ...updatableFields.content.metadata } } : {} }; setClauses.push(`${utils.parseSqlIdentifier("content", "column name")} = ?`); args.push(JSON.stringify(newContent)); delete updatableFields.content; } for (const key in updatableFields) { if (Object.prototype.hasOwnProperty.call(updatableFields, key)) { const dbKey = columnMapping[key] || key; setClauses.push(`${utils.parseSqlIdentifier(dbKey, "column name")} = ?`); let value = updatableFields[key]; if (typeof value === "object" && value !== null) { value = JSON.stringify(value); } args.push(value); } } if (setClauses.length === 0) continue; args.push(id); const sql = `UPDATE ${storage.TABLE_MESSAGES} SET ${setClauses.join(", ")} WHERE id = ?`; batchStatements.push({ sql, args }); } if (batchStatements.length === 0) { return existingMessages; } const now = (/* @__PURE__ */ new Date()).toISOString(); for (const threadId of threadIdsToUpdate) { if (threadId) { batchStatements.push({ sql: `UPDATE ${storage.TABLE_THREADS} SET updatedAt = ? WHERE id = ?`, args: [now, threadId] }); } } await this.#client.batch(batchStatements, "write"); const updatedResult = await this.#client.execute({ sql: selectSql, args: messageIds }); return updatedResult.rows.map((row) => this.parseRow(row)); } async deleteMessages(messageIds) { if (!messageIds || messageIds.length === 0) { return; } try { const BATCH_SIZE = 100; const threadIds = /* @__PURE__ */ new Set(); const tx = await this.#client.transaction("write"); try { for (let i = 0; i < messageIds.length; i += BATCH_SIZE) { const batch = messageIds.slice(i, i + BATCH_SIZE); const placeholders = batch.map(() => "?").join(","); const result = await tx.execute({ sql: `SELECT DISTINCT thread_id FROM "${storage.TABLE_MESSAGES}" WHERE id IN (${placeholders})`, args: batch }); result.rows?.forEach((row) => { if (row.thread_id) threadIds.add(row.thread_id); }); await tx.execute({ sql: `DELETE FROM "${storage.TABLE_MESSAGES}" WHERE id IN (${placeholders})`, args: batch }); } if (threadIds.size > 0) { const now = (/* @__PURE__ */ new Date()).toISOString(); for (const threadId of threadIds) { await tx.execute({ sql: `UPDATE "${storage.TABLE_THREADS}" SET "updatedAt" = ? WHERE id = ?`, args: [now, threadId] }); } } await tx.commit(); } catch (error) { await tx.rollback(); throw error; } } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "DELETE_MESSAGES", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { messageIds: messageIds.join(", ") } }, error$1 ); } } async getResourceById({ resourceId }) { const result = await this.#db.select({ tableName: storage.TABLE_RESOURCES, keys: { id: resourceId } }); if (!result) { return null; } return { ...result, // Ensure workingMemory is always returned as a string, even if auto-parsed as JSON workingMemory: result.workingMemory && typeof result.workingMemory === "object" ? JSON.stringify(result.workingMemory) : result.workingMemory, metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata, createdAt: new Date(result.createdAt), updatedAt: new Date(result.updatedAt) }; } async saveResource({ resource }) { await this.#db.insert({ tableName: storage.TABLE_RESOURCES, record: { ...resource // metadata is handled by prepareStatement which stringifies jsonb columns } }); return resource; } async updateResource({ resourceId, workingMemory, metadata }) { const existingResource = await this.getResourceById({ resourceId }); if (!existingResource) { const newResource = { id: resourceId, workingMemory, metadata: metadata || {}, createdAt: /* @__PURE__ */ new Date(), updatedAt: /* @__PURE__ */ new Date() }; return this.saveResource({ resource: newResource }); } const updatedResource = { ...existingResource, workingMemory: workingMemory !== void 0 ? workingMemory : existingResource.workingMemory, metadata: { ...existingResource.metadata, ...metadata }, updatedAt: /* @__PURE__ */ new Date() }; const updates = []; const values = []; if (workingMemory !== void 0) { updates.push("workingMemory = ?"); values.push(workingMemory); } if (metadata) { updates.push("metadata = jsonb(?)"); values.push(JSON.stringify(updatedResource.metadata)); } updates.push("updatedAt = ?"); values.push(updatedResource.updatedAt.toISOString()); values.push(resourceId); await this.#client.execute({ sql: `UPDATE ${storage.TABLE_RESOURCES} SET ${updates.join(", ")} WHERE id = ?`, args: values }); return updatedResource; } async getThreadById({ threadId, resourceId }) { try { const keys = { id: threadId }; if (resourceId !== void 0) { keys.resourceId = resourceId; } const result = await this.#db.select({ tableName: storage.TABLE_THREADS, keys }); if (!result) { return null; } return { ...result, metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata, createdAt: new Date(result.createdAt), updatedAt: new Date(result.updatedAt) }; } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_THREAD_BY_ID", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { threadId } }, error$1 ); } } async listThreads(args) { const { page = 0, perPage: perPageInput, orderBy, filter } = args; try { this.validatePaginationInput(page, perPageInput ?? 100); } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_THREADS", "INVALID_PAGE"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { page, ...perPageInput !== void 0 && { perPage: perPageInput } } }, error$1 instanceof Error ? error$1 : new Error("Invalid pagination parameters") ); } const perPage = storage.normalizePerPage(perPageInput, 100); try { this.validateMetadataKeys(filter?.metadata); } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_THREADS", "INVALID_METADATA_KEY"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { metadataKeys: filter?.metadata ? Object.keys(filter.metadata).join(", ") : "" } }, error$1 instanceof Error ? error$1 : new Error("Invalid metadata key") ); } const { offset, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage); const { field, direction } = this.parseOrderBy(orderBy); try { const whereClauses = []; const queryParams = []; if (filter?.resourceId) { whereClauses.push("resourceId = ?"); queryParams.push(filter.resourceId); } if (filter?.metadata && Object.keys(filter.metadata).length > 0) { for (const [key, value] of Object.entries(filter.metadata)) { if (value === null) { whereClauses.push(`json_extract(metadata, '$.${key}') IS NULL`); } else if (typeof value === "boolean") { whereClauses.push(`json_extract(metadata, '$.${key}') = ?`); queryParams.push(value ? 1 : 0); } else if (typeof value === "number") { whereClauses.push(`json_extract(metadata, '$.${key}') = ?`); queryParams.push(value); } else if (typeof value === "string") { whereClauses.push(`json_extract(metadata, '$.${key}') = ?`); queryParams.push(value); } else { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "LIST_THREADS", "INVALID_METADATA_VALUE"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, text: `Metadata filter value for key "${key}" must be a scalar type (string, number, boolean, or null), got ${typeof value}`, details: { key, valueType: typeof value } }); } } } const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : ""; const baseQuery = `FROM ${storage.TABLE_THREADS} ${whereClause}`; const mapRowToStorageThreadType = (row) => ({ id: row.id, resourceId: row.resourceId, title: row.title, createdAt: new Date(row.createdAt), updatedAt: new Date(row.updatedAt), metadata: typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata }); const countResult = await this.#client.execute({ sql: `SELECT COUNT(*) as count ${baseQuery}`, args: queryParams }); const total = Number(countResult.rows?.[0]?.count ?? 0); if (total === 0) { return { threads: [], total: 0, page, perPage: perPageForResponse, hasMore: false }; } const limitValue = perPageInput === false ? total : perPage; const dataResult = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_THREADS)} ${baseQuery} ORDER BY "${field}" ${direction} LIMIT ? OFFSET ?`, args: [...queryParams, limitValue, offset] }); const threads = (dataResult.rows || []).map(mapRowToStorageThreadType); return { threads, total, page, perPage: perPageForResponse, hasMore: perPageInput === false ? false : offset + perPage < total }; } catch (error$1) { if (error$1 instanceof error.MastraError && error$1.category === error.ErrorCategory.USER) { throw error$1; } const mastraError = new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_THREADS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { ...filter?.resourceId && { resourceId: filter.resourceId }, hasMetadataFilter: !!filter?.metadata } }, error$1 ); this.logger?.trackException?.(mastraError); this.logger?.error?.(mastraError.toString()); return { threads: [], total: 0, page, perPage: perPageForResponse, hasMore: false }; } } async saveThread({ thread }) { try { await this.#db.insert({ tableName: storage.TABLE_THREADS, record: { ...thread // metadata is handled by prepareStatement which stringifies jsonb columns } }); return thread; } catch (error$1) { const mastraError = new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "SAVE_THREAD", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { threadId: thread.id } }, error$1 ); this.logger?.trackException?.(mastraError); this.logger?.error?.(mastraError.toString()); throw mastraError; } } async updateThread({ id, title, metadata }) { const thread = await this.getThreadById({ threadId: id }); if (!thread) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "UPDATE_THREAD", "NOT_FOUND"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, text: `Thread ${id} not found`, details: { status: 404, threadId: id } }); } const now = /* @__PURE__ */ new Date(); const updatedThread = { ...thread, title, metadata: { ...thread.metadata, ...metadata }, updatedAt: now }; try { await this.#client.execute({ sql: `UPDATE ${storage.TABLE_THREADS} SET title = ?, metadata = jsonb(?), updatedAt = ? WHERE id = ?`, args: [title, JSON.stringify(updatedThread.metadata), now.toISOString(), id] }); return updatedThread; } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "UPDATE_THREAD", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, text: `Failed to update thread ${id}`, details: { threadId: id } }, error$1 ); } } async deleteThread({ threadId }) { try { await this.#client.execute({ sql: `DELETE FROM ${storage.TABLE_MESSAGES} WHERE thread_id = ?`, args: [threadId] }); await this.#client.execute({ sql: `DELETE FROM ${storage.TABLE_THREADS} WHERE id = ?`, args: [threadId] }); } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "DELETE_THREAD", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { threadId } }, error$1 ); } } async cloneThread(args) { const { sourceThreadId, newThreadId: providedThreadId, resourceId, title, metadata, options } = args; const sourceThread = await this.getThreadById({ threadId: sourceThreadId }); if (!sourceThread) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "CLONE_THREAD", "SOURCE_NOT_FOUND"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, text: `Source thread with id ${sourceThreadId} not found`, details: { sourceThreadId } }); } const newThreadId = providedThreadId || crypto.randomUUID(); const existingThread = await this.getThreadById({ threadId: newThreadId }); if (existingThread) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "CLONE_THREAD", "THREAD_EXISTS"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, text: `Thread with id ${newThreadId} already exists`, details: { newThreadId } }); } try { let messageQuery = `SELECT id, content, role, type, "createdAt", thread_id, "resourceId" FROM "${storage.TABLE_MESSAGES}" WHERE thread_id = ?`; const messageParams = [sourceThreadId]; if (options?.messageFilter?.startDate) { messageQuery += ` AND "createdAt" >= ?`; messageParams.push( options.messageFilter.startDate instanceof Date ? options.messageFilter.startDate.toISOString() : options.messageFilter.startDate ); } if (options?.messageFilter?.endDate) { messageQuery += ` AND "createdAt" <= ?`; messageParams.push( options.messageFilter.endDate instanceof Date ? options.messageFilter.endDate.toISOString() : options.messageFilter.endDate ); } if (options?.messageFilter?.messageIds && options.messageFilter.messageIds.length > 0) { messageQuery += ` AND id IN (${options.messageFilter.messageIds.map(() => "?").join(", ")})`; messageParams.push(...options.messageFilter.messageIds); } messageQuery += ` ORDER BY "createdAt" ASC`; if (options?.messageLimit && options.messageLimit > 0) { const limitQuery = `SELECT * FROM (${messageQuery.replace('ORDER BY "createdAt" ASC', 'ORDER BY "createdAt" DESC')} LIMIT ?) ORDER BY "createdAt" ASC`; messageParams.push(options.messageLimit); messageQuery = limitQuery; } const sourceMessagesResult = await this.#client.execute({ sql: messageQuery, args: messageParams }); const sourceMessages = sourceMessagesResult.rows || []; const now = /* @__PURE__ */ new Date(); const nowStr = now.toISOString(); const lastMessageId = sourceMessages.length > 0 ? sourceMessages[sourceMessages.length - 1].id : void 0; const cloneMetadata = { sourceThreadId, clonedAt: now, ...lastMessageId && { lastMessageId } }; const newThread = { id: newThreadId, resourceId: resourceId || sourceThread.resourceId, title: title || (sourceThread.title ? `Clone of ${sourceThread.title}` : ""), metadata: { ...metadata, clone: cloneMetadata }, createdAt: now, updatedAt: now }; const tx = await this.#client.transaction("write"); try { await tx.execute({ sql: `INSERT INTO "${storage.TABLE_THREADS}" (id, "resourceId", title, metadata, "createdAt", "updatedAt") VALUES (?, ?, ?, jsonb(?), ?, ?)`, args: [ newThread.id, newThread.resourceId, newThread.title ?? "", JSON.stringify(newThread.metadata), nowStr, nowStr ] }); const clonedMessages = []; const messageIdMap = {}; const targetResourceId = resourceId || sourceThread.resourceId; for (const sourceMsg of sourceMessages) { const newMessageId = crypto.randomUUID(); messageIdMap[sourceMsg.id] = newMessageId; const contentStr = sourceMsg.content; let parsedContent; try { parsedContent = JSON.parse(contentStr); } catch { parsedContent = { format: 2, parts: [{ type: "text", text: contentStr }] }; } await tx.execute({ sql: `INSERT INTO "${storage.TABLE_MESSAGES}" (id, thread_id, content, role, type, "createdAt", "resourceId") VALUES (?, ?, ?, ?, ?, ?, ?)`, args: [ newMessageId, newThreadId, contentStr, sourceMsg.role, sourceMsg.type || "v2", sourceMsg.createdAt, targetResourceId ] }); clonedMessages.push({ id: newMessageId, threadId: newThreadId, content: parsedContent, role: sourceMsg.role, type: sourceMsg.type || void 0, createdAt: new Date(sourceMsg.createdAt), resourceId: targetResourceId }); } await tx.commit(); return { thread: newThread, clonedMessages, messageIdMap }; } catch (error) { await tx.rollback(); throw error; } } catch (error$1) { if (error$1 instanceof error.MastraError) { throw error$1; } throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "CLONE_THREAD", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { sourceThreadId, newThreadId } }, error$1 ); } } // ============================================ // Observational Memory Methods // ============================================ getOMKey(threadId, resourceId) { return threadId ? `thread:${threadId}` : `resource:${resourceId}`; } parseOMRow(row) { return { id: row.id, scope: row.scope, threadId: row.threadId || null, resourceId: row.resourceId, createdAt: new Date(row.createdAt), updatedAt: new Date(row.updatedAt), lastObservedAt: row.lastObservedAt ? new Date(row.lastObservedAt) : void 0, originType: row.originType || "initial", generationCount: Number(row.generationCount || 0), activeObservations: row.activeObservations || "", // Handle new chunk-based structure bufferedObservationChunks: row.bufferedObservationChunks ? typeof row.bufferedObservationChunks === "string" ? JSON.parse(row.bufferedObservationChunks) : row.bufferedObservationChunks : void 0, // Deprecated fields (for backward compatibility) bufferedObservations: row.activeObservationsPendingUpdate || void 0, bufferedObservationTokens: row.bufferedObservationTokens ? Number(row.bufferedObservationTokens) : void 0, bufferedMessageIds: void 0, // Use bufferedObservationChunks instead bufferedReflection: row.bufferedReflection || void 0, bufferedReflectionTokens: row.bufferedReflectionTokens ? Number(row.bufferedReflectionTokens) : void 0, bufferedReflectionInputTokens: row.bufferedReflectionInputTokens ? Number(row.bufferedReflectionInputTokens) : void 0, reflectedObservationLineCount: row.reflectedObservationLineCount ? Number(row.reflectedObservationLineCount) : void 0, totalTokensObserved: Number(row.totalTokensObserved || 0), observationTokenCount: Number(row.observationTokenCount || 0), pendingMessageTokens: Number(row.pendingMessageTokens || 0), isReflecting: Boolean(row.isReflecting), isObserving: Boolean(row.isObserving), isBufferingObservation: row.isBufferingObservation === true || row.isBufferingObservation === "true" || row.isBufferingObservation === 1, isBufferingReflection: row.isBufferingReflection === true || row.isBufferingReflection === "true" || row.isBufferingReflection === 1, lastBufferedAtTokens: typeof row.lastBufferedAtTokens === "number" ? row.lastBufferedAtTokens : parseInt(String(row.lastBufferedAtTokens ?? "0"), 10) || 0, lastBufferedAtTime: row.lastBufferedAtTime ? new Date(String(row.lastBufferedAtTime)) : null, config: row.config ? JSON.parse(row.config) : {}, metadata: row.metadata ? JSON.parse(row.metadata) : void 0, observedMessageIds: row.observedMessageIds ? JSON.parse(row.observedMessageIds) : void 0, observedTimezone: row.observedTimezone || void 0 }; } async getObservationalMemory(threadId, resourceId) { try { const lookupKey = this.getOMKey(threadId, resourceId); const result = await this.#client.execute({ // Use generationCount DESC for reliable ordering (incremented for each new record) sql: `SELECT * FROM "${OM_TABLE}" WHERE "lookupKey" = ? ORDER BY "generationCount" DESC LIMIT 1`, args: [lookupKey] }); if (!result.rows || result.rows.length === 0) return null; return this.parseOMRow(result.rows[0]); } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_OBSERVATIONAL_MEMORY", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { threadId, resourceId } }, error$1 ); } } async getObservationalMemoryHistory(threadId, resourceId, limit = 10, options) { try { const lookupKey = this.getOMKey(threadId, resourceId); const conditions = [`"lookupKey" = ?`]; const args = [lookupKey]; if (options?.from) { conditions.push(`"createdAt" >= ?`); args.push(options.from.toISOString()); } if (options?.to) { conditions.push(`"createdAt" <= ?`); args.push(options.to.toISOString()); } args.push(limit); let sql = `SELECT * FROM "${OM_TABLE}" WHERE ${conditions.join(" AND ")} ORDER BY "generationCount" DESC LIMIT ?`; if (options?.offset != null) { args.push(options.offset); sql += ` OFFSET ?`; } const result = await this.#client.execute({ sql, args }); if (!result.rows) return []; return result.rows.map((row) => this.parseOMRow(row)); } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_OBSERVATIONAL_MEMORY_HISTORY", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { threadId, resourceId, limit } }, error$1 ); } } async initializeObservationalMemory(input) { try { const id = crypto.randomUUID(); const now = /* @__PURE__ */ new Date(); const lookupKey = this.getOMKey(input.threadId, input.resourceId); const record = { id, scope: input.scope, threadId: input.threadId, resourceId: input.resourceId, createdAt: now, updatedAt: now, lastObservedAt: void 0, originType: "initial", generationCount: 0, activeObservations: "", totalTokensObserved: 0, observationTokenCount: 0, pendingMessageTokens: 0, isReflecting: false, isObserving: false, isBufferingObservation: false, isBufferingReflection: false, lastBufferedAtTokens: 0, lastBufferedAtTime: null, config: input.config, observedTimezone: input.observedTimezone }; await this.#client.execute({ sql: `INSERT INTO "${OM_TABLE}" ( id, "lookupKey", scope, "resourceId", "threadId", "activeObservations", "activeObservationsPendingUpdate", "originType", config, "generationCount", "lastObservedAt", "lastReflectionAt", "pendingMessageTokens", "totalTokensObserved", "observationTokenCount", "isObserving", "isReflecting", "isBufferingObservation", "isBufferingReflection", "lastBufferedAtTokens", "lastBufferedAtTime", "observedTimezone", "createdAt", "updatedAt" ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, args: [ id, lookupKey, input.scope, input.resourceId, input.threadId || null, "", null, "initial", JSON.stringify(input.config), 0, null, null, 0, 0, 0, false, false, false, // isBufferingObservation false, // isBufferingReflection 0, // lastBufferedAtTokens null, // lastBufferedAtTime input.observedTimezone || null, now.toISOString(), now.toISOString() ] }); return record; } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "INITIALIZE_OBSERVATIONAL_MEMORY", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { threadId: input.threadId, resourceId: input.resourceId } }, error$1 ); } } async insertObservationalMemoryRecord(record) { try { const lookupKey = this.getOMKey(record.threadId, record.resourceId); await this.#client.execute({ sql: `INSERT INTO "${OM_TABLE}" ( id, "lookupKey", scope, "resourceId", "threadId", "activeObservations", "activeObservationsPendingUpdate", "originType", config, "generationCount", "lastObservedAt", "lastReflectionAt", "pendingMessageTokens", "totalTokensObserved", "observationTokenCount", "observedMessageIds", "bufferedObservationChunks", "bufferedReflection", "bufferedReflectionTokens", "bufferedReflectionInputTokens", "reflectedObservationLineCount", "isObserving", "isReflecting", "isBufferingObservation", "isBufferingReflection", "lastBufferedAtTokens", "lastBufferedAtTime", "observedTimezone", metadata, "createdAt", "updatedAt" ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, args: [ record.id, lookupKey, record.scope, record.resourceId, record.threadId || null, record.activeObservations || "", null, record.originType || "initial", record.config ? JSON.stringify(record.config) : null, record.generationCount || 0, record.lastObservedAt ? record.lastObservedAt.toISOString() : null, null, record.pendingMessageTokens || 0, record.totalTokensObserved || 0, record.observationTokenCount || 0, record.observedMessageIds ? JSON.stringify(record.observedMessageIds) : null, record.bufferedObservationChunks ? JSON.stringify(record.bufferedObservationChunks) : null, record.bufferedReflection || null, record.bufferedReflectionTokens ?? null, record.bufferedReflectionInputTokens ?? null, record.reflectedObservationLineCount ?? null, record.isObserving || false, record.isReflecting || false, record.isBufferingObservation || false, record.isBufferingReflection || false, record.lastBufferedAtTokens || 0, record.lastBufferedAtTime ? record.lastBufferedAtTime.toISOString() : null, record.observedTimezone || null, record.metadata ? JSON.stringify(record.metadata) : null, record.createdAt.toISOString(), record.updatedAt.toISOString() ] }); } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "INSERT_OBSERVATIONAL_MEMORY_RECORD", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { id: record.id, threadId: record.threadId, resourceId: record.resourceId } }, error$1 ); } } async updateActiveObservations(input) { try { const now = /* @__PURE__ */ new Date(); const observedMessageIdsJson = input.observedMessageIds ? JSON.stringify(input.observedMessageIds) : null; const result = await this.#client.execute({ sql: `UPDATE "${OM_TABLE}" SET "activeObservations" = ?, "lastObservedAt" = ?, "pendingMessageTokens" = 0, "observationTokenCount" = ?, "totalTokensObserved" = "totalTokensObserved" + ?, "observedMessageIds" = ?, "updatedAt" = ? WHERE id = ?`, args: [ input.observations, input.lastObservedAt.toISOString(), input.tokenCount, input.tokenCount, observedMessageIdsJson, now.toISOString(), input.id ] }); if (result.rowsAffected === 0) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "UPDATE_ACTIVE_OBSERVATIONS", "NOT_FOUND"), text: `Observational memory record not found: ${input.id}`, domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { id: input.id } }); } } catch (error$1) { if (error$1 instanceof error.MastraError) { throw error$1; } throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "UPDATE_ACTIVE_OBSERVATIONS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { id: input.id } }, error$1 ); } } async createReflectionGeneration(input) { try { const id = crypto.randomUUID(); const now = /* @__PURE__ */ new Date(); const lookupKey = this.getOMKey(input.currentRecord.threadId, input.currentRecord.resourceId); const record = { id, scope: input.currentRecord.scope, threadId: input.currentRecord.threadId, resourceId: input.currentRecord.resourceId, createdAt: now, updatedAt: now, lastObservedAt: input.currentRecord.lastObservedAt, originType: "reflection", generationCount: input.currentRecord.generationCount + 1, activeObservations: input.reflection, totalTokensObserved: input.currentRecord.totalTokensObserved, observationTokenCount: input.tokenCount, pendingMessageTokens: 0, isReflecting: false, isObserving: false, isBufferingObservation: false, isBufferingReflection: false, lastBufferedAtTokens: 0, lastBufferedAtTime: null, config: input.currentRecord.config, metadata: input.currentRecord.metadata, observedTimezone: input.currentRecord.observedTimezone }; await this.#client.execute({ sql: `INSERT INTO "${OM_TABLE}" ( id, "lookupKey", scope, "resourceId", "threadId", "activeObservations", "activeObservationsPendingUpdate", "originType", config, "generationCount", "lastObservedAt", "lastReflectionAt", "pendingMessageTokens", "totalTokensObserved", "observationTokenCount", "isObserving", "isReflecting", "isBufferingObservation", "isBufferingReflection", "lastBufferedAtTokens", "lastBufferedAtTime", "observedTimezone", metadata, "createdAt", "updatedAt" ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, args: [ id, lookupKey, record.scope, record.resourceId, record.threadId || null, input.reflection, null, "reflection", JSON.stringify(record.config), input.currentRecord.generationCount + 1, record.lastObservedAt?.toISOString() || null, now.toISOString(), record.pendingMessageTokens, record.totalTokensObserved, record.observationTokenCount, false, // isObserving false, // isReflecting false, // isBufferingObservation false, // isBufferingReflection 0, // lastBufferedAtTokens null, // lastBufferedAtTime record.observedTimezone || null, record.metadata ? JSON.stringify(record.metadata) : null, now.toISOString(), now.toISOString() ] }); return record; } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "CREATE_REFLECTION_GENERATION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { currentRecordId: input.currentRecord.id } }, error$1 ); } } async setReflectingFlag(id, isReflecting) { try { const result = await this.#client.execute({ sql: `UPDATE "${OM_TABLE}" SET "isReflecting" = ?, "updatedAt" = ? WHERE id = ?`, args: [isReflecting, (/* @__PURE__ */ new Date()).toISOString(), id] }); if (result.rowsAffected === 0) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "SET_REFLECTING_FLAG", "NOT_FOUND"), text: `Observational memory record not found: ${id}`, domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { id, isReflecting } }); } } catch (error$1) { if (error$1 instanceof error.MastraError) { throw error$1; } throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "SET_REFLECTING_FLAG", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { id, isReflecting } }, error$1 ); } } async setObservingFlag(id, isObserving) { try { const result = await this.#client.execute({ sql: `UPDATE "${OM_TABLE}" SET "isObserving" = ?, "updatedAt" = ? WHERE id = ?`, args: [isObserving, (/* @__PURE__ */ new Date()).toISOString(), id] }); if (result.rowsAffected === 0) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "SET_OBSERVING_FLAG", "NOT_FOUND"), text: `Observational memory record not found: ${id}`, domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { id, isObserving } }); } } catch (error$1) { if (error$1 instanceof error.MastraError) { throw error$1; } throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "SET_OBSERVING_FLAG", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { id, isObserving } }, error$1 ); } } async setBufferingObservationFlag(id, isBuffering, lastBufferedAtTokens) { try { const nowStr = (/* @__PURE__ */ new Date()).toISOString(); let sql; let args; if (lastBufferedAtTokens !== void 0) { sql = `UPDATE "${OM_TABLE}" SET "isBufferingObservation" = ?, "lastBufferedAtTokens" = ?, "updatedAt" = ? WHERE id = ?`; args = [isBuffering, lastBufferedAtTokens, nowStr, id]; } else { sql = `UPDATE "${OM_TABLE}" SET "isBufferingObservation" = ?, "updatedAt" = ? WHERE id = ?`; args = [isBuffering, nowStr, id]; } const result = await this.#client.execute({ sql, args }); if (result.rowsAffected === 0) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "SET_BUFFERING_OBSERVATION_FLAG", "NOT_FOUND"), text: `Observational memory record not found: ${id}`, domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { id, isBuffering, lastBufferedAtTokens: lastBufferedAtTokens ?? null } }); } } catch (error$1) { if (error$1 instanceof error.MastraError) { throw error$1; } throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "SET_BUFFERING_OBSERVATION_FLAG", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { id, isBuffering, lastBufferedAtTokens: lastBufferedAtTokens ?? null } }, error$1 ); } } async setBufferingReflectionFlag(id, isBuffering) { try { const result = await this.#client.execute({ sql: `UPDATE "${OM_TABLE}" SET "isBufferingReflection" = ?, "updatedAt" = ? WHERE id = ?`, args: [isBuffering, (/* @__PURE__ */ new Date()).toISOString(), id] }); if (result.rowsAffected === 0) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "SET_BUFFERING_REFLECTION_FLAG", "NOT_FOUND"), text: `Observational memory record not found: ${id}`, domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { id, isBuffering } }); } } catch (error$1) { if (error$1 instanceof error.MastraError) { throw error$1; } throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "SET_BUFFERING_REFLECTION_FLAG", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { id, isBuffering } }, error$1 ); } } async clearObservationalMemory(threadId, resourceId) { try { const lookupKey = this.getOMKey(threadId, resourceId); await this.#client.execute({ sql: `DELETE FROM "${OM_TABLE}" WHERE "lookupKey" = ?`, args: [lookupKey] }); } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "CLEAR_OBSERVATIONAL_MEMORY", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { threadId, resourceId } }, error$1 ); } } async setPendingMessageTokens(id, tokenCount) { try { const result = await this.#client.execute({ sql: `UPDATE "${OM_TABLE}" SET "pendingMessageTokens" = ?, "updatedAt" = ? WHERE id = ?`, args: [tokenCount, (/* @__PURE__ */ new Date()).toISOString(), id] }); if (result.rowsAffected === 0) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "SET_PENDING_MESSAGE_TOKENS", "NOT_FOUND"), text: `Observational memory record not found: ${id}`, domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { id, tokenCount } }); } } catch (error$1) { if (error$1 instanceof error.MastraError) { throw error$1; } throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "SET_PENDING_MESSAGE_TOKENS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { id, tokenCount } }, error$1 ); } } async updateObservationalMemoryConfig(input) { try { const selectResult = await this.#client.execute({ sql: `SELECT config FROM "${OM_TABLE}" WHERE id = ?`, args: [input.id] }); if (selectResult.rows.length === 0) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "UPDATE_OM_CONFIG", "NOT_FOUND"), text: `Observational memory record not found: ${input.id}`, domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { id: input.id } }); } const row = selectResult.rows[0]; const existing = row.config ? JSON.parse(row.config) : {}; const merged = this.deepMergeConfig(existing, input.config); await this.#client.execute({ sql: `UPDATE "${OM_TABLE}" SET config = ?, "updatedAt" = ? WHERE id = ?`, args: [JSON.stringify(merged), (/* @__PURE__ */ new Date()).toISOString(), input.id] }); } catch (error$1) { if (error$1 instanceof error.MastraError) { throw error$1; } throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "UPDATE_OM_CONFIG", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { id: input.id } }, error$1 ); } } // ============================================ // Async Buffering Methods // ============================================ async updateBufferedObservations(input) { try { const nowStr = (/* @__PURE__ */ new Date()).toISOString(); const current = await this.#client.execute({ sql: `SELECT "bufferedObservationChunks" FROM "${OM_TABLE}" WHERE id = ?`, args: [input.id] }); if (!current.rows || current.rows.length === 0) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "UPDATE_BUFFERED_OBSERVATIONS", "NOT_FOUND"), text: `Observational memory record not found: ${input.id}`, domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { id: input.id } }); } const row = current.rows[0]; let existingChunks = []; if (row.bufferedObservationChunks) { try { const parsed = typeof row.bufferedObservationChunks === "string" ? JSON.parse(row.bufferedObservationChunks) : row.bufferedObservationChunks; existingChunks = Array.isArray(parsed) ? parsed : []; } catch { existingChunks = []; } } const newChunk = { id: `ombuf-${crypto$1.randomUUID()}`, cycleId: input.chunk.cycleId, observations: input.chunk.observations, tokenCount: input.chunk.tokenCount, messageIds: input.chunk.messageIds, messageTokens: input.chunk.messageTokens, lastObservedAt: input.chunk.lastObservedAt, createdAt: /* @__PURE__ */ new Date(), suggestedContinuation: input.chunk.suggestedContinuation, currentTask: input.chunk.currentTask, threadTitle: input.chunk.threadTitle }; const newChunks = [...existingChunks, newChunk]; const lastBufferedAtTime = input.lastBufferedAtTime ? input.lastBufferedAtTime.toISOString() : null; const result = await this.#client.execute({ sql: `UPDATE "${OM_TABLE}" SET "bufferedObservationChunks" = ?, "lastBufferedAtTime" = COALESCE(?, "lastBufferedAtTime"), "updatedAt" = ? WHERE id = ?`, args: [JSON.stringify(newChunks), lastBufferedAtTime, nowStr, input.id] }); if (result.rowsAffected === 0) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "UPDATE_BUFFERED_OBSERVATIONS", "NOT_FOUND"), text: `Observational memory record not found: ${input.id}`, domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { id: input.id } }); } } catch (error$1) { if (error$1 instanceof error.MastraError) { throw error$1; } throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "UPDATE_BUFFERED_OBSERVATIONS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { id: input.id } }, error$1 ); } } async swapBufferedToActive(input) { try { const nowStr = (/* @__PURE__ */ new Date()).toISOString(); const current = await this.#client.execute({ sql: `SELECT * FROM "${OM_TABLE}" WHERE id = ?`, args: [input.id] }); if (!current.rows || current.rows.length === 0) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "SWAP_BUFFERED_TO_ACTIVE", "NOT_FOUND"), text: `Observational memory record not found: ${input.id}`, domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { id: input.id } }); } const row = current.rows[0]; let chunks = []; if (row.bufferedObservationChunks) { try { const parsed = typeof row.bufferedObservationChunks === "string" ? JSON.parse(row.bufferedObservationChunks) : row.bufferedObservationChunks; chunks = Array.isArray(parsed) ? parsed : []; } catch { chunks = []; } } if (chunks.length === 0) { return { chunksActivated: 0, messageTokensActivated: 0, observationTokensActivated: 0, messagesActivated: 0, activatedCycleIds: [], activatedMessageIds: [] }; } const retentionFloor = input.messageTokensThreshold * (1 - input.activationRatio); const targetMessageTokens = Math.max(0, input.currentPendingTokens - retentionFloor); let cumulativeMessageTokens = 0; let bestOverBoundary = 0; let bestOverTokens = 0; let bestUnderBoundary = 0; let bestUnderTokens = 0; for (let i = 0; i < chunks.length; i++) { cumulativeMessageTokens += chunks[i].messageTokens ?? 0; const boundary2 = i + 1; if (cumulativeMessageTokens >= targetMessageTokens) { if (bestOverBoundary === 0 || cumulativeMessageTokens < bestOverTokens) { bestOverBoundary = boundary2; bestOverTokens = cumulativeMessageTokens; } } else { if (cumulativeMessageTokens > bestUnderTokens) { bestUnderBoundary = boundary2; bestUnderTokens = cumulativeMessageTokens; } } } const maxOvershoot = retentionFloor * 0.95; const overshoot = bestOverTokens - targetMessageTokens; const remainingAfterOver = input.currentPendingTokens - bestOverTokens; const remainingAfterUnder = input.currentPendingTokens - bestUnderTokens; const minRemaining = Math.min(1e3, retentionFloor); let chunksToActivate; if (input.forceMaxActivation && bestOverBoundary > 0 && remainingAfterOver >= minRemaining) { chunksToActivate = bestOverBoundary; } else if (bestOverBoundary > 0 && overshoot <= maxOvershoot && remainingAfterOver >= minRemaining) { chunksToActivate = bestOverBoundary; } else if (bestUnderBoundary > 0 && remainingAfterUnder >= minRemaining) { chunksToActivate = bestUnderBoundary; } else if (bestOverBoundary > 0) { chunksToActivate = bestOverBoundary; } else { chunksToActivate = 1; } const activatedChunks = chunks.slice(0, chunksToActivate); const remainingChunks = chunks.slice(chunksToActivate); const activatedContent = activatedChunks.map((c) => c.observations).join("\n\n"); const activatedTokens = activatedChunks.reduce((sum, c) => sum + c.tokenCount, 0); const activatedMessageTokens = activatedChunks.reduce((sum, c) => sum + (c.messageTokens ?? 0), 0); const activatedMessageCount = activatedChunks.reduce((sum, c) => sum + c.messageIds.length, 0); const activatedCycleIds = activatedChunks.map((c) => c.cycleId).filter((id) => !!id); const activatedMessageIds = activatedChunks.flatMap((c) => c.messageIds ?? []); const latestChunk = activatedChunks[activatedChunks.length - 1]; const lastObservedAt = input.lastObservedAt ?? (latestChunk?.lastObservedAt ? new Date(latestChunk.lastObservedAt) : /* @__PURE__ */ new Date()); const lastObservedAtStr = lastObservedAt.toISOString(); const existingActive = row.activeObservations || ""; const existingTokenCount = Number(row.observationTokenCount || 0); const boundary = ` --- message boundary (${lastObservedAt.toISOString()}) --- `; const newActive = existingActive ? `${existingActive}${boundary}${activatedContent}` : activatedContent; const newTokenCount = existingTokenCount + activatedTokens; const existingPending = Number(row.pendingMessageTokens || 0); const newPending = Math.max(0, existingPending - activatedMessageTokens); const updateResult = await this.#client.execute({ sql: `UPDATE "${OM_TABLE}" SET "activeObservations" = ?, "observationTokenCount" = ?, "pendingMessageTokens" = ?, "bufferedObservationChunks" = ?, "lastObservedAt" = ?, "updatedAt" = ? WHERE id = ? AND "bufferedObservationChunks" IS NOT NULL AND "bufferedObservationChunks" != '[]'`, args: [ newActive, newTokenCount, newPending, remainingChunks.length > 0 ? JSON.stringify(remainingChunks) : null, lastObservedAtStr, nowStr, input.id ] }); if (updateResult.rowsAffected === 0) { return { chunksActivated: 0, messageTokensActivated: 0, observationTokensActivated: 0, messagesActivated: 0, activatedCycleIds: [], activatedMessageIds: [] }; } const latestChunkHints = activatedChunks[activatedChunks.length - 1]; return { chunksActivated: activatedChunks.length, messageTokensActivated: activatedMessageTokens, observationTokensActivated: activatedTokens, messagesActivated: activatedMessageCount, activatedCycleIds, activatedMessageIds, observations: activatedContent, perChunk: activatedChunks.map((c) => ({ cycleId: c.cycleId ?? "", messageTokens: c.messageTokens ?? 0, observationTokens: c.tokenCount, messageCount: c.messageIds.length, observations: c.observations })), suggestedContinuation: latestChunkHints?.suggestedContinuation ?? void 0, currentTask: latestChunkHints?.currentTask ?? void 0 }; } catch (error$1) { if (error$1 instanceof error.MastraError) { throw error$1; } throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "SWAP_BUFFERED_TO_ACTIVE", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { id: input.id } }, error$1 ); } } async updateBufferedReflection(input) { try { const nowStr = (/* @__PURE__ */ new Date()).toISOString(); const result = await this.#client.execute({ sql: `UPDATE "${OM_TABLE}" SET "bufferedReflection" = CASE WHEN "bufferedReflection" IS NOT NULL AND "bufferedReflection" != '' THEN "bufferedReflection" || char(10) || char(10) || ? ELSE ? END, "bufferedReflectionTokens" = COALESCE("bufferedReflectionTokens", 0) + ?, "bufferedReflectionInputTokens" = COALESCE("bufferedReflectionInputTokens", 0) + ?, "reflectedObservationLineCount" = ?, "updatedAt" = ? WHERE id = ?`, args: [ input.reflection, input.reflection, input.tokenCount, input.inputTokenCount, input.reflectedObservationLineCount, nowStr, input.id ] }); if (result.rowsAffected === 0) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "UPDATE_BUFFERED_REFLECTION", "NOT_FOUND"), text: `Observational memory record not found: ${input.id}`, domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { id: input.id } }); } } catch (error$1) { if (error$1 instanceof error.MastraError) { throw error$1; } throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "UPDATE_BUFFERED_REFLECTION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { id: input.id } }, error$1 ); } } async swapBufferedReflectionToActive(input) { try { const current = await this.#client.execute({ sql: `SELECT * FROM "${OM_TABLE}" WHERE id = ?`, args: [input.currentRecord.id] }); if (!current.rows || current.rows.length === 0) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "SWAP_BUFFERED_REFLECTION_TO_ACTIVE", "NOT_FOUND"), text: `Observational memory record not found: ${input.currentRecord.id}`, domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { id: input.currentRecord.id } }); } const row = current.rows[0]; const bufferedReflection = row.bufferedReflection || ""; const reflectedLineCount = Number(row.reflectedObservationLineCount || 0); if (!bufferedReflection) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "SWAP_BUFFERED_REFLECTION_TO_ACTIVE", "NO_CONTENT"), text: "No buffered reflection to swap", domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { id: input.currentRecord.id } }); } const currentObservations = row.activeObservations || ""; const allLines = currentObservations.split("\n"); const unreflectedLines = allLines.slice(reflectedLineCount); const unreflectedContent = unreflectedLines.join("\n").trim(); const newObservations = unreflectedContent ? `${bufferedReflection} ${unreflectedContent}` : bufferedReflection; const newRecord = await this.createReflectionGeneration({ currentRecord: input.currentRecord, reflection: newObservations, tokenCount: input.tokenCount }); const nowStr = (/* @__PURE__ */ new Date()).toISOString(); await this.#client.execute({ sql: `UPDATE "${OM_TABLE}" SET "bufferedReflection" = NULL, "bufferedReflectionTokens" = NULL, "bufferedReflectionInputTokens" = NULL, "reflectedObservationLineCount" = NULL, "updatedAt" = ? WHERE id = ?`, args: [nowStr, input.currentRecord.id] }); return newRecord; } catch (error$1) { if (error$1 instanceof error.MastraError) { throw error$1; } throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "SWAP_BUFFERED_REFLECTION_TO_ACTIVE", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { id: input.currentRecord.id } }, error$1 ); } } }; var statusTimestamp = (status, now) => { if (status === "delivered") return { deliveredAt: now }; if (status === "seen") return { seenAt: now }; if (status === "dismissed") return { dismissedAt: now }; if (status === "archived") return { archivedAt: now }; if (status === "discarded") return { discardedAt: now }; return {}; }; function parseJson2(value) { if (value == null) return void 0; if (typeof value === "string") { try { return JSON.parse(value); } catch { return value; } } return value; } function parseDate(value) { return value == null ? void 0 : new Date(String(value)); } var cloneValue = (value) => value === void 0 ? void 0 : structuredClone(value); function rowToNotification(row) { return { id: String(row.id), threadId: String(row.threadId), source: String(row.source), kind: String(row.kind), priority: String(row.priority), status: String(row.status), summary: String(row.summary), payload: parseJson2(row.payload), resourceId: row.resourceId == null ? void 0 : String(row.resourceId), agentId: row.agentId == null ? void 0 : String(row.agentId), sourceId: row.sourceId == null ? void 0 : String(row.sourceId), dedupeKey: row.dedupeKey == null ? void 0 : String(row.dedupeKey), coalesceKey: row.coalesceKey == null ? void 0 : String(row.coalesceKey), coalescedCount: Number(row.coalescedCount ?? 1), attributes: parseJson2(row.attributes), createdAt: new Date(String(row.createdAt)), updatedAt: new Date(String(row.updatedAt)), deliveredAt: parseDate(row.deliveredAt), seenAt: parseDate(row.seenAt), dismissedAt: parseDate(row.dismissedAt), archivedAt: parseDate(row.archivedAt), discardedAt: parseDate(row.discardedAt), deliverAt: parseDate(row.deliverAt), summaryAt: parseDate(row.summaryAt), deliveryReason: row.deliveryReason == null ? void 0 : String(row.deliveryReason), deliveryAttempts: Number(row.deliveryAttempts ?? 0), lastDeliveryAttemptAt: parseDate(row.lastDeliveryAttemptAt), lastDeliveryError: row.lastDeliveryError == null ? void 0 : String(row.lastDeliveryError), deliveredSignalId: row.deliveredSignalId == null ? void 0 : String(row.deliveredSignalId), summarySignalId: row.summarySignalId == null ? void 0 : String(row.summarySignalId), metadata: parseJson2(row.metadata) }; } function addArrayFilter(conditions, args, column, value) { if (!value) return; const values = Array.isArray(value) ? value : [value]; conditions.push(`"${column}" IN (${values.map(() => "?").join(", ")})`); args.push(...values); } var NotificationsLibSQL = class extends storage.NotificationsStorage { #db; #client; constructor(config) { super(); const client = resolveClient(config); this.#client = client; this.#db = new LibSQLDB({ client, maxRetries: config.maxRetries, initialBackoffMs: config.initialBackoffMs }); } async init() { await this.#db.createTable({ tableName: storage.TABLE_NOTIFICATIONS, schema: storage.TABLE_SCHEMAS[storage.TABLE_NOTIFICATIONS] }); await this.#client.batch( [ { sql: `CREATE INDEX IF NOT EXISTS idx_notifications_thread_status_updated ON "${storage.TABLE_NOTIFICATIONS}" ("threadId", "status", "updatedAt")`, args: [] }, { sql: `CREATE INDEX IF NOT EXISTS idx_notifications_coalescing ON "${storage.TABLE_NOTIFICATIONS}" ("threadId", "source", "kind", "status", "agentId", "resourceId", "dedupeKey", "coalesceKey")`, args: [] }, { sql: `CREATE INDEX IF NOT EXISTS idx_notifications_due ON "${storage.TABLE_NOTIFICATIONS}" ("status", "deliverAt", "summaryAt")`, args: [] } ], "write" ); } async dangerouslyClearAll() { await this.#db.deleteData({ tableName: storage.TABLE_NOTIFICATIONS }); } async createNotification(input) { const existing = await this.findCoalescable(input); if (existing) { const now2 = /* @__PURE__ */ new Date(); const attributes = input.attributes ? { ...cloneValue(existing.attributes), ...cloneValue(input.attributes) } : cloneValue(existing.attributes); const metadata = input.metadata ? { ...cloneValue(existing.metadata), ...cloneValue(input.metadata) } : cloneValue(existing.metadata); await this.#db.update({ tableName: storage.TABLE_NOTIFICATIONS, keys: { threadId: existing.threadId, id: existing.id }, data: { summary: input.summary, payload: cloneValue(input.payload ?? existing.payload) ?? null, priority: input.priority ?? existing.priority, attributes: attributes ?? null, updatedAt: now2, deliverAt: input.deliverAt ?? existing.deliverAt ?? null, summaryAt: input.summaryAt ?? existing.summaryAt ?? null, deliveryReason: input.deliveryReason ?? existing.deliveryReason ?? null, coalescedCount: (existing.coalescedCount ?? 1) + 1, metadata: metadata ?? null } }); const updated = await this.getNotification({ threadId: existing.threadId, id: existing.id }); if (!updated) throw new Error(`Notification ${existing.id} was not found for thread ${existing.threadId}`); return updated; } const now = input.createdAt ?? /* @__PURE__ */ new Date(); const record = { id: input.id ?? crypto$1.randomUUID(), threadId: input.threadId, source: input.source, kind: input.kind, priority: input.priority ?? "medium", status: "pending", summary: input.summary, payload: cloneValue(input.payload), resourceId: input.resourceId, agentId: input.agentId, sourceId: input.sourceId, dedupeKey: input.dedupeKey, coalesceKey: input.coalesceKey, coalescedCount: 1, attributes: cloneValue(input.attributes), createdAt: now, updatedAt: now, deliverAt: input.deliverAt, summaryAt: input.summaryAt, deliveryReason: input.deliveryReason, deliveryAttempts: 0, metadata: cloneValue(input.metadata) }; await this.#db.insert({ tableName: storage.TABLE_NOTIFICATIONS, record: { ...record, payload: record.payload ?? null, attributes: record.attributes ?? null, metadata: record.metadata ?? null, resourceId: record.resourceId ?? null, agentId: record.agentId ?? null, sourceId: record.sourceId ?? null, dedupeKey: record.dedupeKey ?? null, coalesceKey: record.coalesceKey ?? null, deliverAt: record.deliverAt ?? null, summaryAt: record.summaryAt ?? null, deliveryReason: record.deliveryReason ?? null, deliveryAttempts: record.deliveryAttempts ?? 0 } }); return record; } async listNotifications(input) { const conditions = ['"threadId" = ?']; const args = [input.threadId]; addArrayFilter(conditions, args, "status", input.status); addArrayFilter(conditions, args, "priority", input.priority); if (input.source) { conditions.push('"source" = ?'); args.push(input.source); } if (input.resourceId) { conditions.push('"resourceId" = ?'); args.push(input.resourceId); } if (input.agentId) { conditions.push('"agentId" = ?'); args.push(input.agentId); } if (input.search) { conditions.push('(LOWER("summary") LIKE ? OR LOWER("kind") LIKE ?)'); const search = `%${input.search.toLowerCase()}%`; args.push(search, search); } const limit = input.limit ? " LIMIT ?" : ""; if (input.limit) args.push(input.limit); const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_NOTIFICATIONS)} FROM "${storage.TABLE_NOTIFICATIONS}" WHERE ${conditions.join(" AND ")} ORDER BY "updatedAt" DESC${limit}`, args }); return (result.rows ?? []).map((row) => rowToNotification(row)); } async listDueNotifications(input) { const now = input.now.toISOString(); const conditions = [ '"status" = ?', '(("deliverAt" IS NOT NULL AND "deliverAt" <= ?) OR ("summaryAt" IS NOT NULL AND "summaryAt" <= ?))' ]; const args = ["pending", now, now]; if (input.agentId) { conditions.push('"agentId" = ?'); args.push(input.agentId); } if (input.resourceId) { conditions.push('"resourceId" = ?'); args.push(input.resourceId); } const limit = input.limit ? " LIMIT ?" : ""; if (input.limit) args.push(input.limit); const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_NOTIFICATIONS)} FROM "${storage.TABLE_NOTIFICATIONS}" WHERE ${conditions.join(" AND ")} ORDER BY CASE WHEN "deliverAt" IS NULL THEN "summaryAt" WHEN "summaryAt" IS NULL THEN "deliverAt" WHEN "deliverAt" <= "summaryAt" THEN "deliverAt" ELSE "summaryAt" END ASC, "updatedAt" ASC${limit}`, args }); return (result.rows ?? []).map((row) => rowToNotification(row)); } async getNotification(input) { const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_NOTIFICATIONS)} FROM "${storage.TABLE_NOTIFICATIONS}" WHERE "threadId" = ? AND "id" = ? LIMIT 1`, args: [input.threadId, input.id] }); const row = result.rows?.[0]; return row ? rowToNotification(row) : null; } async updateNotification(input) { const existing = await this.getNotification({ threadId: input.threadId, id: input.id }); if (!existing) { throw new Error(`Notification ${input.id} was not found for thread ${input.threadId}`); } const now = /* @__PURE__ */ new Date(); await this.#db.update({ tableName: storage.TABLE_NOTIFICATIONS, keys: { threadId: input.threadId, id: input.id }, data: { ...input.status ? { status: input.status, ...statusTimestamp(input.status, now) } : {}, ...input.summary !== void 0 ? { summary: input.summary } : {}, ...input.payload !== void 0 ? { payload: cloneValue(input.payload) } : {}, ...input.attributes !== void 0 ? { attributes: cloneValue(input.attributes) } : {}, ...input.metadata !== void 0 ? { metadata: cloneValue(input.metadata) } : {}, ...input.deliverAt !== void 0 ? { deliverAt: input.deliverAt } : {}, ...input.summaryAt !== void 0 ? { summaryAt: input.summaryAt } : {}, ...input.deliveryReason !== void 0 ? { deliveryReason: input.deliveryReason } : {}, ...input.deliveryAttempts !== void 0 ? { deliveryAttempts: input.deliveryAttempts } : {}, ...input.lastDeliveryAttemptAt !== void 0 ? { lastDeliveryAttemptAt: input.lastDeliveryAttemptAt } : {}, ...input.lastDeliveryError !== void 0 ? { lastDeliveryError: input.lastDeliveryError } : {}, ...input.deliveredSignalId !== void 0 ? { deliveredSignalId: input.deliveredSignalId } : {}, ...input.summarySignalId !== void 0 ? { summarySignalId: input.summarySignalId } : {}, updatedAt: now } }); const updated = await this.getNotification({ threadId: input.threadId, id: input.id }); if (!updated) throw new Error(`Notification ${input.id} was not found for thread ${input.threadId}`); return updated; } async findCoalescable(input) { if (!input.dedupeKey && !input.coalesceKey) return void 0; const agentId = input.agentId ?? null; const resourceId = input.resourceId ?? null; const dedupeKey = input.dedupeKey ?? null; const coalesceKey = input.coalesceKey ?? null; const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_NOTIFICATIONS)} FROM "${storage.TABLE_NOTIFICATIONS}" WHERE "threadId" = ? AND "source" = ? AND "kind" = ? AND "status" = ? AND (("agentId" = ?) OR ("agentId" IS NULL AND ? IS NULL)) AND (("resourceId" = ?) OR ("resourceId" IS NULL AND ? IS NULL)) AND ((? IS NOT NULL AND "dedupeKey" = ?) OR (? IS NOT NULL AND "coalesceKey" = ?)) ORDER BY "updatedAt" DESC LIMIT 1`, args: [ input.threadId, input.source, input.kind, "pending", agentId, agentId, resourceId, resourceId, dedupeKey, dedupeKey, coalesceKey, coalesceKey ] }); const row = result.rows?.[0]; return row ? rowToNotification(row) : void 0; } }; var ObservabilityLibSQL = class extends storage.ObservabilityStorage { #db; constructor(config) { super(); const client = resolveClient(config); this.#db = new LibSQLDB({ client, maxRetries: config.maxRetries, initialBackoffMs: config.initialBackoffMs }); } async init() { await this.#db.createTable({ tableName: storage.TABLE_SPANS, schema: storage.SPAN_SCHEMA }); await this.#db.alterTable({ tableName: storage.TABLE_SPANS, schema: storage.SPAN_SCHEMA, ifNotExists: ["requestContext"] }); } async dangerouslyClearAll() { await this.#db.deleteData({ tableName: storage.TABLE_SPANS }); } /** * Manually run the spans migration to deduplicate and add the unique constraint. * This is intended to be called from the CLI when duplicates are detected. * * @returns Migration result with status and details */ async migrateSpans() { return this.#db.migrateSpans(); } /** * Check migration status for the spans table. * Returns information about whether migration is needed. */ async checkSpansMigrationStatus() { return this.#db.checkSpansMigrationStatus(); } get tracingStrategy() { return { preferred: "batch-with-updates", supported: ["batch-with-updates", "insert-only"] }; } async createSpan(args) { const { span } = args; try { const startedAt = span.startedAt instanceof Date ? span.startedAt.toISOString() : span.startedAt; const endedAt = span.endedAt instanceof Date ? span.endedAt.toISOString() : span.endedAt; const now = (/* @__PURE__ */ new Date()).toISOString(); const record = { ...span, startedAt, endedAt, createdAt: now, updatedAt: now }; return this.#db.insert({ tableName: storage.TABLE_SPANS, record }); } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "CREATE_SPAN", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { spanId: span.spanId, traceId: span.traceId, spanType: span.spanType, name: span.name } }, error$1 ); } } async getSpan(args) { const { traceId, spanId } = args; try { const rows = await this.#db.selectMany({ tableName: storage.TABLE_SPANS, whereClause: { sql: " WHERE traceId = ? AND spanId = ?", args: [traceId, spanId] }, limit: 1 }); if (!rows || rows.length === 0) { return null; } return { span: transformFromSqlRow({ tableName: storage.TABLE_SPANS, sqlRow: rows[0] }) }; } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_SPAN", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { traceId, spanId } }, error$1 ); } } async getRootSpan(args) { const { traceId } = args; try { const rows = await this.#db.selectMany({ tableName: storage.TABLE_SPANS, whereClause: { sql: " WHERE traceId = ? AND parentSpanId IS NULL", args: [traceId] }, limit: 1 }); if (!rows || rows.length === 0) { return null; } return { span: transformFromSqlRow({ tableName: storage.TABLE_SPANS, sqlRow: rows[0] }) }; } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_ROOT_SPAN", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { traceId } }, error$1 ); } } async getTrace(args) { const { traceId } = args; try { const spans = await this.#db.selectMany({ tableName: storage.TABLE_SPANS, whereClause: { sql: " WHERE traceId = ?", args: [traceId] }, orderBy: "startedAt ASC" }); if (!spans || spans.length === 0) { return null; } return { traceId, spans: spans.map((span) => transformFromSqlRow({ tableName: storage.TABLE_SPANS, sqlRow: span })) }; } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_TRACE", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { traceId } }, error$1 ); } } async getTraceLight(args) { const { traceId } = args; try { const spans = await this.#db.selectMany({ tableName: storage.TABLE_SPANS, whereClause: { sql: " WHERE traceId = ?", args: [traceId] }, orderBy: "startedAt ASC" }); if (!spans || spans.length === 0) { return null; } return { traceId, spans: spans.map((span) => { const transformed = transformFromSqlRow({ tableName: storage.TABLE_SPANS, sqlRow: span }); const { input, output, attributes, metadata, tags, links, ...light } = transformed; return light; }) }; } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_TRACE_LIGHT", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { traceId } }, error$1 ); } } async updateSpan(args) { const { traceId, spanId, updates } = args; try { const data = { ...updates }; if (data.endedAt instanceof Date) { data.endedAt = data.endedAt.toISOString(); } if (data.startedAt instanceof Date) { data.startedAt = data.startedAt.toISOString(); } data.updatedAt = (/* @__PURE__ */ new Date()).toISOString(); await this.#db.update({ tableName: storage.TABLE_SPANS, keys: { spanId, traceId }, data }); } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "UPDATE_SPAN", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { spanId, traceId } }, error$1 ); } } async listTraces(args) { const { filters, pagination, orderBy } = storage.listTracesArgsSchema.parse(args); const page = pagination?.page ?? 0; const perPage = pagination?.perPage ?? 10; const tableName = utils.parseSqlIdentifier(storage.TABLE_SPANS, "table name"); try { const conditions = ["parentSpanId IS NULL"]; const queryArgs = []; if (filters) { if (filters.startedAt?.start) { conditions.push(`startedAt >= ?`); queryArgs.push(filters.startedAt.start.toISOString()); } if (filters.startedAt?.end) { conditions.push(`startedAt <= ?`); queryArgs.push(filters.startedAt.end.toISOString()); } if (filters.endedAt?.start) { conditions.push(`endedAt >= ?`); queryArgs.push(filters.endedAt.start.toISOString()); } if (filters.endedAt?.end) { conditions.push(`endedAt <= ?`); queryArgs.push(filters.endedAt.end.toISOString()); } if (filters.spanType !== void 0) { conditions.push(`spanType = ?`); queryArgs.push(filters.spanType); } if (filters.entityType !== void 0) { conditions.push(`entityType = ?`); queryArgs.push(filters.entityType); } if (filters.entityId !== void 0) { conditions.push(`entityId = ?`); queryArgs.push(filters.entityId); } if (filters.entityName !== void 0) { conditions.push(`entityName = ?`); queryArgs.push(filters.entityName); } if (filters.userId !== void 0) { conditions.push(`userId = ?`); queryArgs.push(filters.userId); } if (filters.organizationId !== void 0) { conditions.push(`organizationId = ?`); queryArgs.push(filters.organizationId); } if (filters.resourceId !== void 0) { conditions.push(`resourceId = ?`); queryArgs.push(filters.resourceId); } if (filters.runId !== void 0) { conditions.push(`runId = ?`); queryArgs.push(filters.runId); } if (filters.sessionId !== void 0) { conditions.push(`sessionId = ?`); queryArgs.push(filters.sessionId); } if (filters.threadId !== void 0) { conditions.push(`threadId = ?`); queryArgs.push(filters.threadId); } if (filters.requestId !== void 0) { conditions.push(`requestId = ?`); queryArgs.push(filters.requestId); } if (filters.environment !== void 0) { conditions.push(`environment = ?`); queryArgs.push(filters.environment); } if (filters.source !== void 0) { conditions.push(`source = ?`); queryArgs.push(filters.source); } if (filters.serviceName !== void 0) { conditions.push(`serviceName = ?`); queryArgs.push(filters.serviceName); } if (filters.scope != null) { for (const [key, value] of Object.entries(filters.scope)) { if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key)) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "LIST_TRACES", "INVALID_FILTER_KEY"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { key } }); } conditions.push(`json_extract(scope, '$.${key}') = ?`); queryArgs.push(typeof value === "string" ? value : JSON.stringify(value)); } } if (filters.metadata != null) { for (const [key, value] of Object.entries(filters.metadata)) { if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key)) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "LIST_TRACES", "INVALID_FILTER_KEY"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { key } }); } conditions.push(`json_extract(metadata, '$.${key}') = ?`); queryArgs.push(typeof value === "string" ? value : JSON.stringify(value)); } } if (filters.tags != null && filters.tags.length > 0) { for (const tag of filters.tags) { conditions.push(`EXISTS (SELECT 1 FROM json_each(${tableName}.tags) WHERE value = ?)`); queryArgs.push(tag); } } if (filters.status !== void 0) { switch (filters.status) { case storage.TraceStatus.ERROR: conditions.push(`error IS NOT NULL`); break; case storage.TraceStatus.RUNNING: conditions.push(`endedAt IS NULL AND error IS NULL`); break; case storage.TraceStatus.SUCCESS: conditions.push(`endedAt IS NOT NULL AND error IS NULL`); break; } } if (filters.hasChildError !== void 0) { if (filters.hasChildError) { conditions.push(`EXISTS ( SELECT 1 FROM ${tableName} c WHERE c.traceId = ${tableName}.traceId AND c.error IS NOT NULL )`); } else { conditions.push(`NOT EXISTS ( SELECT 1 FROM ${tableName} c WHERE c.traceId = ${tableName}.traceId AND c.error IS NOT NULL )`); } } } const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : ""; const sortField = orderBy?.field ?? "startedAt"; const sortDirection = orderBy?.direction ?? "DESC"; let orderByClause; if (sortField === "endedAt") { 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`; } else { orderByClause = `${sortField} ${sortDirection}`; } const count = await this.#db.selectTotalCount({ tableName: storage.TABLE_SPANS, whereClause: { sql: whereClause, args: queryArgs } }); if (count === 0) { return { pagination: { total: 0, page, perPage, hasMore: false }, spans: [] }; } const spans = await this.#db.selectMany({ tableName: storage.TABLE_SPANS, whereClause: { sql: whereClause, args: queryArgs }, orderBy: orderByClause, offset: page * perPage, limit: perPage }); return { pagination: { total: count, page, perPage, hasMore: (page + 1) * perPage < count }, spans: storage.toTraceSpans( spans.map((span) => transformFromSqlRow({ tableName: storage.TABLE_SPANS, sqlRow: span })) ) }; } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_TRACES", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER }, error$1 ); } } async batchCreateSpans(args) { try { const now = (/* @__PURE__ */ new Date()).toISOString(); const records = args.records.map((record) => { const startedAt = record.startedAt instanceof Date ? record.startedAt.toISOString() : record.startedAt; const endedAt = record.endedAt instanceof Date ? record.endedAt.toISOString() : record.endedAt; return { ...record, startedAt, endedAt, createdAt: now, updatedAt: now }; }); return this.#db.batchInsert({ tableName: storage.TABLE_SPANS, records }); } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "BATCH_CREATE_SPANS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER }, error$1 ); } } async batchUpdateSpans(args) { const now = (/* @__PURE__ */ new Date()).toISOString(); try { return this.#db.batchUpdate({ tableName: storage.TABLE_SPANS, updates: args.records.map((record) => { const data = { ...record.updates }; if (data.endedAt instanceof Date) { data.endedAt = data.endedAt.toISOString(); } if (data.startedAt instanceof Date) { data.startedAt = data.startedAt.toISOString(); } data.updatedAt = now; return { keys: { spanId: record.spanId, traceId: record.traceId }, data }; }) }); } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "BATCH_UPDATE_SPANS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER }, error$1 ); } } async batchDeleteTraces(args) { try { const keys = args.traceIds.map((traceId) => ({ traceId })); return this.#db.batchDelete({ tableName: storage.TABLE_SPANS, keys }); } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "BATCH_DELETE_TRACES", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER }, error$1 ); } } }; var PromptBlocksLibSQL = class extends storage.PromptBlocksStorage { #db; #client; constructor(config) { super(); const client = resolveClient(config); this.#client = client; this.#db = new LibSQLDB({ client, maxRetries: config.maxRetries, initialBackoffMs: config.initialBackoffMs }); } async init() { await this.#db.createTable({ tableName: storage.TABLE_PROMPT_BLOCKS, schema: storage.PROMPT_BLOCKS_SCHEMA }); await this.#db.createTable({ tableName: storage.TABLE_PROMPT_BLOCK_VERSIONS, schema: storage.PROMPT_BLOCK_VERSIONS_SCHEMA }); await this.#db.alterTable({ tableName: storage.TABLE_PROMPT_BLOCK_VERSIONS, schema: storage.PROMPT_BLOCK_VERSIONS_SCHEMA, ifNotExists: ["requestContextSchema"] }); await this.#client.execute( `CREATE UNIQUE INDEX IF NOT EXISTS idx_prompt_block_versions_block_version ON "${storage.TABLE_PROMPT_BLOCK_VERSIONS}" ("blockId", "versionNumber")` ); } async dangerouslyClearAll() { await this.#db.deleteData({ tableName: storage.TABLE_PROMPT_BLOCKS }); await this.#db.deleteData({ tableName: storage.TABLE_PROMPT_BLOCK_VERSIONS }); } // ========================================================================== // Prompt Block CRUD // ========================================================================== async getById(id) { try { const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_PROMPT_BLOCKS)} FROM "${storage.TABLE_PROMPT_BLOCKS}" WHERE id = ?`, args: [id] }); const row = result.rows?.[0]; return row ? this.#parseBlockRow(row) : null; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_PROMPT_BLOCK", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async create(input) { const { promptBlock } = input; try { const now = /* @__PURE__ */ new Date(); await this.#db.insert({ tableName: storage.TABLE_PROMPT_BLOCKS, record: { id: promptBlock.id, status: "draft", activeVersionId: null, authorId: promptBlock.authorId ?? null, metadata: promptBlock.metadata ?? null, createdAt: now.toISOString(), updatedAt: now.toISOString() } }); const { id: _id, authorId: _authorId, metadata: _metadata, ...snapshotConfig } = promptBlock; const versionId = crypto.randomUUID(); await this.createVersion({ id: versionId, blockId: promptBlock.id, versionNumber: 1, ...snapshotConfig, changedFields: Object.keys(snapshotConfig), changeMessage: "Initial version" }); return { id: promptBlock.id, status: "draft", activeVersionId: void 0, authorId: promptBlock.authorId, metadata: promptBlock.metadata, createdAt: now, updatedAt: now }; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "CREATE_PROMPT_BLOCK", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async update(input) { const { id, ...updates } = input; try { const existing = await this.getById(id); if (!existing) { throw new Error(`Prompt block with id ${id} not found`); } const { authorId, activeVersionId, metadata, status } = updates; const updateData = { updatedAt: (/* @__PURE__ */ new Date()).toISOString() }; if (authorId !== void 0) updateData.authorId = authorId; if (activeVersionId !== void 0) updateData.activeVersionId = activeVersionId; if (status !== void 0) updateData.status = status; if (metadata !== void 0) { updateData.metadata = { ...existing.metadata, ...metadata }; } await this.#db.update({ tableName: storage.TABLE_PROMPT_BLOCKS, keys: { id }, data: updateData }); const updated = await this.getById(id); if (!updated) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "UPDATE_PROMPT_BLOCK", "NOT_FOUND_AFTER_UPDATE"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.SYSTEM, text: `Prompt block ${id} not found after update`, details: { id } }); } return updated; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "UPDATE_PROMPT_BLOCK", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async delete(id) { try { await this.deleteVersionsByParentId(id); await this.#client.execute({ sql: `DELETE FROM "${storage.TABLE_PROMPT_BLOCKS}" WHERE "id" = ?`, args: [id] }); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "DELETE_PROMPT_BLOCK", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async list(args) { try { const { page = 0, perPage: perPageInput, orderBy, authorId, metadata, status } = args || {}; const { field, direction } = this.parseOrderBy(orderBy); const conditions = []; const queryParams = []; if (status) { conditions.push("status = ?"); queryParams.push(status); } if (authorId !== void 0) { conditions.push("authorId = ?"); queryParams.push(authorId); } if (metadata && Object.keys(metadata).length > 0) { for (const [key, value] of Object.entries(metadata)) { if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key)) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "LIST_PROMPT_BLOCKS", "INVALID_METADATA_KEY"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, text: `Invalid metadata key: ${key}. Keys must be alphanumeric with underscores.`, details: { key } }); } conditions.push(`json_extract(metadata, '$.${key}') = ?`); queryParams.push(typeof value === "string" ? value : JSON.stringify(value)); } } const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : ""; const countResult = await this.#client.execute({ sql: `SELECT COUNT(*) as count FROM "${storage.TABLE_PROMPT_BLOCKS}" ${whereClause}`, args: queryParams }); const total = Number(countResult.rows?.[0]?.count ?? 0); if (total === 0) { return { promptBlocks: [], total: 0, page, perPage: perPageInput ?? 100, hasMore: false }; } const perPage = storage.normalizePerPage(perPageInput, 100); const { offset: start, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage); const limitValue = perPageInput === false ? total : perPage; const end = perPageInput === false ? total : start + perPage; const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_PROMPT_BLOCKS)} FROM "${storage.TABLE_PROMPT_BLOCKS}" ${whereClause} ORDER BY ${field} ${direction} LIMIT ? OFFSET ?`, args: [...queryParams, limitValue, start] }); const promptBlocks = result.rows?.map((row) => this.#parseBlockRow(row)) ?? []; return { promptBlocks, total, page, perPage: perPageForResponse, hasMore: end < total }; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_PROMPT_BLOCKS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } // ========================================================================== // Prompt Block Version Methods // ========================================================================== async createVersion(input) { try { const now = /* @__PURE__ */ new Date(); await this.#db.insert({ tableName: storage.TABLE_PROMPT_BLOCK_VERSIONS, record: { id: input.id, blockId: input.blockId, versionNumber: input.versionNumber, name: input.name, description: input.description ?? null, content: input.content, rules: input.rules ?? null, requestContextSchema: input.requestContextSchema ?? null, changedFields: input.changedFields ?? null, changeMessage: input.changeMessage ?? null, createdAt: now.toISOString() } }); return { ...input, createdAt: now }; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "CREATE_PROMPT_BLOCK_VERSION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async getVersion(id) { try { const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_PROMPT_BLOCK_VERSIONS)} FROM "${storage.TABLE_PROMPT_BLOCK_VERSIONS}" WHERE id = ?`, args: [id] }); const row = result.rows?.[0]; return row ? this.#parseVersionRow(row) : null; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_PROMPT_BLOCK_VERSION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async getVersionByNumber(blockId, versionNumber) { try { const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_PROMPT_BLOCK_VERSIONS)} FROM "${storage.TABLE_PROMPT_BLOCK_VERSIONS}" WHERE blockId = ? AND versionNumber = ?`, args: [blockId, versionNumber] }); const row = result.rows?.[0]; return row ? this.#parseVersionRow(row) : null; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_PROMPT_BLOCK_VERSION_BY_NUMBER", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async getLatestVersion(blockId) { try { const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_PROMPT_BLOCK_VERSIONS)} FROM "${storage.TABLE_PROMPT_BLOCK_VERSIONS}" WHERE blockId = ? ORDER BY versionNumber DESC LIMIT 1`, args: [blockId] }); const row = result.rows?.[0]; return row ? this.#parseVersionRow(row) : null; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_LATEST_PROMPT_BLOCK_VERSION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async listVersions(input) { try { const { blockId, page = 0, perPage: perPageInput, orderBy } = input; const { field, direction } = this.parseVersionOrderBy(orderBy); const countResult = await this.#client.execute({ sql: `SELECT COUNT(*) as count FROM "${storage.TABLE_PROMPT_BLOCK_VERSIONS}" WHERE blockId = ?`, args: [blockId] }); const total = Number(countResult.rows?.[0]?.count ?? 0); if (total === 0) { return { versions: [], total: 0, page, perPage: perPageInput ?? 20, hasMore: false }; } const perPage = storage.normalizePerPage(perPageInput, 20); const { offset: start, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage); const limitValue = perPageInput === false ? total : perPage; const end = perPageInput === false ? total : start + perPage; const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_PROMPT_BLOCK_VERSIONS)} FROM "${storage.TABLE_PROMPT_BLOCK_VERSIONS}" WHERE blockId = ? ORDER BY ${field} ${direction} LIMIT ? OFFSET ?`, args: [blockId, limitValue, start] }); const versions = result.rows?.map((row) => this.#parseVersionRow(row)) ?? []; return { versions, total, page, perPage: perPageForResponse, hasMore: end < total }; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_PROMPT_BLOCK_VERSIONS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async deleteVersion(id) { try { await this.#client.execute({ sql: `DELETE FROM "${storage.TABLE_PROMPT_BLOCK_VERSIONS}" WHERE "id" = ?`, args: [id] }); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "DELETE_PROMPT_BLOCK_VERSION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async deleteVersionsByParentId(entityId) { try { await this.#client.execute({ sql: `DELETE FROM "${storage.TABLE_PROMPT_BLOCK_VERSIONS}" WHERE "blockId" = ?`, args: [entityId] }); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "DELETE_PROMPT_BLOCK_VERSIONS_BY_BLOCK", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async countVersions(blockId) { try { const result = await this.#client.execute({ sql: `SELECT COUNT(*) as count FROM "${storage.TABLE_PROMPT_BLOCK_VERSIONS}" WHERE blockId = ?`, args: [blockId] }); return Number(result.rows?.[0]?.count ?? 0); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "COUNT_PROMPT_BLOCK_VERSIONS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } // ========================================================================== // Private Helpers // ========================================================================== #parseBlockRow(row) { const safeParseJSON = (val) => { if (val === null || val === void 0) return void 0; if (typeof val === "string") { try { return JSON.parse(val); } catch { return val; } } return val; }; return { id: row.id, status: row.status ?? "draft", activeVersionId: row.activeVersionId ?? void 0, authorId: row.authorId ?? void 0, metadata: safeParseJSON(row.metadata), createdAt: new Date(row.createdAt), updatedAt: new Date(row.updatedAt) }; } #parseVersionRow(row) { const safeParseJSON = (val) => { if (val === null || val === void 0) return void 0; if (typeof val === "string") { try { return JSON.parse(val); } catch { return val; } } return val; }; return { id: row.id, blockId: row.blockId, versionNumber: Number(row.versionNumber), name: row.name, description: row.description ?? void 0, content: row.content, rules: safeParseJSON(row.rules), requestContextSchema: safeParseJSON(row.requestContextSchema), changedFields: safeParseJSON(row.changedFields), changeMessage: row.changeMessage ?? void 0, createdAt: new Date(row.createdAt) }; } }; function parseJson3(val) { if (val == null) return void 0; if (typeof val === "string") { try { return JSON.parse(val); } catch { return val; } } return val; } function toNumber(val) { if (typeof val === "bigint") return Number(val); return Number(val); } function rowToSchedule(row) { const target = parseJson3(row.target); if (!target) { throw new Error(`Schedule row ${row.id} has invalid target`); } const schedule = { id: String(row.id), target, cron: String(row.cron), status: String(row.status), nextFireAt: toNumber(row.next_fire_at), createdAt: toNumber(row.created_at), updatedAt: toNumber(row.updated_at) }; if (row.timezone != null) schedule.timezone = String(row.timezone); if (row.last_fire_at != null) schedule.lastFireAt = toNumber(row.last_fire_at); if (row.last_run_id != null) schedule.lastRunId = String(row.last_run_id); const metadata = parseJson3(row.metadata); if (metadata !== void 0) schedule.metadata = metadata; if (row.owner_type != null) schedule.ownerType = String(row.owner_type); if (row.owner_id != null) schedule.ownerId = String(row.owner_id); return schedule; } function rowToTrigger(row) { const trigger = { id: row.id != null ? String(row.id) : void 0, scheduleId: String(row.schedule_id), runId: row.run_id != null ? String(row.run_id) : null, scheduledFireAt: toNumber(row.scheduled_fire_at), actualFireAt: toNumber(row.actual_fire_at), outcome: String(row.outcome), triggerKind: row.trigger_kind != null ? String(row.trigger_kind) : "schedule-fire" }; if (row.error != null) trigger.error = String(row.error); if (row.parent_trigger_id != null) trigger.parentTriggerId = String(row.parent_trigger_id); const metadata = parseJson3(row.metadata); if (metadata !== void 0) trigger.metadata = metadata; return trigger; } var SchedulesLibSQL = class extends storage.SchedulesStorage { #db; #client; constructor(config) { super(); const client = resolveClient(config); this.#client = client; this.#db = new LibSQLDB({ client, maxRetries: config.maxRetries, initialBackoffMs: config.initialBackoffMs }); } async init() { await this.#db.createTable({ tableName: storage.TABLE_SCHEDULES, schema: storage.TABLE_SCHEMAS[storage.TABLE_SCHEDULES] }); await this.#db.createTable({ tableName: storage.TABLE_SCHEDULE_TRIGGERS, schema: storage.TABLE_SCHEMAS[storage.TABLE_SCHEDULE_TRIGGERS] }); await this.#client.batch( [ { sql: `CREATE INDEX IF NOT EXISTS idx_schedules_status_next_fire ON "${storage.TABLE_SCHEDULES}" ("status", "next_fire_at")`, args: [] }, { sql: `CREATE INDEX IF NOT EXISTS idx_schedule_triggers_schedule_fire ON "${storage.TABLE_SCHEDULE_TRIGGERS}" ("schedule_id", "actual_fire_at")`, args: [] } ], "write" ); } async dangerouslyClearAll() { await this.#db.deleteData({ tableName: storage.TABLE_SCHEDULE_TRIGGERS }); await this.#db.deleteData({ tableName: storage.TABLE_SCHEDULES }); } async createSchedule(schedule) { const existing = await this.getSchedule(schedule.id); if (existing) { throw new Error(`Schedule with id "${schedule.id}" already exists`); } await this.#db.insert({ tableName: storage.TABLE_SCHEDULES, record: { id: schedule.id, target: schedule.target, cron: schedule.cron, timezone: schedule.timezone ?? null, status: schedule.status, next_fire_at: schedule.nextFireAt, last_fire_at: schedule.lastFireAt ?? null, last_run_id: schedule.lastRunId ?? null, created_at: schedule.createdAt, updated_at: schedule.updatedAt, metadata: schedule.metadata ?? null, owner_type: schedule.ownerType ?? null, owner_id: schedule.ownerId ?? null } }); return schedule; } async getSchedule(id) { const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_SCHEDULES)} FROM ${storage.TABLE_SCHEDULES} WHERE id = ?`, args: [id] }); const row = result.rows[0]; return row ? rowToSchedule(row) : null; } async listSchedules(filter) { const conditions = []; const params = []; if (filter?.status) { conditions.push("status = ?"); params.push(filter.status); } if (filter?.workflowId) { conditions.push("json_extract(target, '$.workflowId') = ?"); params.push(filter.workflowId); } if (filter?.ownerType !== void 0) { if (filter.ownerType === null) { conditions.push("owner_type IS NULL"); } else { conditions.push("owner_type = ?"); params.push(filter.ownerType); } } if (filter?.ownerId !== void 0) { if (filter.ownerId === null) { conditions.push("owner_id IS NULL"); } else { conditions.push("owner_id = ?"); params.push(filter.ownerId); } } const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : ""; const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_SCHEDULES)} FROM ${storage.TABLE_SCHEDULES} ${where} ORDER BY created_at ASC`, args: params }); return result.rows.map((r) => rowToSchedule(r)); } async listDueSchedules(now, limit) { const cap = limit ?? 100; const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_SCHEDULES)} FROM ${storage.TABLE_SCHEDULES} WHERE status = ? AND next_fire_at <= ? ORDER BY next_fire_at ASC LIMIT ?`, args: ["active", now, cap] }); return result.rows.map((r) => rowToSchedule(r)); } async updateSchedule(id, patch) { const setClauses = []; const params = []; if ("cron" in patch && patch.cron !== void 0) { setClauses.push("cron = ?"); params.push(patch.cron); } if ("timezone" in patch) { setClauses.push("timezone = ?"); params.push(patch.timezone ?? null); } if ("status" in patch && patch.status !== void 0) { setClauses.push("status = ?"); params.push(patch.status); } if ("nextFireAt" in patch && patch.nextFireAt !== void 0) { setClauses.push("next_fire_at = ?"); params.push(patch.nextFireAt); } if ("target" in patch && patch.target !== void 0) { setClauses.push("target = jsonb(?)"); params.push(JSON.stringify(patch.target)); } if ("metadata" in patch) { setClauses.push("metadata = jsonb(?)"); params.push(patch.metadata != null ? JSON.stringify(patch.metadata) : null); } if ("ownerType" in patch) { setClauses.push("owner_type = ?"); params.push(patch.ownerType ?? null); } if ("ownerId" in patch) { setClauses.push("owner_id = ?"); params.push(patch.ownerId ?? null); } setClauses.push("updated_at = ?"); params.push(Date.now()); params.push(id); if (setClauses.length === 1) { const existing = await this.getSchedule(id); if (!existing) throw new Error(`Schedule ${id} not found`); return existing; } await this.#client.execute({ sql: `UPDATE ${storage.TABLE_SCHEDULES} SET ${setClauses.join(", ")} WHERE id = ?`, args: params }); const updated = await this.getSchedule(id); if (!updated) throw new Error(`Schedule ${id} not found`); return updated; } async updateScheduleNextFire(id, expectedNextFireAt, newNextFireAt, lastFireAt, lastRunId) { const result = await this.#client.execute({ sql: `UPDATE ${storage.TABLE_SCHEDULES} SET next_fire_at = ?, last_fire_at = ?, last_run_id = ?, updated_at = ? WHERE id = ? AND next_fire_at = ? AND status = ?`, args: [newNextFireAt, lastFireAt, lastRunId, Date.now(), id, expectedNextFireAt, "active"] }); return (result.rowsAffected ?? 0) > 0; } async deleteSchedule(id) { await this.#client.execute({ sql: `DELETE FROM ${storage.TABLE_SCHEDULE_TRIGGERS} WHERE schedule_id = ?`, args: [id] }); await this.#client.execute({ sql: `DELETE FROM ${storage.TABLE_SCHEDULES} WHERE id = ?`, args: [id] }); } async recordTrigger(trigger) { const id = trigger.id ?? crypto.randomUUID(); await this.#db.insert({ tableName: storage.TABLE_SCHEDULE_TRIGGERS, record: { id, schedule_id: trigger.scheduleId, run_id: trigger.runId, scheduled_fire_at: trigger.scheduledFireAt, actual_fire_at: trigger.actualFireAt, outcome: trigger.outcome, error: trigger.error ?? null, trigger_kind: trigger.triggerKind ?? "schedule-fire", parent_trigger_id: trigger.parentTriggerId ?? null, metadata: trigger.metadata ?? null } }); } async listTriggers(scheduleId, opts) { const conditions = ["schedule_id = ?"]; const params = [scheduleId]; if (opts?.fromActualFireAt != null) { conditions.push("actual_fire_at >= ?"); params.push(opts.fromActualFireAt); } if (opts?.toActualFireAt != null) { conditions.push("actual_fire_at < ?"); params.push(opts.toActualFireAt); } const limitClause = opts?.limit != null ? `LIMIT ${Math.floor(opts.limit)}` : ""; const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_SCHEDULE_TRIGGERS)} FROM ${storage.TABLE_SCHEDULE_TRIGGERS} WHERE ${conditions.join(" AND ")} ORDER BY actual_fire_at DESC ${limitClause}`, args: params }); return result.rows.map((r) => rowToTrigger(r)); } }; var ScorerDefinitionsLibSQL = class extends storage.ScorerDefinitionsStorage { #db; #client; constructor(config) { super(); const client = resolveClient(config); this.#client = client; this.#db = new LibSQLDB({ client, maxRetries: config.maxRetries, initialBackoffMs: config.initialBackoffMs }); } async init() { await this.#db.createTable({ tableName: storage.TABLE_SCORER_DEFINITIONS, schema: storage.SCORER_DEFINITIONS_SCHEMA }); await this.#db.createTable({ tableName: storage.TABLE_SCORER_DEFINITION_VERSIONS, schema: storage.SCORER_DEFINITION_VERSIONS_SCHEMA }); await this.#client.execute( `CREATE UNIQUE INDEX IF NOT EXISTS idx_scorer_definition_versions_scorer_version ON "${storage.TABLE_SCORER_DEFINITION_VERSIONS}" ("scorerDefinitionId", "versionNumber")` ); } async dangerouslyClearAll() { await this.#db.deleteData({ tableName: storage.TABLE_SCORER_DEFINITIONS }); await this.#db.deleteData({ tableName: storage.TABLE_SCORER_DEFINITION_VERSIONS }); } // ========================================================================== // Scorer Definition CRUD // ========================================================================== async getById(id) { try { const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_SCORER_DEFINITIONS)} FROM "${storage.TABLE_SCORER_DEFINITIONS}" WHERE id = ?`, args: [id] }); const row = result.rows?.[0]; return row ? this.#parseScorerRow(row) : null; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_SCORER_DEFINITION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async create(input) { const { scorerDefinition } = input; try { const now = /* @__PURE__ */ new Date(); await this.#db.insert({ tableName: storage.TABLE_SCORER_DEFINITIONS, record: { id: scorerDefinition.id, status: "draft", activeVersionId: null, authorId: scorerDefinition.authorId ?? null, metadata: scorerDefinition.metadata ?? null, createdAt: now.toISOString(), updatedAt: now.toISOString() } }); const { id: _id, authorId: _authorId, metadata: _metadata, ...snapshotConfig } = scorerDefinition; const versionId = crypto.randomUUID(); await this.createVersion({ id: versionId, scorerDefinitionId: scorerDefinition.id, versionNumber: 1, ...snapshotConfig, changedFields: Object.keys(snapshotConfig), changeMessage: "Initial version" }); return { id: scorerDefinition.id, status: "draft", activeVersionId: void 0, authorId: scorerDefinition.authorId, metadata: scorerDefinition.metadata, createdAt: now, updatedAt: now }; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "CREATE_SCORER_DEFINITION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async update(input) { const { id, ...updates } = input; try { const existing = await this.getById(id); if (!existing) { throw new Error(`Scorer definition with id ${id} not found`); } const { authorId, activeVersionId, metadata, status } = updates; const updateData = { updatedAt: (/* @__PURE__ */ new Date()).toISOString() }; if (authorId !== void 0) updateData.authorId = authorId; if (activeVersionId !== void 0) updateData.activeVersionId = activeVersionId; if (status !== void 0) updateData.status = status; if (metadata !== void 0) { updateData.metadata = { ...existing.metadata, ...metadata }; } await this.#db.update({ tableName: storage.TABLE_SCORER_DEFINITIONS, keys: { id }, data: updateData }); const updated = await this.getById(id); if (!updated) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "UPDATE_SCORER_DEFINITION", "NOT_FOUND_AFTER_UPDATE"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.SYSTEM, text: `Scorer definition ${id} not found after update`, details: { id } }); } return updated; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "UPDATE_SCORER_DEFINITION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async delete(id) { try { await this.deleteVersionsByParentId(id); await this.#client.execute({ sql: `DELETE FROM "${storage.TABLE_SCORER_DEFINITIONS}" WHERE "id" = ?`, args: [id] }); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "DELETE_SCORER_DEFINITION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async list(args) { try { const { page = 0, perPage: perPageInput, orderBy, authorId, metadata, status } = args || {}; const { field, direction } = this.parseOrderBy(orderBy); const conditions = []; const queryParams = []; if (status) { conditions.push("status = ?"); queryParams.push(status); } if (authorId !== void 0) { conditions.push("authorId = ?"); queryParams.push(authorId); } if (metadata && Object.keys(metadata).length > 0) { for (const [key, value] of Object.entries(metadata)) { if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key)) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "LIST_SCORER_DEFINITIONS", "INVALID_METADATA_KEY"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, text: `Invalid metadata key: ${key}. Keys must be alphanumeric with underscores.`, details: { key } }); } conditions.push(`json_extract(metadata, '$.${key}') = ?`); queryParams.push(typeof value === "string" ? value : JSON.stringify(value)); } } const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : ""; const countResult = await this.#client.execute({ sql: `SELECT COUNT(*) as count FROM "${storage.TABLE_SCORER_DEFINITIONS}" ${whereClause}`, args: queryParams }); const total = Number(countResult.rows?.[0]?.count ?? 0); if (total === 0) { return { scorerDefinitions: [], total: 0, page, perPage: perPageInput ?? 100, hasMore: false }; } const perPage = storage.normalizePerPage(perPageInput, 100); const { offset: start, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage); const limitValue = perPageInput === false ? total : perPage; const end = perPageInput === false ? total : start + perPage; const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_SCORER_DEFINITIONS)} FROM "${storage.TABLE_SCORER_DEFINITIONS}" ${whereClause} ORDER BY ${field} ${direction} LIMIT ? OFFSET ?`, args: [...queryParams, limitValue, start] }); const scorerDefinitions = result.rows?.map((row) => this.#parseScorerRow(row)) ?? []; return { scorerDefinitions, total, page, perPage: perPageForResponse, hasMore: end < total }; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_SCORER_DEFINITIONS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } // ========================================================================== // Scorer Definition Version Methods // ========================================================================== async createVersion(input) { try { const now = /* @__PURE__ */ new Date(); await this.#db.insert({ tableName: storage.TABLE_SCORER_DEFINITION_VERSIONS, record: { id: input.id, scorerDefinitionId: input.scorerDefinitionId, versionNumber: input.versionNumber, name: input.name, description: input.description ?? null, type: input.type, model: input.model ?? null, instructions: input.instructions ?? null, scoreRange: input.scoreRange ?? null, presetConfig: input.presetConfig ?? null, defaultSampling: input.defaultSampling ?? null, changedFields: input.changedFields ?? null, changeMessage: input.changeMessage ?? null, createdAt: now.toISOString() } }); return { ...input, createdAt: now }; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "CREATE_SCORER_DEFINITION_VERSION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async getVersion(id) { try { const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_SCORER_DEFINITION_VERSIONS)} FROM "${storage.TABLE_SCORER_DEFINITION_VERSIONS}" WHERE id = ?`, args: [id] }); const row = result.rows?.[0]; return row ? this.#parseVersionRow(row) : null; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_SCORER_DEFINITION_VERSION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async getVersionByNumber(scorerDefinitionId, versionNumber) { try { const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_SCORER_DEFINITION_VERSIONS)} FROM "${storage.TABLE_SCORER_DEFINITION_VERSIONS}" WHERE scorerDefinitionId = ? AND versionNumber = ?`, args: [scorerDefinitionId, versionNumber] }); const row = result.rows?.[0]; return row ? this.#parseVersionRow(row) : null; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_SCORER_DEFINITION_VERSION_BY_NUMBER", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async getLatestVersion(scorerDefinitionId) { try { const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_SCORER_DEFINITION_VERSIONS)} FROM "${storage.TABLE_SCORER_DEFINITION_VERSIONS}" WHERE scorerDefinitionId = ? ORDER BY versionNumber DESC LIMIT 1`, args: [scorerDefinitionId] }); const row = result.rows?.[0]; return row ? this.#parseVersionRow(row) : null; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_LATEST_SCORER_DEFINITION_VERSION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async listVersions(input) { try { const { scorerDefinitionId, page = 0, perPage: perPageInput, orderBy } = input; const { field, direction } = this.parseVersionOrderBy(orderBy); const countResult = await this.#client.execute({ sql: `SELECT COUNT(*) as count FROM "${storage.TABLE_SCORER_DEFINITION_VERSIONS}" WHERE scorerDefinitionId = ?`, args: [scorerDefinitionId] }); const total = Number(countResult.rows?.[0]?.count ?? 0); if (total === 0) { return { versions: [], total: 0, page, perPage: perPageInput ?? 20, hasMore: false }; } const perPage = storage.normalizePerPage(perPageInput, 20); const { offset: start, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage); const limitValue = perPageInput === false ? total : perPage; const end = perPageInput === false ? total : start + perPage; const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_SCORER_DEFINITION_VERSIONS)} FROM "${storage.TABLE_SCORER_DEFINITION_VERSIONS}" WHERE scorerDefinitionId = ? ORDER BY ${field} ${direction} LIMIT ? OFFSET ?`, args: [scorerDefinitionId, limitValue, start] }); const versions = result.rows?.map((row) => this.#parseVersionRow(row)) ?? []; return { versions, total, page, perPage: perPageForResponse, hasMore: end < total }; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_SCORER_DEFINITION_VERSIONS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async deleteVersion(id) { try { await this.#client.execute({ sql: `DELETE FROM "${storage.TABLE_SCORER_DEFINITION_VERSIONS}" WHERE "id" = ?`, args: [id] }); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "DELETE_SCORER_DEFINITION_VERSION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async deleteVersionsByParentId(entityId) { try { await this.#client.execute({ sql: `DELETE FROM "${storage.TABLE_SCORER_DEFINITION_VERSIONS}" WHERE "scorerDefinitionId" = ?`, args: [entityId] }); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "DELETE_SCORER_DEFINITION_VERSIONS_BY_SCORER", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async countVersions(scorerDefinitionId) { try { const result = await this.#client.execute({ sql: `SELECT COUNT(*) as count FROM "${storage.TABLE_SCORER_DEFINITION_VERSIONS}" WHERE scorerDefinitionId = ?`, args: [scorerDefinitionId] }); return Number(result.rows?.[0]?.count ?? 0); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "COUNT_SCORER_DEFINITION_VERSIONS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } // ========================================================================== // Private Helpers // ========================================================================== #parseScorerRow(row) { const safeParseJSON = (val) => { if (val === null || val === void 0) return void 0; if (typeof val === "string") { try { return JSON.parse(val); } catch { return val; } } return val; }; return { id: row.id, status: row.status ?? "draft", activeVersionId: row.activeVersionId ?? void 0, authorId: row.authorId ?? void 0, metadata: safeParseJSON(row.metadata), createdAt: new Date(row.createdAt), updatedAt: new Date(row.updatedAt) }; } #parseVersionRow(row) { const safeParseJSON = (val) => { if (val === null || val === void 0) return void 0; if (typeof val === "string") { try { return JSON.parse(val); } catch { return val; } } return val; }; return { id: row.id, scorerDefinitionId: row.scorerDefinitionId, versionNumber: Number(row.versionNumber), name: row.name, description: row.description ?? void 0, type: row.type, model: safeParseJSON(row.model), instructions: row.instructions ?? void 0, scoreRange: safeParseJSON(row.scoreRange), presetConfig: safeParseJSON(row.presetConfig), defaultSampling: safeParseJSON(row.defaultSampling), changedFields: safeParseJSON(row.changedFields), changeMessage: row.changeMessage ?? void 0, createdAt: new Date(row.createdAt) }; } }; var ScoresLibSQL = class extends storage.ScoresStorage { #db; #client; constructor(config) { super(); const client = resolveClient(config); this.#client = client; this.#db = new LibSQLDB({ client, maxRetries: config.maxRetries, initialBackoffMs: config.initialBackoffMs }); } async init() { await this.#db.createTable({ tableName: storage.TABLE_SCORERS, schema: storage.SCORERS_SCHEMA }); await this.#db.alterTable({ tableName: storage.TABLE_SCORERS, schema: storage.SCORERS_SCHEMA, ifNotExists: ["spanId", "requestContext"] }); } async dangerouslyClearAll() { await this.#db.deleteData({ tableName: storage.TABLE_SCORERS }); } async listScoresByRunId({ runId, pagination }) { try { const { page, perPage: perPageInput } = pagination; const countResult = await this.#client.execute({ sql: `SELECT COUNT(*) as count FROM ${storage.TABLE_SCORERS} WHERE runId = ?`, args: [runId] }); const total = Number(countResult.rows?.[0]?.count ?? 0); if (total === 0) { return { pagination: { total: 0, page, perPage: perPageInput, hasMore: false }, scores: [] }; } const perPage = storage.normalizePerPage(perPageInput, 100); const { offset: start, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage); const limitValue = perPageInput === false ? total : perPage; const end = perPageInput === false ? total : start + perPage; const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_SCORERS)} FROM ${storage.TABLE_SCORERS} WHERE runId = ? ORDER BY createdAt DESC LIMIT ? OFFSET ?`, args: [runId, limitValue, start] }); const scores = result.rows?.map((row) => this.transformScoreRow(row)) ?? []; return { scores, pagination: { total, page, perPage: perPageForResponse, hasMore: end < total } }; } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_SCORES_BY_RUN_ID", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async listScoresByScorerId({ scorerId, entityId, entityType, source, pagination }) { try { const { page, perPage: perPageInput } = pagination; const conditions = []; const queryParams = []; if (scorerId) { conditions.push(`scorerId = ?`); queryParams.push(scorerId); } if (entityId) { conditions.push(`entityId = ?`); queryParams.push(entityId); } if (entityType) { conditions.push(`entityType = ?`); queryParams.push(entityType); } if (source) { conditions.push(`source = ?`); queryParams.push(source); } const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : ""; const countResult = await this.#client.execute({ sql: `SELECT COUNT(*) as count FROM ${storage.TABLE_SCORERS} ${whereClause}`, args: queryParams }); const total = Number(countResult.rows?.[0]?.count ?? 0); if (total === 0) { return { pagination: { total: 0, page, perPage: perPageInput, hasMore: false }, scores: [] }; } const perPage = storage.normalizePerPage(perPageInput, 100); const { offset: start, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage); const limitValue = perPageInput === false ? total : perPage; const end = perPageInput === false ? total : start + perPage; const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_SCORERS)} FROM ${storage.TABLE_SCORERS} ${whereClause} ORDER BY createdAt DESC LIMIT ? OFFSET ?`, args: [...queryParams, limitValue, start] }); const scores = result.rows?.map((row) => this.transformScoreRow(row)) ?? []; return { scores, pagination: { total, page, perPage: perPageForResponse, hasMore: end < total } }; } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_SCORES_BY_SCORER_ID", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } /** * LibSQL-specific score row transformation. */ transformScoreRow(row) { return storage.transformScoreRow(row); } async getScoreById({ id }) { const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_SCORERS)} FROM ${storage.TABLE_SCORERS} WHERE id = ?`, args: [id] }); return result.rows?.[0] ? this.transformScoreRow(result.rows[0]) : null; } async saveScore(score) { let parsedScore; try { parsedScore = evals.saveScorePayloadSchema.parse(score); } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "SAVE_SCORE", "VALIDATION_FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, details: { scorer: typeof score.scorer?.id === "string" ? score.scorer.id : String(score.scorer?.id ?? "unknown"), entityId: score.entityId ?? "unknown", entityType: score.entityType ?? "unknown", traceId: score.traceId ?? "", spanId: score.spanId ?? "" } }, error$1 ); } try { const id = crypto.randomUUID(); const now = /* @__PURE__ */ new Date(); await this.#db.insert({ tableName: storage.TABLE_SCORERS, record: { ...parsedScore, id, createdAt: now.toISOString(), updatedAt: now.toISOString() } }); return { score: { ...parsedScore, id, createdAt: now, updatedAt: now } }; } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "SAVE_SCORE", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async listScoresByEntityId({ entityId, entityType, pagination }) { try { const { page, perPage: perPageInput } = pagination; const countResult = await this.#client.execute({ sql: `SELECT COUNT(*) as count FROM ${storage.TABLE_SCORERS} WHERE entityId = ? AND entityType = ?`, args: [entityId, entityType] }); const total = Number(countResult.rows?.[0]?.count ?? 0); if (total === 0) { return { pagination: { total: 0, page, perPage: perPageInput, hasMore: false }, scores: [] }; } const perPage = storage.normalizePerPage(perPageInput, 100); const { offset: start, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage); const limitValue = perPageInput === false ? total : perPage; const end = perPageInput === false ? total : start + perPage; const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_SCORERS)} FROM ${storage.TABLE_SCORERS} WHERE entityId = ? AND entityType = ? ORDER BY createdAt DESC LIMIT ? OFFSET ?`, args: [entityId, entityType, limitValue, start] }); const scores = result.rows?.map((row) => this.transformScoreRow(row)) ?? []; return { scores, pagination: { total, page, perPage: perPageForResponse, hasMore: end < total } }; } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_SCORES_BY_ENTITY_ID", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async listScoresBySpan({ traceId, spanId, pagination }) { try { const { page, perPage: perPageInput } = pagination; const perPage = storage.normalizePerPage(perPageInput, 100); const { offset: start, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage); const countSQLResult = await this.#client.execute({ sql: `SELECT COUNT(*) as count FROM ${storage.TABLE_SCORERS} WHERE traceId = ? AND spanId = ?`, args: [traceId, spanId] }); const total = Number(countSQLResult.rows?.[0]?.count ?? 0); const limitValue = perPageInput === false ? total : perPage; const end = perPageInput === false ? total : start + perPage; const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_SCORERS)} FROM ${storage.TABLE_SCORERS} WHERE traceId = ? AND spanId = ? ORDER BY createdAt DESC LIMIT ? OFFSET ?`, args: [traceId, spanId, limitValue, start] }); const scores = result.rows?.map((row) => this.transformScoreRow(row)) ?? []; return { scores, pagination: { total, page, perPage: perPageForResponse, hasMore: end < total } }; } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_SCORES_BY_SPAN", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } }; var SNAPSHOT_FIELDS = [ "name", "description", "instructions", "license", "compatibility", "source", "references", "scripts", "assets", "files", "metadata", "tree" ]; var SkillsLibSQL = class extends storage.SkillsStorage { #db; #client; constructor(config) { super(); const client = resolveClient(config); this.#client = client; this.#db = new LibSQLDB({ client, maxRetries: config.maxRetries, initialBackoffMs: config.initialBackoffMs }); } async init() { await this.#db.createTable({ tableName: storage.TABLE_SKILLS, schema: storage.SKILLS_SCHEMA }); await this.#db.createTable({ tableName: storage.TABLE_SKILL_VERSIONS, schema: storage.SKILL_VERSIONS_SCHEMA }); await this.#db.alterTable({ tableName: storage.TABLE_SKILLS, schema: storage.SKILLS_SCHEMA, ifNotExists: ["visibility", "favoriteCount"] }); await this.#db.alterTable({ tableName: storage.TABLE_SKILL_VERSIONS, schema: storage.SKILL_VERSIONS_SCHEMA, ifNotExists: ["files"] }); await this.#client.execute( `CREATE UNIQUE INDEX IF NOT EXISTS idx_skill_versions_skill_version ON "${storage.TABLE_SKILL_VERSIONS}" ("skillId", "versionNumber")` ); } async dangerouslyClearAll() { await this.#db.deleteData({ tableName: storage.TABLE_SKILLS }); await this.#db.deleteData({ tableName: storage.TABLE_SKILL_VERSIONS }); } // ========================================================================== // Skill CRUD // ========================================================================== async getById(id) { try { const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_SKILLS)} FROM "${storage.TABLE_SKILLS}" WHERE id = ?`, args: [id] }); const row = result.rows?.[0]; return row ? this.#parseSkillRow(row) : null; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_SKILL", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async create(input) { const { skill } = input; try { const now = /* @__PURE__ */ new Date(); const visibility = skill.visibility ?? (skill.authorId ? "private" : void 0); await this.#db.insert({ tableName: storage.TABLE_SKILLS, record: { id: skill.id, status: "draft", activeVersionId: null, authorId: skill.authorId ?? null, visibility: visibility ?? null, favoriteCount: 0, createdAt: now.toISOString(), updatedAt: now.toISOString() } }); const { id: _id, authorId: _authorId, visibility: _visibility, ...snapshotConfig } = skill; const versionId = crypto.randomUUID(); try { await this.createVersion({ id: versionId, skillId: skill.id, versionNumber: 1, ...snapshotConfig, changedFields: Object.keys(snapshotConfig), changeMessage: "Initial version" }); } catch (versionError) { await this.#db.delete({ tableName: storage.TABLE_SKILLS, keys: { id: skill.id } }); throw versionError; } return { id: skill.id, status: "draft", activeVersionId: void 0, authorId: skill.authorId, createdAt: now, updatedAt: now }; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "CREATE_SKILL", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async update(input) { const { id, ...updates } = input; try { const existing = await this.getById(id); if (!existing) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "UPDATE_SKILL", "NOT_FOUND"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, text: `Skill ${id} not found`, details: { skillId: id } }); } const { authorId, visibility, activeVersionId, status, ...rawConfigFields } = updates; const configFields = {}; for (const [key, value] of Object.entries(rawConfigFields)) { if (value !== void 0) configFields[key] = value; } const configFieldNames = SNAPSHOT_FIELDS; const hasConfigUpdate = configFieldNames.some((field) => field in configFields); const updateData = { updatedAt: (/* @__PURE__ */ new Date()).toISOString() }; if (authorId !== void 0) updateData.authorId = authorId; if (visibility !== void 0) updateData.visibility = visibility; if (activeVersionId !== void 0) { updateData.activeVersionId = activeVersionId; if (status === void 0) { updateData.status = "published"; } } if (status !== void 0) updateData.status = status; await this.#db.update({ tableName: storage.TABLE_SKILLS, keys: { id }, data: updateData }); if (hasConfigUpdate) { const latestVersion = await this.getLatestVersion(id); if (!latestVersion) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "UPDATE_SKILL", "NO_VERSIONS"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, text: `No versions found for skill ${id}`, details: { skillId: id } }); } const { id: _versionId, skillId: _skillId, versionNumber: _versionNumber, changedFields: _changedFields, changeMessage: _changeMessage, createdAt: _createdAt, ...latestConfig } = latestVersion; const newConfig = { ...latestConfig, ...configFields }; const changedFields = configFieldNames.filter( (field) => field in configFields && !skills.skillSnapshotFieldValuesEqual( configFields[field], latestConfig[field] ) ); if (changedFields.length > 0) { const newVersionId = crypto.randomUUID(); await this.createVersion({ id: newVersionId, skillId: id, versionNumber: latestVersion.versionNumber + 1, ...newConfig, changedFields, changeMessage: `Updated ${changedFields.join(", ")}` }); } } const updated = await this.getById(id); if (!updated) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "UPDATE_SKILL", "NOT_FOUND_AFTER_UPDATE"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.SYSTEM, text: `Skill ${id} not found after update`, details: { id } }); } return updated; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "UPDATE_SKILL", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async delete(id) { try { await this.deleteVersionsByParentId(id); await this.#client.execute({ sql: `DELETE FROM "${storage.TABLE_SKILLS}" WHERE "id" = ?`, args: [id] }); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "DELETE_SKILL", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async list(args) { try { const { page = 0, perPage: perPageInput, orderBy, authorId, visibility, status, entityIds, pinFavoritedFor, favoritedOnly } = args || {}; const { field, direction } = this.parseOrderBy(orderBy); if (entityIds && entityIds.length === 0) { return { skills: [], total: 0, page, perPage: perPageInput ?? 100, hasMore: false }; } const conditions = []; const queryParams = []; if (authorId !== void 0) { conditions.push("s_e.authorId = ?"); queryParams.push(authorId); } if (visibility !== void 0) { conditions.push("s_e.visibility = ?"); queryParams.push(visibility); } if (status !== void 0) { conditions.push("s_e.status = ?"); queryParams.push(status); } if (entityIds && entityIds.length > 0) { const placeholders = entityIds.map(() => "?").join(", "); conditions.push(`s_e.id IN (${placeholders})`); queryParams.push(...entityIds); } const joinUserId = pinFavoritedFor; const useJoin = Boolean(joinUserId); let joinClause = ""; const joinParams = []; if (useJoin && joinUserId) { joinClause = `LEFT JOIN "${storage.TABLE_FAVORITES}" st ON st."entityType" = 'skill' AND st."entityId" = s_e.id AND st."userId" = ?`; joinParams.push(joinUserId); if (favoritedOnly) { conditions.push('st."userId" IS NOT NULL'); } } else if (favoritedOnly) { conditions.push("1=0"); } const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : ""; const countResult = await this.#client.execute({ sql: `SELECT COUNT(*) as count FROM "${storage.TABLE_SKILLS}" s_e ${joinClause} ${whereClause}`, args: [...joinParams, ...queryParams] }); const total = Number(countResult.rows?.[0]?.count ?? 0); if (total === 0) { return { skills: [], total: 0, page, perPage: perPageInput ?? 100, hasMore: false }; } const perPage = storage.normalizePerPage(perPageInput, 100); const { offset: start, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage); const limitValue = perPageInput === false ? total : perPage; const end = perPageInput === false ? total : start + perPage; const orderByParts = []; if (useJoin && joinUserId) { orderByParts.push(`(st."userId" IS NOT NULL) DESC`); } orderByParts.push(`s_e."${field}" ${direction}`); orderByParts.push(`s_e."id" ASC`); const orderByClause = `ORDER BY ${orderByParts.join(", ")}`; const selectCols = buildSelectColumnsWithAlias(storage.TABLE_SKILLS, "s_e"); const result = await this.#client.execute({ sql: `SELECT ${selectCols} FROM "${storage.TABLE_SKILLS}" s_e ${joinClause} ${whereClause} ${orderByClause} LIMIT ? OFFSET ?`, args: [...joinParams, ...queryParams, limitValue, start] }); const skills = result.rows?.map((row) => this.#parseSkillRow(row)) ?? []; return { skills, total, page, perPage: perPageForResponse, hasMore: end < total }; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_SKILLS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } // ========================================================================== // Skill Version Methods // ========================================================================== async createVersion(input) { try { const now = /* @__PURE__ */ new Date(); await this.#client.execute({ sql: `INSERT INTO "${storage.TABLE_SKILL_VERSIONS}" ( id, "skillId", "versionNumber", name, description, instructions, license, compatibility, source, "references", scripts, assets, files, metadata, tree, "changedFields", "changeMessage", "createdAt" ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, args: [ input.id, input.skillId, input.versionNumber, input.name, input.description ?? null, input.instructions ?? null, input.license ?? null, input.compatibility ? JSON.stringify(input.compatibility) : null, input.source ? JSON.stringify(input.source) : null, input.references ? JSON.stringify(input.references) : null, input.scripts ? JSON.stringify(input.scripts) : null, input.assets ? JSON.stringify(input.assets) : null, input.files ? JSON.stringify(input.files) : null, input.metadata ? JSON.stringify(input.metadata) : null, input.tree ? JSON.stringify(input.tree) : null, input.changedFields ? JSON.stringify(input.changedFields) : null, input.changeMessage ?? null, now.toISOString() ] }); return { ...input, createdAt: now }; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "CREATE_SKILL_VERSION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async getVersion(id) { try { const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_SKILL_VERSIONS)} FROM "${storage.TABLE_SKILL_VERSIONS}" WHERE id = ?`, args: [id] }); const row = result.rows?.[0]; return row ? this.#parseVersionRow(row) : null; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_SKILL_VERSION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async getVersionByNumber(skillId, versionNumber) { try { const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_SKILL_VERSIONS)} FROM "${storage.TABLE_SKILL_VERSIONS}" WHERE skillId = ? AND versionNumber = ?`, args: [skillId, versionNumber] }); const row = result.rows?.[0]; return row ? this.#parseVersionRow(row) : null; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_SKILL_VERSION_BY_NUMBER", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async getLatestVersion(skillId) { try { const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_SKILL_VERSIONS)} FROM "${storage.TABLE_SKILL_VERSIONS}" WHERE skillId = ? ORDER BY versionNumber DESC LIMIT 1`, args: [skillId] }); const row = result.rows?.[0]; return row ? this.#parseVersionRow(row) : null; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_LATEST_SKILL_VERSION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async listVersions(input) { try { const { skillId, page = 0, perPage: perPageInput, orderBy } = input; const { field, direction } = this.parseVersionOrderBy(orderBy); const countResult = await this.#client.execute({ sql: `SELECT COUNT(*) as count FROM "${storage.TABLE_SKILL_VERSIONS}" WHERE skillId = ?`, args: [skillId] }); const total = Number(countResult.rows?.[0]?.count ?? 0); if (total === 0) { return { versions: [], total: 0, page, perPage: perPageInput ?? 20, hasMore: false }; } const perPage = storage.normalizePerPage(perPageInput, 20); const { offset: start, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage); const limitValue = perPageInput === false ? total : perPage; const end = perPageInput === false ? total : start + perPage; const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_SKILL_VERSIONS)} FROM "${storage.TABLE_SKILL_VERSIONS}" WHERE skillId = ? ORDER BY ${field} ${direction} LIMIT ? OFFSET ?`, args: [skillId, limitValue, start] }); const versions = result.rows?.map((row) => this.#parseVersionRow(row)) ?? []; return { versions, total, page, perPage: perPageForResponse, hasMore: end < total }; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_SKILL_VERSIONS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async deleteVersion(id) { try { await this.#client.execute({ sql: `DELETE FROM "${storage.TABLE_SKILL_VERSIONS}" WHERE "id" = ?`, args: [id] }); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "DELETE_SKILL_VERSION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async deleteVersionsByParentId(entityId) { try { await this.#client.execute({ sql: `DELETE FROM "${storage.TABLE_SKILL_VERSIONS}" WHERE "skillId" = ?`, args: [entityId] }); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "DELETE_SKILL_VERSIONS_BY_SKILL", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async countVersions(skillId) { try { const result = await this.#client.execute({ sql: `SELECT COUNT(*) as count FROM "${storage.TABLE_SKILL_VERSIONS}" WHERE skillId = ?`, args: [skillId] }); return Number(result.rows?.[0]?.count ?? 0); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "COUNT_SKILL_VERSIONS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } // ========================================================================== // Private Helpers // ========================================================================== #parseSkillRow(row) { return { id: row.id, status: row.status ?? "draft", activeVersionId: row.activeVersionId ?? void 0, authorId: row.authorId ?? void 0, visibility: row.visibility ?? void 0, favoriteCount: row.favoriteCount === null || row.favoriteCount === void 0 ? 0 : Number(row.favoriteCount), createdAt: new Date(row.createdAt), updatedAt: new Date(row.updatedAt) }; } #parseVersionRow(row) { const safeParseJSON = (val) => { if (val === null || val === void 0) return void 0; if (typeof val === "string") { try { return JSON.parse(val); } catch { return val; } } return val; }; return { id: row.id, skillId: row.skillId, versionNumber: Number(row.versionNumber), name: row.name, description: row.description ?? void 0, instructions: row.instructions ?? void 0, license: row.license ?? void 0, compatibility: safeParseJSON(row.compatibility), source: safeParseJSON(row.source), references: safeParseJSON(row.references), scripts: safeParseJSON(row.scripts), assets: safeParseJSON(row.assets), files: safeParseJSON(row.files), metadata: safeParseJSON(row.metadata), tree: safeParseJSON(row.tree), changedFields: safeParseJSON(row.changedFields), changeMessage: row.changeMessage ?? void 0, createdAt: new Date(row.createdAt) }; } }; var ThreadStateLibSQL = class extends storage.ThreadStateStorage { #db; #client; constructor(config) { super(); const client = resolveClient(config); this.#client = client; this.#db = new LibSQLDB({ client, maxRetries: config.maxRetries, initialBackoffMs: config.initialBackoffMs }); } async init() { await this.#db.createTable({ tableName: storage.TABLE_THREAD_STATE, schema: storage.THREAD_STATE_SCHEMA, compositePrimaryKey: ["threadId", "type"] }); } async dangerouslyClearAll() { try { await this.#client.execute(`DELETE FROM "${storage.TABLE_THREAD_STATE}"`); } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "THREAD_STATE_CLEAR_ALL", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async getState({ threadId, type }) { try { const result = await this.#client.execute({ sql: `SELECT "value" FROM "${storage.TABLE_THREAD_STATE}" WHERE "threadId" = ? AND "type" = ? LIMIT 1`, args: [threadId, type] }); const raw = result.rows?.[0]?.value; if (raw === void 0 || raw === null) return void 0; return typeof raw === "string" ? JSON.parse(raw) : raw; } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "THREAD_STATE_GET", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { threadId, type } }, error$1 ); } } async setState({ threadId, type, value }) { const now = (/* @__PURE__ */ new Date()).toISOString(); const serialized = JSON.stringify(value ?? null); try { await this.#client.execute({ sql: `INSERT INTO "${storage.TABLE_THREAD_STATE}" ("threadId", "type", "value", "createdAt", "updatedAt") VALUES (?, ?, ?, ?, ?) ON CONFLICT ("threadId", "type") DO UPDATE SET "value" = excluded."value", "updatedAt" = excluded."updatedAt"`, args: [threadId, type, serialized, now, now] }); } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "THREAD_STATE_SET", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { threadId, type } }, error$1 ); } } async deleteState({ threadId, type }) { try { await this.#client.execute({ sql: `DELETE FROM "${storage.TABLE_THREAD_STATE}" WHERE "threadId" = ? AND "type" = ?`, args: [threadId, type] }); } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "THREAD_STATE_DELETE", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { threadId, type } }, error$1 ); } } }; function normaliseScope(raw) { const value = raw == null ? "per-author" : String(raw); if (value === "shared") return "shared"; if (value === "caller-supplied") return "caller-supplied"; return "per-author"; } function rowToToolProviderConnection(row) { return { authorId: String(row.authorId), providerId: String(row.providerId), toolkit: String(row.toolkit), connectionId: String(row.connectionId), label: row.label == null ? null : String(row.label), scope: normaliseScope(row.scope), createdAt: new Date(String(row.createdAt)), updatedAt: new Date(String(row.updatedAt)) }; } var ToolProviderConnectionsLibSQL = class extends storage.ToolProviderConnectionsStorage { #db; #client; constructor(config) { super(); const client = resolveClient(config); this.#client = client; this.#db = new LibSQLDB({ client, maxRetries: config.maxRetries, initialBackoffMs: config.initialBackoffMs }); } async init() { await this.#db.createTable({ tableName: storage.TABLE_TOOL_PROVIDER_CONNECTIONS, schema: storage.TOOL_PROVIDER_CONNECTIONS_SCHEMA, compositePrimaryKey: ["authorId", "providerId", "connectionId"] }); await this.#client.execute( `CREATE INDEX IF NOT EXISTS idx_tool_provider_connections_author ON "${storage.TABLE_TOOL_PROVIDER_CONNECTIONS}" ("authorId", "providerId", "toolkit")` ); } async dangerouslyClearAll() { await this.#client.execute(`DELETE FROM "${storage.TABLE_TOOL_PROVIDER_CONNECTIONS}"`); } async getConnectionById({ authorId, providerId, connectionId }) { try { const result = await this.#client.execute({ sql: `SELECT * FROM "${storage.TABLE_TOOL_PROVIDER_CONNECTIONS}" WHERE "authorId" = ? AND "providerId" = ? AND "connectionId" = ? LIMIT 1`, args: [authorId, providerId, connectionId] }); const row = result.rows?.[0]; if (!row) return null; return rowToToolProviderConnection(row); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "TOOL_PROVIDER_CONNECTION_GET", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { authorId, providerId, connectionId } }, error$1 ); } } async upsertConnection(input) { const { authorId, providerId, toolkit, connectionId, label } = input; const now = /* @__PURE__ */ new Date(); const nowIso = now.toISOString(); const labelValue = label == null ? null : label; try { const tx = await this.#client.transaction("write"); try { const existing = await tx.execute({ sql: `SELECT "createdAt", "scope" FROM "${storage.TABLE_TOOL_PROVIDER_CONNECTIONS}" WHERE "authorId" = ? AND "providerId" = ? AND "connectionId" = ? LIMIT 1`, args: [authorId, providerId, connectionId] }); const existingRow = existing.rows?.[0]; const createdAt = existingRow ? String(existingRow.createdAt) : nowIso; const existingScope = existingRow && existingRow.scope != null ? normaliseScope(existingRow.scope) : void 0; const scope = input.scope ?? existingScope ?? "per-author"; if (existingRow) { await tx.execute({ sql: `UPDATE "${storage.TABLE_TOOL_PROVIDER_CONNECTIONS}" SET "toolkit" = ?, "label" = ?, "scope" = ?, "updatedAt" = ? WHERE "authorId" = ? AND "providerId" = ? AND "connectionId" = ?`, args: [toolkit, labelValue, scope, nowIso, authorId, providerId, connectionId] }); } else { await tx.execute({ sql: `INSERT INTO "${storage.TABLE_TOOL_PROVIDER_CONNECTIONS}" ("authorId", "providerId", "toolkit", "connectionId", "label", "scope", "createdAt", "updatedAt") VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, args: [authorId, providerId, toolkit, connectionId, labelValue, scope, createdAt, nowIso] }); } await tx.commit(); return { authorId, providerId, toolkit, connectionId, label: labelValue, scope, createdAt: new Date(createdAt), updatedAt: now }; } catch (error) { if (!tx.closed) { await tx.rollback(); } throw error; } } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "TOOL_PROVIDER_CONNECTION_UPSERT", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { authorId, providerId, connectionId } }, error$1 ); } } async listConnectionsByAuthor({ authorId, providerId, toolkit, scope }) { try { const clauses = []; const args = []; if (authorId !== void 0) { clauses.push('"authorId" = ?'); args.push(authorId); } if (providerId) { clauses.push('"providerId" = ?'); args.push(providerId); } if (toolkit) { clauses.push('"toolkit" = ?'); args.push(toolkit); } if (scope) { clauses.push('"scope" = ?'); args.push(scope); } const whereClause = clauses.length > 0 ? ` WHERE ${clauses.join(" AND ")}` : ""; const result = await this.#client.execute({ sql: `SELECT * FROM "${storage.TABLE_TOOL_PROVIDER_CONNECTIONS}"${whereClause}`, args }); return (result.rows ?? []).map((row) => rowToToolProviderConnection(row)); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "TOOL_PROVIDER_CONNECTION_LIST", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { authorId: authorId ?? "", providerId: providerId ?? "", toolkit: toolkit ?? "" } }, error$1 ); } } async deleteConnection({ authorId, providerId, connectionId }) { try { await this.#client.execute({ sql: `DELETE FROM "${storage.TABLE_TOOL_PROVIDER_CONNECTIONS}" WHERE "authorId" = ? AND "providerId" = ? AND "connectionId" = ?`, args: [authorId, providerId, connectionId] }); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "TOOL_PROVIDER_CONNECTION_DELETE", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { authorId, providerId, connectionId } }, error$1 ); } } }; var WorkflowsLibSQL = class extends storage.WorkflowsStorage { #db; #client; executeWithRetry; constructor(config) { super(); const client = resolveClient(config); const maxRetries = config.maxRetries ?? 5; const initialBackoffMs = config.initialBackoffMs ?? 500; this.#client = client; this.#db = new LibSQLDB({ client, maxRetries, initialBackoffMs }); this.executeWithRetry = createExecuteWriteOperationWithRetry({ logger: this.logger, maxRetries, initialBackoffMs }); this.setupPragmaSettings().catch( (err) => this.logger.warn("LibSQL Workflows: Failed to setup PRAGMA settings.", err) ); } supportsConcurrentUpdates() { return true; } parseWorkflowRun(row) { let parsedSnapshot = row.snapshot; if (typeof parsedSnapshot === "string") { try { parsedSnapshot = JSON.parse(row.snapshot); } catch (e) { this.logger.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`); } } return { workflowName: row.workflow_name, runId: row.run_id, snapshot: parsedSnapshot, resourceId: row.resourceId, createdAt: new Date(row.createdAt), updatedAt: new Date(row.updatedAt) }; } async init() { const schema = storage.TABLE_SCHEMAS[storage.TABLE_WORKFLOW_SNAPSHOT]; await this.#db.createTable({ tableName: storage.TABLE_WORKFLOW_SNAPSHOT, schema }); await this.#db.alterTable({ tableName: storage.TABLE_WORKFLOW_SNAPSHOT, schema, ifNotExists: ["resourceId"] }); } async dangerouslyClearAll() { await this.#db.deleteData({ tableName: storage.TABLE_WORKFLOW_SNAPSHOT }); } async setupPragmaSettings() { try { await this.#client.execute("PRAGMA busy_timeout = 10000;"); this.logger.debug("LibSQL Workflows: PRAGMA busy_timeout=10000 set."); try { await this.#client.execute("PRAGMA journal_mode = WAL;"); this.logger.debug("LibSQL Workflows: PRAGMA journal_mode=WAL set."); } catch { this.logger.debug("LibSQL Workflows: WAL mode not supported, using default journal mode."); } try { await this.#client.execute("PRAGMA synchronous = NORMAL;"); this.logger.debug("LibSQL Workflows: PRAGMA synchronous=NORMAL set."); } catch { this.logger.debug("LibSQL Workflows: Failed to set synchronous mode."); } } catch (err) { this.logger.warn("LibSQL Workflows: Failed to set PRAGMA settings.", err); } } async updateWorkflowResults({ workflowName, runId, stepId, result, requestContext }) { return this.executeWithRetry( () => ( // Serialize the interactive transaction against all other writes on the shared // connection so a concurrent autocommit write can't leak into this open BEGIN. withClientWriteLock(this.#client, async () => { const tx = await this.#client.transaction("write"); try { const existingSnapshotResult = await tx.execute({ sql: `SELECT json(snapshot) as snapshot FROM ${storage.TABLE_WORKFLOW_SNAPSHOT} WHERE workflow_name = ? AND run_id = ?`, args: [workflowName, runId] }); let snapshot; if (!existingSnapshotResult.rows?.[0]) { snapshot = { context: {}, activePaths: [], timestamp: Date.now(), suspendedPaths: {}, activeStepsPath: {}, resumeLabels: {}, serializedStepGraph: [], status: "pending", value: {}, waitingPaths: {}, runId, requestContext: {} }; } else { const existingSnapshot = existingSnapshotResult.rows[0].snapshot; snapshot = typeof existingSnapshot === "string" ? JSON.parse(existingSnapshot) : existingSnapshot; } storage.mergeWorkflowStepResult({ snapshot, stepId, result, requestContext }); const now = (/* @__PURE__ */ new Date()).toISOString(); await tx.execute({ sql: `INSERT INTO ${storage.TABLE_WORKFLOW_SNAPSHOT} (workflow_name, run_id, snapshot, createdAt, updatedAt) VALUES (?, ?, jsonb(?), ?, ?) ON CONFLICT(workflow_name, run_id) DO UPDATE SET snapshot = excluded.snapshot, updatedAt = excluded.updatedAt`, args: [workflowName, runId, safeStringify(snapshot), now, now] }); await tx.commit(); return snapshot.context; } catch (error) { if (!tx.closed) { await tx.rollback(); } throw error; } }) ), "updateWorkflowResults" ); } async updateWorkflowState({ workflowName, runId, opts }) { return this.executeWithRetry( () => ( // Serialize the interactive transaction against all other writes on the shared // connection so a concurrent autocommit write can't leak into this open BEGIN. withClientWriteLock(this.#client, async () => { const tx = await this.#client.transaction("write"); try { const existingSnapshotResult = await tx.execute({ sql: `SELECT json(snapshot) as snapshot FROM ${storage.TABLE_WORKFLOW_SNAPSHOT} WHERE workflow_name = ? AND run_id = ?`, args: [workflowName, runId] }); if (!existingSnapshotResult.rows?.[0]) { await tx.rollback(); return void 0; } const existingSnapshot = existingSnapshotResult.rows[0].snapshot; const snapshot = typeof existingSnapshot === "string" ? JSON.parse(existingSnapshot) : existingSnapshot; if (!snapshot || !snapshot?.context) { await tx.rollback(); throw new Error(`Snapshot not found for runId ${runId}`); } const updatedSnapshot = { ...snapshot, ...opts }; await tx.execute({ sql: `UPDATE ${storage.TABLE_WORKFLOW_SNAPSHOT} SET snapshot = jsonb(?) WHERE workflow_name = ? AND run_id = ?`, args: [safeStringify(updatedSnapshot), workflowName, runId] }); await tx.commit(); return updatedSnapshot; } catch (error) { if (!tx.closed) { await tx.rollback(); } throw error; } }) ), "updateWorkflowState" ); } async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt }) { const now = /* @__PURE__ */ new Date(); const data = { workflow_name: workflowName, run_id: runId, resourceId, snapshot, createdAt: createdAt ?? now, updatedAt: updatedAt ?? now }; this.logger.debug("Persisting workflow snapshot", { workflowName, runId, data }); await this.#db.insert({ tableName: storage.TABLE_WORKFLOW_SNAPSHOT, record: data }); } async loadWorkflowSnapshot({ workflowName, runId }) { this.logger.debug("Loading workflow snapshot", { workflowName, runId }); const d = await this.#db.select({ tableName: storage.TABLE_WORKFLOW_SNAPSHOT, keys: { workflow_name: workflowName, run_id: runId } }); return d ? d.snapshot : null; } async getWorkflowRunById({ runId, workflowName }) { const conditions = []; const args = []; if (runId) { conditions.push("run_id = ?"); args.push(runId); } if (workflowName) { conditions.push("workflow_name = ?"); args.push(workflowName); } const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : ""; try { const result = await this.#client.execute({ 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`, args }); if (!result.rows?.[0]) { return null; } return this.parseWorkflowRun(result.rows[0]); } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_WORKFLOW_RUN_BY_ID", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async deleteWorkflowRunById({ runId, workflowName }) { return this.executeWithRetry(async () => { try { await this.#client.execute({ sql: `DELETE FROM ${storage.TABLE_WORKFLOW_SNAPSHOT} WHERE workflow_name = ? AND run_id = ?`, args: [workflowName, runId] }); } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "DELETE_WORKFLOW_RUN_BY_ID", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: { runId, workflowName } }, error$1 ); } }, "deleteWorkflowRunById"); } async listWorkflowRuns({ workflowName, fromDate, toDate: toDate2, page, perPage, resourceId, status } = {}) { try { const conditions = []; const args = []; if (workflowName) { conditions.push("workflow_name = ?"); args.push(workflowName); } if (status) { conditions.push("json_extract(snapshot, '$.status') = ?"); args.push(status); } if (fromDate) { conditions.push("createdAt >= ?"); args.push(fromDate.toISOString()); } if (toDate2) { conditions.push("createdAt <= ?"); args.push(toDate2.toISOString()); } if (resourceId) { const hasResourceId = await this.#db.hasColumn(storage.TABLE_WORKFLOW_SNAPSHOT, "resourceId"); if (hasResourceId) { conditions.push("resourceId = ?"); args.push(resourceId); } else { this.logger.warn(`[${storage.TABLE_WORKFLOW_SNAPSHOT}] resourceId column not found. Skipping resourceId filter.`); } } const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : ""; let total = 0; const usePagination = typeof perPage === "number" && typeof page === "number"; if (usePagination) { const countResult = await this.#client.execute({ sql: `SELECT COUNT(*) as count FROM ${storage.TABLE_WORKFLOW_SNAPSHOT} ${whereClause}`, args }); total = Number(countResult.rows?.[0]?.count ?? 0); } const normalizedPerPage = usePagination ? storage.normalizePerPage(perPage, Number.MAX_SAFE_INTEGER) : 0; const offset = usePagination ? page * normalizedPerPage : 0; const result = await this.#client.execute({ 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 ?` : ""}`, args: usePagination ? [...args, normalizedPerPage, offset] : args }); const runs = (result.rows || []).map((row) => this.parseWorkflowRun(row)); return { runs, total: total || runs.length }; } catch (error$1) { throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_WORKFLOW_RUNS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } }; var SNAPSHOT_FIELDS2 = [ "name", "description", "filesystem", "sandbox", "mounts", "search", "skills", "tools", "autoSync", "operationTimeout" ]; var WorkspacesLibSQL = class extends storage.WorkspacesStorage { #db; #client; constructor(config) { super(); const client = resolveClient(config); this.#client = client; this.#db = new LibSQLDB({ client, maxRetries: config.maxRetries, initialBackoffMs: config.initialBackoffMs }); } async init() { await this.#db.createTable({ tableName: storage.TABLE_WORKSPACES, schema: storage.WORKSPACES_SCHEMA }); await this.#db.createTable({ tableName: storage.TABLE_WORKSPACE_VERSIONS, schema: storage.WORKSPACE_VERSIONS_SCHEMA }); await this.#client.execute( `CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_versions_workspace_version ON "${storage.TABLE_WORKSPACE_VERSIONS}" ("workspaceId", "versionNumber")` ); } async dangerouslyClearAll() { await this.#db.deleteData({ tableName: storage.TABLE_WORKSPACES }); await this.#db.deleteData({ tableName: storage.TABLE_WORKSPACE_VERSIONS }); } // ========================================================================== // Workspace CRUD // ========================================================================== async getById(id) { try { const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_WORKSPACES)} FROM "${storage.TABLE_WORKSPACES}" WHERE id = ?`, args: [id] }); const row = result.rows?.[0]; return row ? this.#parseWorkspaceRow(row) : null; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_WORKSPACE", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async create(input) { const { workspace } = input; try { const now = /* @__PURE__ */ new Date(); await this.#db.insert({ tableName: storage.TABLE_WORKSPACES, record: { id: workspace.id, status: "draft", activeVersionId: null, authorId: workspace.authorId ?? null, metadata: workspace.metadata ?? null, createdAt: now.toISOString(), updatedAt: now.toISOString() } }); const { id: _id, authorId: _authorId, metadata: _metadata, ...snapshotConfig } = workspace; const versionId = crypto.randomUUID(); try { await this.createVersion({ id: versionId, workspaceId: workspace.id, versionNumber: 1, ...snapshotConfig, changedFields: Object.keys(snapshotConfig), changeMessage: "Initial version" }); } catch (versionError) { await this.#db.delete({ tableName: storage.TABLE_WORKSPACES, keys: { id: workspace.id } }); throw versionError; } return { id: workspace.id, status: "draft", activeVersionId: void 0, authorId: workspace.authorId, metadata: workspace.metadata, createdAt: now, updatedAt: now }; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "CREATE_WORKSPACE", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async update(input) { const { id, ...updates } = input; try { const existing = await this.getById(id); if (!existing) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "UPDATE_WORKSPACE", "NOT_FOUND"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, text: `Workspace ${id} not found`, details: { workspaceId: id } }); } const { authorId, activeVersionId, metadata, status, ...rawConfigFields } = updates; const configFields = {}; for (const [key, value] of Object.entries(rawConfigFields)) { if (value !== void 0) configFields[key] = value; } const configFieldNames = SNAPSHOT_FIELDS2; const hasConfigUpdate = configFieldNames.some((field) => field in configFields); const updateData = { updatedAt: (/* @__PURE__ */ new Date()).toISOString() }; if (authorId !== void 0) updateData.authorId = authorId; if (activeVersionId !== void 0) { updateData.activeVersionId = activeVersionId; if (status === void 0) { updateData.status = "published"; } } if (status !== void 0) updateData.status = status; if (metadata !== void 0) { updateData.metadata = { ...existing.metadata || {}, ...metadata }; } await this.#db.update({ tableName: storage.TABLE_WORKSPACES, keys: { id }, data: updateData }); if (hasConfigUpdate) { const latestVersion = await this.getLatestVersion(id); if (!latestVersion) { throw new Error(`No versions found for workspace ${id}`); } const { id: _versionId, workspaceId: _workspaceId, versionNumber: _versionNumber, changedFields: _changedFields, changeMessage: _changeMessage, createdAt: _createdAt, ...latestConfig } = latestVersion; const newConfig = { ...latestConfig, ...configFields }; const changedFields = configFieldNames.filter( (field) => field in configFields && JSON.stringify(configFields[field]) !== JSON.stringify(latestConfig[field]) ); if (changedFields.length > 0) { const newVersionId = crypto.randomUUID(); await this.createVersion({ id: newVersionId, workspaceId: id, versionNumber: latestVersion.versionNumber + 1, ...newConfig, changedFields, changeMessage: `Updated ${changedFields.join(", ")}` }); } } const updated = await this.getById(id); if (!updated) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "UPDATE_WORKSPACE", "NOT_FOUND_AFTER_UPDATE"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.SYSTEM, text: `Workspace ${id} not found after update`, details: { id } }); } return updated; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "UPDATE_WORKSPACE", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async delete(id) { try { await this.deleteVersionsByParentId(id); await this.#client.execute({ sql: `DELETE FROM "${storage.TABLE_WORKSPACES}" WHERE "id" = ?`, args: [id] }); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "DELETE_WORKSPACE", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async list(args) { try { const { page = 0, perPage: perPageInput, orderBy, authorId, metadata } = args || {}; const { field, direction } = this.parseOrderBy(orderBy); const conditions = []; const queryParams = []; if (authorId !== void 0) { conditions.push("authorId = ?"); queryParams.push(authorId); } if (metadata && Object.keys(metadata).length > 0) { for (const [key, value] of Object.entries(metadata)) { if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key)) { throw new error.MastraError({ id: storage.createStorageErrorId("LIBSQL", "LIST_WORKSPACES", "INVALID_METADATA_KEY"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, text: `Invalid metadata key: ${key}. Keys must be alphanumeric with underscores.`, details: { key } }); } conditions.push(`json_extract(metadata, '$.${key}') = ?`); queryParams.push(typeof value === "string" ? value : JSON.stringify(value)); } } const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : ""; const countResult = await this.#client.execute({ sql: `SELECT COUNT(*) as count FROM "${storage.TABLE_WORKSPACES}" ${whereClause}`, args: queryParams }); const total = Number(countResult.rows?.[0]?.count ?? 0); if (total === 0) { return { workspaces: [], total: 0, page, perPage: perPageInput ?? 100, hasMore: false }; } const perPage = storage.normalizePerPage(perPageInput, 100); const { offset: start, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage); const limitValue = perPageInput === false ? total : perPage; const end = perPageInput === false ? total : start + perPage; const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_WORKSPACES)} FROM "${storage.TABLE_WORKSPACES}" ${whereClause} ORDER BY ${field} ${direction} LIMIT ? OFFSET ?`, args: [...queryParams, limitValue, start] }); const workspaces = result.rows?.map((row) => this.#parseWorkspaceRow(row)) ?? []; return { workspaces, total, page, perPage: perPageForResponse, hasMore: end < total }; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_WORKSPACES", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } // ========================================================================== // Workspace Version Methods // ========================================================================== async createVersion(input) { try { const now = /* @__PURE__ */ new Date(); await this.#client.execute({ sql: `INSERT INTO "${storage.TABLE_WORKSPACE_VERSIONS}" ( id, "workspaceId", "versionNumber", name, description, filesystem, sandbox, mounts, search, skills, tools, "autoSync", "operationTimeout", "changedFields", "changeMessage", "createdAt" ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, args: [ input.id, input.workspaceId, input.versionNumber, input.name, input.description ?? null, input.filesystem ? JSON.stringify(input.filesystem) : null, input.sandbox ? JSON.stringify(input.sandbox) : null, input.mounts ? JSON.stringify(input.mounts) : null, input.search ? JSON.stringify(input.search) : null, input.skills ? JSON.stringify(input.skills) : null, input.tools ? JSON.stringify(input.tools) : null, input.autoSync ? 1 : 0, input.operationTimeout ?? null, input.changedFields ? JSON.stringify(input.changedFields) : null, input.changeMessage ?? null, now.toISOString() ] }); return { ...input, createdAt: now }; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "CREATE_WORKSPACE_VERSION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async getVersion(id) { try { const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_WORKSPACE_VERSIONS)} FROM "${storage.TABLE_WORKSPACE_VERSIONS}" WHERE id = ?`, args: [id] }); const row = result.rows?.[0]; return row ? this.#parseVersionRow(row) : null; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_WORKSPACE_VERSION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async getVersionByNumber(workspaceId, versionNumber) { try { const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_WORKSPACE_VERSIONS)} FROM "${storage.TABLE_WORKSPACE_VERSIONS}" WHERE "workspaceId" = ? AND "versionNumber" = ?`, args: [workspaceId, versionNumber] }); const row = result.rows?.[0]; return row ? this.#parseVersionRow(row) : null; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_WORKSPACE_VERSION_BY_NUMBER", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async getLatestVersion(workspaceId) { try { const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_WORKSPACE_VERSIONS)} FROM "${storage.TABLE_WORKSPACE_VERSIONS}" WHERE "workspaceId" = ? ORDER BY "versionNumber" DESC LIMIT 1`, args: [workspaceId] }); const row = result.rows?.[0]; return row ? this.#parseVersionRow(row) : null; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "GET_LATEST_WORKSPACE_VERSION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async listVersions(input) { try { const { workspaceId, page = 0, perPage: perPageInput, orderBy } = input; const { field, direction } = this.parseVersionOrderBy(orderBy); const countResult = await this.#client.execute({ sql: `SELECT COUNT(*) as count FROM "${storage.TABLE_WORKSPACE_VERSIONS}" WHERE "workspaceId" = ?`, args: [workspaceId] }); const total = Number(countResult.rows?.[0]?.count ?? 0); if (total === 0) { return { versions: [], total: 0, page, perPage: perPageInput ?? 20, hasMore: false }; } const perPage = storage.normalizePerPage(perPageInput, 20); const { offset: start, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage); const limitValue = perPageInput === false ? total : perPage; const end = perPageInput === false ? total : start + perPage; const result = await this.#client.execute({ sql: `SELECT ${buildSelectColumns(storage.TABLE_WORKSPACE_VERSIONS)} FROM "${storage.TABLE_WORKSPACE_VERSIONS}" WHERE "workspaceId" = ? ORDER BY ${field} ${direction} LIMIT ? OFFSET ?`, args: [workspaceId, limitValue, start] }); const versions = result.rows?.map((row) => this.#parseVersionRow(row)) ?? []; return { versions, total, page, perPage: perPageForResponse, hasMore: end < total }; } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "LIST_WORKSPACE_VERSIONS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async deleteVersion(id) { try { await this.#client.execute({ sql: `DELETE FROM "${storage.TABLE_WORKSPACE_VERSIONS}" WHERE "id" = ?`, args: [id] }); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "DELETE_WORKSPACE_VERSION", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async deleteVersionsByParentId(entityId) { try { await this.#client.execute({ sql: `DELETE FROM "${storage.TABLE_WORKSPACE_VERSIONS}" WHERE "workspaceId" = ?`, args: [entityId] }); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "DELETE_WORKSPACE_VERSIONS_BY_WORKSPACE", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } async countVersions(workspaceId) { try { const result = await this.#client.execute({ sql: `SELECT COUNT(*) as count FROM "${storage.TABLE_WORKSPACE_VERSIONS}" WHERE "workspaceId" = ?`, args: [workspaceId] }); return Number(result.rows?.[0]?.count ?? 0); } catch (error$1) { if (error$1 instanceof error.MastraError) throw error$1; throw new error.MastraError( { id: storage.createStorageErrorId("LIBSQL", "COUNT_WORKSPACE_VERSIONS", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY }, error$1 ); } } // ========================================================================== // Private Helpers // ========================================================================== #parseWorkspaceRow(row) { const safeParseJSON = (val) => { if (val === null || val === void 0) return void 0; if (typeof val === "string") { try { return JSON.parse(val); } catch { return val; } } return val; }; return { id: row.id, status: row.status ?? "draft", activeVersionId: row.activeVersionId ?? void 0, authorId: row.authorId ?? void 0, metadata: safeParseJSON(row.metadata), createdAt: new Date(row.createdAt), updatedAt: new Date(row.updatedAt) }; } #parseVersionRow(row) { const safeParseJSON = (val) => { if (val === null || val === void 0) return void 0; if (typeof val === "string") { try { return JSON.parse(val); } catch { return val; } } return val; }; return { id: row.id, workspaceId: row.workspaceId, versionNumber: Number(row.versionNumber), name: row.name, description: row.description ?? void 0, filesystem: safeParseJSON(row.filesystem), sandbox: safeParseJSON(row.sandbox), mounts: safeParseJSON(row.mounts), search: safeParseJSON(row.search), skills: safeParseJSON(row.skills), tools: safeParseJSON(row.tools), autoSync: Boolean(row.autoSync), operationTimeout: row.operationTimeout != null ? Number(row.operationTimeout) : void 0, changedFields: safeParseJSON(row.changedFields), changeMessage: row.changeMessage ?? void 0, createdAt: new Date(row.createdAt) }; } }; // src/storage/index.ts var DEFAULT_LOCAL_CACHE_SIZE = -16e3; var DEFAULT_LOCAL_MMAP_SIZE = 134217728; var LibSQLStore = class extends storage.MastraCompositeStore { client; maxRetries; initialBackoffMs; connectionTimeoutMs; pragmasReady; isLocalDb; localPragmas; stores; constructor(config) { if (!config.id || typeof config.id !== "string" || config.id.trim() === "") { throw new Error("LibSQLStore: id must be provided and cannot be empty."); } super({ id: config.id, name: `LibSQLStore`, disableInit: config.disableInit }); this.maxRetries = config.maxRetries ?? 5; this.initialBackoffMs = config.initialBackoffMs ?? 100; this.connectionTimeoutMs = config.connectionTimeoutMs ?? DEFAULT_CONNECTION_TIMEOUT_MS; this.localPragmas = { cacheSize: config.localPragmas?.cacheSize ?? DEFAULT_LOCAL_CACHE_SIZE, mmapSize: config.localPragmas?.mmapSize ?? DEFAULT_LOCAL_MMAP_SIZE }; if ("url" in config) { if (config.url.includes(":memory:")) { this.shouldCacheInit = false; } this.isLocalDb = config.url.startsWith("file:") || config.url.includes(":memory:"); this.client = client.createClient({ url: config.url, ...config.authToken ? { authToken: config.authToken } : {}, // `busy_timeout` only applies to local sqlite3 connections; remote // contention is handled server-side. See libsql-client-ts#288/#345. ...this.isLocalDb ? { timeout: this.connectionTimeoutMs } : {} }); this.pragmasReady = this.isLocalDb ? this.applyLocalPragmas() : Promise.resolve(); } else { this.client = config.client; this.isLocalDb = false; this.pragmasReady = Promise.resolve(); } const domainConfig = { client: this.client, maxRetries: this.maxRetries, initialBackoffMs: this.initialBackoffMs }; const scores = new ScoresLibSQL(domainConfig); const workflows = new WorkflowsLibSQL(domainConfig); const memory = new MemoryLibSQL(domainConfig); const observability = new ObservabilityLibSQL(domainConfig); const agents = new AgentsLibSQL(domainConfig); const channels = new ChannelsLibSQL(domainConfig); const datasets = new DatasetsLibSQL(domainConfig); const experiments = new ExperimentsLibSQL(domainConfig); const promptBlocks = new PromptBlocksLibSQL(domainConfig); const scorerDefinitions = new ScorerDefinitionsLibSQL(domainConfig); const mcpClients = new MCPClientsLibSQL(domainConfig); const mcpServers = new MCPServersLibSQL(domainConfig); const workspaces = new WorkspacesLibSQL(domainConfig); const skills = new SkillsLibSQL(domainConfig); const favorites = new FavoritesLibSQL(domainConfig); const blobs = new BlobsLibSQL(domainConfig); const backgroundTasks = new BackgroundTasksLibSQL(domainConfig); const schedules = new SchedulesLibSQL(domainConfig); const harness = new HarnessLibSQL(domainConfig); const toolProviderConnections = new ToolProviderConnectionsLibSQL(domainConfig); const notifications = new NotificationsLibSQL(domainConfig); const threadState = new ThreadStateLibSQL(domainConfig); this.stores = { scores, workflows, memory, observability, agents, channels, datasets, experiments, promptBlocks, scorerDefinitions, mcpClients, mcpServers, workspaces, skills, favorites, blobs, backgroundTasks, schedules, harness, toolProviderConnections, notifications, threadState }; } async applyLocalPragmas() { const pragmas = [ ["journal_mode=WAL", "PRAGMA journal_mode=WAL;"], // Keep in sync with the connection-level `timeout` passed to createClient // so a custom connectionTimeoutMs isn't clobbered back to a hardcoded value. [`busy_timeout=${this.connectionTimeoutMs}`, `PRAGMA busy_timeout=${this.connectionTimeoutMs};`], ["synchronous=NORMAL", "PRAGMA synchronous=NORMAL;"], ["temp_store=MEMORY", "PRAGMA temp_store=MEMORY;"], [`cache_size=${this.localPragmas.cacheSize}`, `PRAGMA cache_size=${this.localPragmas.cacheSize};`], [`mmap_size=${this.localPragmas.mmapSize}`, `PRAGMA mmap_size=${this.localPragmas.mmapSize};`] ]; for (const [label, sql] of pragmas) { try { await this.client.execute(sql); this.logger.debug(`LibSQLStore: PRAGMA ${label} set.`); } catch (err) { this.logger.warn(`LibSQLStore: Failed to set PRAGMA ${label}.`, err); } } } getStoresToInit() { return Object.values(this.stores).filter(Boolean); } async initDomainsSequentially() { for (const store of this.getStoresToInit()) { await store.init(); } return true; } async initDomainsInParallel() { await Promise.all(this.getStoresToInit().map((store) => store.init())); return true; } async init() { await this.pragmasReady; if (!this.isLocalDb) { if (this.shouldCacheInit) { if (this.hasInitialized) { await this.hasInitialized; return; } this.hasInitialized = this.initDomainsInParallel(); await this.hasInitialized; return; } await this.initDomainsInParallel(); return; } if (this.shouldCacheInit) { if (this.hasInitialized) { await this.hasInitialized; return; } this.hasInitialized = this.initDomainsSequentially(); await this.hasInitialized; return; } await this.initDomainsSequentially(); } /** * Closes the underlying libsql client, releasing all OS file handles. * * For local file databases, first runs PRAGMA wal_checkpoint(TRUNCATE) and * switches back to journal_mode=DELETE so that Windows releases the -wal * and -shm sidecar files promptly. Without this, the handles stay open * until process exit, causing EBUSY errors when callers try to fs.rm the * storage directory after Mastra.shutdown(). * * Remote (Turso) databases skip the WAL pragmas and just close the client. * * Safe to call more than once; subsequent calls are no-ops. */ async close() { if (this.client.closed) { return; } const isLocalFileDb = this.isLocalDb || this.client.protocol === "file"; if (isLocalFileDb) { try { await this.client.execute("PRAGMA wal_checkpoint(TRUNCATE);"); await this.client.execute("PRAGMA journal_mode=DELETE;"); } catch (err) { this.logger.warn("LibSQLStore: Failed to checkpoint WAL before close.", err); } } this.client.close(); } }; // src/vector/prompt.ts var LIBSQL_PROMPT = `When querying LibSQL Vector, you can ONLY use the operators listed below. Any other operators will be rejected. Important: Don't explain how to construct the filter - use the specified operators and fields to search the content and return relevant results. If a user tries to give an explicit operator that is not supported, reject the filter entirely and let them know that the operator is not supported. Basic Comparison Operators: - $eq: Exact match (default when using field: value) Example: { "category": "electronics" } - $ne: Not equal Example: { "category": { "$ne": "electronics" } } - $gt: Greater than Example: { "price": { "$gt": 100 } } - $gte: Greater than or equal Example: { "price": { "$gte": 100 } } - $lt: Less than Example: { "price": { "$lt": 100 } } - $lte: Less than or equal Example: { "price": { "$lte": 100 } } Array Operators: - $in: Match any value in array Example: { "category": { "$in": ["electronics", "books"] } } - $nin: Does not match any value in array Example: { "category": { "$nin": ["electronics", "books"] } } - $all: Match all values in array Example: { "tags": { "$all": ["premium", "sale"] } } - $elemMatch: Match array elements that meet all specified conditions Example: { "items": { "$elemMatch": { "price": { "$gt": 100 } } } } - $contains: Check if array contains value Example: { "tags": { "$contains": "premium" } } Logical Operators: - $and: Logical AND (implicit when using multiple conditions) Example: { "$and": [{ "price": { "$gt": 100 } }, { "category": "electronics" }] } - $or: Logical OR Example: { "$or": [{ "price": { "$lt": 50 } }, { "category": "books" }] } - $not: Logical NOT Example: { "$not": { "category": "electronics" } } - $nor: Logical NOR Example: { "$nor": [{ "price": { "$lt": 50 } }, { "category": "books" }] } Element Operators: - $exists: Check if field exists Example: { "rating": { "$exists": true } } Special Operators: - $size: Array length check Example: { "tags": { "$size": 2 } } Restrictions: - Regex patterns are not supported - Direct RegExp patterns will throw an error - Nested fields are supported using dot notation - Multiple conditions on the same field are supported with both implicit and explicit $and - Array operations work on array fields only - Basic operators handle array values as JSON strings - Empty arrays in conditions are handled gracefully - Only logical operators ($and, $or, $not, $nor) can be used at the top level - All other operators must be used within a field condition Valid: { "field": { "$gt": 100 } } Valid: { "$and": [...] } Invalid: { "$gt": 100 } Invalid: { "$contains": "value" } - Logical operators must contain field conditions, not direct operators Valid: { "$and": [{ "field": { "$gt": 100 } }] } Invalid: { "$and": [{ "$gt": 100 }] } - $not operator: - Must be an object - Cannot be empty - Can be used at field level or top level - Valid: { "$not": { "field": "value" } } - Valid: { "field": { "$not": { "$eq": "value" } } } - Other logical operators ($and, $or, $nor): - Can only be used at top level or nested within other logical operators - Can not be used on a field level, or be nested inside a field - Can not be used inside an operator - Valid: { "$and": [{ "field": { "$gt": 100 } }] } - Valid: { "$or": [{ "$and": [{ "field": { "$gt": 100 } }] }] } - Invalid: { "field": { "$and": [{ "$gt": 100 }] } } - Invalid: { "field": { "$or": [{ "$gt": 100 }] } } - Invalid: { "field": { "$gt": { "$and": [{...}] } } } - $elemMatch requires an object with conditions Valid: { "array": { "$elemMatch": { "field": "value" } } } Invalid: { "array": { "$elemMatch": "value" } } Example Complex Query: { "$and": [ { "category": { "$in": ["electronics", "computers"] } }, { "price": { "$gte": 100, "$lte": 1000 } }, { "tags": { "$all": ["premium", "sale"] } }, { "items": { "$elemMatch": { "price": { "$gt": 50 }, "inStock": true } } }, { "$or": [ { "stock": { "$gt": 0 } }, { "preorder": true } ]} ] }`; exports.AgentsLibSQL = AgentsLibSQL; exports.BackgroundTasksLibSQL = BackgroundTasksLibSQL; exports.BlobsLibSQL = BlobsLibSQL; exports.ChannelsLibSQL = ChannelsLibSQL; exports.DatasetsLibSQL = DatasetsLibSQL; exports.DefaultStorage = LibSQLStore; exports.ExperimentsLibSQL = ExperimentsLibSQL; exports.FavoritesLibSQL = FavoritesLibSQL; exports.HarnessLibSQL = HarnessLibSQL; exports.LIBSQL_PROMPT = LIBSQL_PROMPT; exports.LibSQLStore = LibSQLStore; exports.LibSQLVector = LibSQLVector; exports.MCPClientsLibSQL = MCPClientsLibSQL; exports.MCPServersLibSQL = MCPServersLibSQL; exports.MemoryLibSQL = MemoryLibSQL; exports.NotificationsLibSQL = NotificationsLibSQL; exports.ObservabilityLibSQL = ObservabilityLibSQL; exports.PromptBlocksLibSQL = PromptBlocksLibSQL; exports.SchedulesLibSQL = SchedulesLibSQL; exports.ScorerDefinitionsLibSQL = ScorerDefinitionsLibSQL; exports.ScoresLibSQL = ScoresLibSQL; exports.SkillsLibSQL = SkillsLibSQL; exports.ThreadStateLibSQL = ThreadStateLibSQL; exports.ToolProviderConnectionsLibSQL = ToolProviderConnectionsLibSQL; exports.WorkflowsLibSQL = WorkflowsLibSQL; exports.WorkspacesLibSQL = WorkspacesLibSQL; //# sourceMappingURL=index.cjs.map //# sourceMappingURL=index.cjs.map