@mastra/libsql 0.0.0-1.x-tester-20251106055847

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,3389 @@
1
+ import { createClient } from '@libsql/client';
2
+ import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
3
+ import { parseSqlIdentifier, parseFieldKey } from '@mastra/core/utils';
4
+ import { MastraVector } from '@mastra/core/vector';
5
+ import { BaseFilterTranslator } from '@mastra/core/vector/filter';
6
+ import { MastraStorage, StoreOperations, TABLE_WORKFLOW_SNAPSHOT, TABLE_SPANS, ScoresStorage, TABLE_SCORERS, normalizePerPage, calculatePagination, safelyParseJSON, WorkflowsStorage, MemoryStorage, TABLE_MESSAGES, TABLE_THREADS, TABLE_RESOURCES, ObservabilityStorage, TABLE_SCHEMAS, SPAN_SCHEMA } from '@mastra/core/storage';
7
+ import { MessageList } from '@mastra/core/agent';
8
+ import { saveScorePayloadSchema } from '@mastra/core/evals';
9
+
10
+ // src/vector/index.ts
11
+ var LibSQLFilterTranslator = class extends BaseFilterTranslator {
12
+ getSupportedOperators() {
13
+ return {
14
+ ...BaseFilterTranslator.DEFAULT_OPERATORS,
15
+ regex: [],
16
+ custom: ["$contains", "$size"]
17
+ };
18
+ }
19
+ translate(filter) {
20
+ if (this.isEmpty(filter)) {
21
+ return filter;
22
+ }
23
+ this.validateFilter(filter);
24
+ return this.translateNode(filter);
25
+ }
26
+ translateNode(node, currentPath = "") {
27
+ if (this.isRegex(node)) {
28
+ throw new Error("Direct regex pattern format is not supported in LibSQL");
29
+ }
30
+ const withPath = (result2) => currentPath ? { [currentPath]: result2 } : result2;
31
+ if (this.isPrimitive(node)) {
32
+ return withPath({ $eq: this.normalizeComparisonValue(node) });
33
+ }
34
+ if (Array.isArray(node)) {
35
+ return withPath({ $in: this.normalizeArrayValues(node) });
36
+ }
37
+ const entries = Object.entries(node);
38
+ const result = {};
39
+ for (const [key, value] of entries) {
40
+ const newPath = currentPath ? `${currentPath}.${key}` : key;
41
+ if (this.isLogicalOperator(key)) {
42
+ result[key] = Array.isArray(value) ? value.map((filter) => this.translateNode(filter)) : this.translateNode(value);
43
+ } else if (this.isOperator(key)) {
44
+ if (this.isArrayOperator(key) && !Array.isArray(value) && key !== "$elemMatch") {
45
+ result[key] = [value];
46
+ } else if (this.isBasicOperator(key) && Array.isArray(value)) {
47
+ result[key] = JSON.stringify(value);
48
+ } else {
49
+ result[key] = value;
50
+ }
51
+ } else if (typeof value === "object" && value !== null) {
52
+ const hasOperators = Object.keys(value).some((k) => this.isOperator(k));
53
+ if (hasOperators) {
54
+ result[newPath] = this.translateNode(value);
55
+ } else {
56
+ Object.assign(result, this.translateNode(value, newPath));
57
+ }
58
+ } else {
59
+ result[newPath] = this.translateNode(value);
60
+ }
61
+ }
62
+ return result;
63
+ }
64
+ // TODO: Look more into regex support for LibSQL
65
+ // private translateRegexPattern(pattern: string, options: string = ''): any {
66
+ // if (!options) return { $regex: pattern };
67
+ // const flags = options
68
+ // .split('')
69
+ // .filter(f => 'imsux'.includes(f))
70
+ // .join('');
71
+ // return {
72
+ // $regex: pattern,
73
+ // $options: flags,
74
+ // };
75
+ // }
76
+ };
77
+ var createBasicOperator = (symbol) => {
78
+ return (key, value) => {
79
+ const jsonPath = getJsonPath(key);
80
+ return {
81
+ sql: `CASE
82
+ WHEN ? IS NULL THEN json_extract(metadata, ${jsonPath}) IS ${symbol === "=" ? "" : "NOT"} NULL
83
+ ELSE json_extract(metadata, ${jsonPath}) ${symbol} ?
84
+ END`,
85
+ needsValue: true,
86
+ transformValue: () => {
87
+ return [value, value];
88
+ }
89
+ };
90
+ };
91
+ };
92
+ var createNumericOperator = (symbol) => {
93
+ return (key) => {
94
+ const jsonPath = getJsonPath(key);
95
+ return {
96
+ sql: `CAST(json_extract(metadata, ${jsonPath}) AS NUMERIC) ${symbol} ?`,
97
+ needsValue: true
98
+ };
99
+ };
100
+ };
101
+ var validateJsonArray = (key) => {
102
+ const jsonPath = getJsonPath(key);
103
+ return `json_valid(json_extract(metadata, ${jsonPath}))
104
+ AND json_type(json_extract(metadata, ${jsonPath})) = 'array'`;
105
+ };
106
+ var pattern = /json_extract\(metadata, '\$\.(?:"[^"]*"(?:\."[^"]*")*|[^']+)'\)/g;
107
+ function buildElemMatchConditions(value) {
108
+ const conditions = Object.entries(value).map(([field, fieldValue]) => {
109
+ if (field.startsWith("$")) {
110
+ const { sql, values } = buildCondition("elem.value", { [field]: fieldValue });
111
+ const elemSql = sql.replace(pattern, "elem.value");
112
+ return { sql: elemSql, values };
113
+ } else if (typeof fieldValue === "object" && !Array.isArray(fieldValue)) {
114
+ const { sql, values } = buildCondition(field, fieldValue);
115
+ const jsonPath = parseJsonPathKey(field);
116
+ const elemSql = sql.replace(pattern, `json_extract(elem.value, '$.${jsonPath}')`);
117
+ return { sql: elemSql, values };
118
+ } else {
119
+ const jsonPath = parseJsonPathKey(field);
120
+ return {
121
+ sql: `json_extract(elem.value, '$.${jsonPath}') = ?`,
122
+ values: [fieldValue]
123
+ };
124
+ }
125
+ });
126
+ return conditions;
127
+ }
128
+ var FILTER_OPERATORS = {
129
+ $eq: createBasicOperator("="),
130
+ $ne: createBasicOperator("!="),
131
+ $gt: createNumericOperator(">"),
132
+ $gte: createNumericOperator(">="),
133
+ $lt: createNumericOperator("<"),
134
+ $lte: createNumericOperator("<="),
135
+ // Array Operators
136
+ $in: (key, value) => {
137
+ const jsonPath = getJsonPath(key);
138
+ const arr = Array.isArray(value) ? value : [value];
139
+ if (arr.length === 0) {
140
+ return { sql: "1 = 0", needsValue: true, transformValue: () => [] };
141
+ }
142
+ const paramPlaceholders = arr.map(() => "?").join(",");
143
+ return {
144
+ sql: `(
145
+ CASE
146
+ WHEN ${validateJsonArray(key)} THEN
147
+ EXISTS (
148
+ SELECT 1 FROM json_each(json_extract(metadata, ${jsonPath})) as elem
149
+ WHERE elem.value IN (SELECT value FROM json_each(?))
150
+ )
151
+ ELSE json_extract(metadata, ${jsonPath}) IN (${paramPlaceholders})
152
+ END
153
+ )`,
154
+ needsValue: true,
155
+ transformValue: () => [JSON.stringify(arr), ...arr]
156
+ };
157
+ },
158
+ $nin: (key, value) => {
159
+ const jsonPath = getJsonPath(key);
160
+ const arr = Array.isArray(value) ? value : [value];
161
+ if (arr.length === 0) {
162
+ return { sql: "1 = 1", needsValue: true, transformValue: () => [] };
163
+ }
164
+ const paramPlaceholders = arr.map(() => "?").join(",");
165
+ return {
166
+ sql: `(
167
+ CASE
168
+ WHEN ${validateJsonArray(key)} THEN
169
+ NOT EXISTS (
170
+ SELECT 1 FROM json_each(json_extract(metadata, ${jsonPath})) as elem
171
+ WHERE elem.value IN (SELECT value FROM json_each(?))
172
+ )
173
+ ELSE json_extract(metadata, ${jsonPath}) NOT IN (${paramPlaceholders})
174
+ END
175
+ )`,
176
+ needsValue: true,
177
+ transformValue: () => [JSON.stringify(arr), ...arr]
178
+ };
179
+ },
180
+ $all: (key, value) => {
181
+ const jsonPath = getJsonPath(key);
182
+ let sql;
183
+ const arrayValue = Array.isArray(value) ? value : [value];
184
+ if (arrayValue.length === 0) {
185
+ sql = "1 = 0";
186
+ } else {
187
+ sql = `(
188
+ CASE
189
+ WHEN ${validateJsonArray(key)} THEN
190
+ NOT EXISTS (
191
+ SELECT value
192
+ FROM json_each(?)
193
+ WHERE value NOT IN (
194
+ SELECT value
195
+ FROM json_each(json_extract(metadata, ${jsonPath}))
196
+ )
197
+ )
198
+ ELSE FALSE
199
+ END
200
+ )`;
201
+ }
202
+ return {
203
+ sql,
204
+ needsValue: true,
205
+ transformValue: () => {
206
+ if (arrayValue.length === 0) {
207
+ return [];
208
+ }
209
+ return [JSON.stringify(arrayValue)];
210
+ }
211
+ };
212
+ },
213
+ $elemMatch: (key, value) => {
214
+ const jsonPath = getJsonPath(key);
215
+ if (typeof value !== "object" || Array.isArray(value)) {
216
+ throw new Error("$elemMatch requires an object with conditions");
217
+ }
218
+ const conditions = buildElemMatchConditions(value);
219
+ return {
220
+ sql: `(
221
+ CASE
222
+ WHEN ${validateJsonArray(key)} THEN
223
+ EXISTS (
224
+ SELECT 1
225
+ FROM json_each(json_extract(metadata, ${jsonPath})) as elem
226
+ WHERE ${conditions.map((c) => c.sql).join(" AND ")}
227
+ )
228
+ ELSE FALSE
229
+ END
230
+ )`,
231
+ needsValue: true,
232
+ transformValue: () => conditions.flatMap((c) => c.values)
233
+ };
234
+ },
235
+ // Element Operators
236
+ $exists: (key) => {
237
+ const jsonPath = getJsonPath(key);
238
+ return {
239
+ sql: `json_extract(metadata, ${jsonPath}) IS NOT NULL`,
240
+ needsValue: false
241
+ };
242
+ },
243
+ // Logical Operators
244
+ $and: (key) => ({
245
+ sql: `(${key})`,
246
+ needsValue: false
247
+ }),
248
+ $or: (key) => ({
249
+ sql: `(${key})`,
250
+ needsValue: false
251
+ }),
252
+ $not: (key) => ({ sql: `NOT (${key})`, needsValue: false }),
253
+ $nor: (key) => ({
254
+ sql: `NOT (${key})`,
255
+ needsValue: false
256
+ }),
257
+ $size: (key, paramIndex) => {
258
+ const jsonPath = getJsonPath(key);
259
+ return {
260
+ sql: `(
261
+ CASE
262
+ WHEN json_type(json_extract(metadata, ${jsonPath})) = 'array' THEN
263
+ json_array_length(json_extract(metadata, ${jsonPath})) = $${paramIndex}
264
+ ELSE FALSE
265
+ END
266
+ )`,
267
+ needsValue: true
268
+ };
269
+ },
270
+ // /**
271
+ // * Regex Operators
272
+ // * Supports case insensitive and multiline
273
+ // */
274
+ // $regex: (key: string): FilterOperator => ({
275
+ // sql: `json_extract(metadata, '$."${toJsonPathKey(key)}"') = ?`,
276
+ // needsValue: true,
277
+ // transformValue: (value: any) => {
278
+ // const pattern = typeof value === 'object' ? value.$regex : value;
279
+ // const options = typeof value === 'object' ? value.$options || '' : '';
280
+ // let sql = `json_extract(metadata, '$."${toJsonPathKey(key)}"')`;
281
+ // // Handle multiline
282
+ // // if (options.includes('m')) {
283
+ // // sql = `REPLACE(${sql}, CHAR(10), '\n')`;
284
+ // // }
285
+ // // let finalPattern = pattern;
286
+ // // if (options) {
287
+ // // finalPattern = `(\\?${options})${pattern}`;
288
+ // // }
289
+ // // // Handle case insensitivity
290
+ // // if (options.includes('i')) {
291
+ // // sql = `LOWER(${sql}) REGEXP LOWER(?)`;
292
+ // // } else {
293
+ // // sql = `${sql} REGEXP ?`;
294
+ // // }
295
+ // if (options.includes('m')) {
296
+ // sql = `EXISTS (
297
+ // SELECT 1
298
+ // FROM json_each(
299
+ // json_array(
300
+ // ${sql},
301
+ // REPLACE(${sql}, CHAR(10), CHAR(13))
302
+ // )
303
+ // ) as lines
304
+ // WHERE lines.value REGEXP ?
305
+ // )`;
306
+ // } else {
307
+ // sql = `${sql} REGEXP ?`;
308
+ // }
309
+ // // Handle case insensitivity
310
+ // if (options.includes('i')) {
311
+ // sql = sql.replace('REGEXP ?', 'REGEXP LOWER(?)');
312
+ // sql = sql.replace('value REGEXP', 'LOWER(value) REGEXP');
313
+ // }
314
+ // // Handle extended - allows whitespace and comments in pattern
315
+ // if (options.includes('x')) {
316
+ // // Remove whitespace and comments from pattern
317
+ // const cleanPattern = pattern.replace(/\s+|#.*$/gm, '');
318
+ // return {
319
+ // sql,
320
+ // values: [cleanPattern],
321
+ // };
322
+ // }
323
+ // return {
324
+ // sql,
325
+ // values: [pattern],
326
+ // };
327
+ // },
328
+ // }),
329
+ $contains: (key, value) => {
330
+ const jsonPathKey = parseJsonPathKey(key);
331
+ let sql;
332
+ if (Array.isArray(value)) {
333
+ sql = `(
334
+ SELECT ${validateJsonArray(jsonPathKey)}
335
+ AND EXISTS (
336
+ SELECT 1
337
+ FROM json_each(json_extract(metadata, '$."${jsonPathKey}"')) as m
338
+ WHERE m.value IN (SELECT value FROM json_each(?))
339
+ )
340
+ )`;
341
+ } else if (typeof value === "string") {
342
+ sql = `lower(json_extract(metadata, '$."${jsonPathKey}"')) LIKE '%' || lower(?) || '%' ESCAPE '\\'`;
343
+ } else {
344
+ sql = `json_extract(metadata, '$."${jsonPathKey}"') = ?`;
345
+ }
346
+ return {
347
+ sql,
348
+ needsValue: true,
349
+ transformValue: () => {
350
+ if (Array.isArray(value)) {
351
+ return [JSON.stringify(value)];
352
+ }
353
+ if (typeof value === "object" && value !== null) {
354
+ return [JSON.stringify(value)];
355
+ }
356
+ if (typeof value === "string") {
357
+ return [escapeLikePattern(value)];
358
+ }
359
+ return [value];
360
+ }
361
+ };
362
+ }
363
+ /**
364
+ * $objectContains: True JSON containment for advanced use (deep sub-object match).
365
+ * Usage: { field: { $objectContains: { ...subobject } } }
366
+ */
367
+ // $objectContains: (key: string) => ({
368
+ // sql: '', // Will be overridden by transformValue
369
+ // needsValue: true,
370
+ // transformValue: (value: any) => ({
371
+ // sql: `json_type(json_extract(metadata, '$."${toJsonPathKey(key)}"')) = 'object'
372
+ // AND json_patch(json_extract(metadata, '$."${toJsonPathKey(key)}"'), ?) = json_extract(metadata, '$."${toJsonPathKey(key)}"')`,
373
+ // values: [JSON.stringify(value)],
374
+ // }),
375
+ // }),
376
+ };
377
+ function isFilterResult(obj) {
378
+ return obj && typeof obj === "object" && typeof obj.sql === "string" && Array.isArray(obj.values);
379
+ }
380
+ var parseJsonPathKey = (key) => {
381
+ const parsedKey = parseFieldKey(key);
382
+ if (parsedKey.includes(".")) {
383
+ return parsedKey.split(".").map((segment) => `"${segment}"`).join(".");
384
+ }
385
+ return parsedKey;
386
+ };
387
+ var getJsonPath = (key) => {
388
+ const jsonPathKey = parseJsonPathKey(key);
389
+ return `'$.${jsonPathKey}'`;
390
+ };
391
+ function escapeLikePattern(str) {
392
+ return str.replace(/([%_\\])/g, "\\$1");
393
+ }
394
+ function buildFilterQuery(filter) {
395
+ if (!filter) {
396
+ return { sql: "", values: [] };
397
+ }
398
+ const values = [];
399
+ const conditions = Object.entries(filter).map(([key, value]) => {
400
+ const condition = buildCondition(key, value);
401
+ values.push(...condition.values);
402
+ return condition.sql;
403
+ }).join(" AND ");
404
+ return {
405
+ sql: conditions ? `WHERE ${conditions}` : "",
406
+ values
407
+ };
408
+ }
409
+ function buildCondition(key, value, parentPath) {
410
+ if (["$and", "$or", "$not", "$nor"].includes(key)) {
411
+ return handleLogicalOperator(key, value);
412
+ }
413
+ if (!value || typeof value !== "object") {
414
+ const jsonPath = getJsonPath(key);
415
+ return {
416
+ sql: `json_extract(metadata, ${jsonPath}) = ?`,
417
+ values: [value]
418
+ };
419
+ }
420
+ return handleOperator(key, value);
421
+ }
422
+ function handleLogicalOperator(key, value, parentPath) {
423
+ if (!value || Array.isArray(value) && value.length === 0) {
424
+ switch (key) {
425
+ case "$and":
426
+ case "$nor":
427
+ return { sql: "true", values: [] };
428
+ case "$or":
429
+ return { sql: "false", values: [] };
430
+ case "$not":
431
+ throw new Error("$not operator cannot be empty");
432
+ default:
433
+ return { sql: "true", values: [] };
434
+ }
435
+ }
436
+ if (key === "$not") {
437
+ const entries = Object.entries(value);
438
+ const conditions2 = entries.map(([fieldKey, fieldValue]) => buildCondition(fieldKey, fieldValue));
439
+ return {
440
+ sql: `NOT (${conditions2.map((c) => c.sql).join(" AND ")})`,
441
+ values: conditions2.flatMap((c) => c.values)
442
+ };
443
+ }
444
+ const values = [];
445
+ const joinOperator = key === "$or" || key === "$nor" ? "OR" : "AND";
446
+ const conditions = Array.isArray(value) ? value.map((f) => {
447
+ const entries = !!f ? Object.entries(f) : [];
448
+ return entries.map(([k, v]) => buildCondition(k, v));
449
+ }) : [buildCondition(key, value)];
450
+ const joined = conditions.flat().map((c) => {
451
+ values.push(...c.values);
452
+ return c.sql;
453
+ }).join(` ${joinOperator} `);
454
+ return {
455
+ sql: key === "$nor" ? `NOT (${joined})` : `(${joined})`,
456
+ values
457
+ };
458
+ }
459
+ function handleOperator(key, value) {
460
+ if (typeof value === "object" && !Array.isArray(value)) {
461
+ const entries = Object.entries(value);
462
+ const results = entries.map(
463
+ ([operator2, operatorValue2]) => operator2 === "$not" ? {
464
+ sql: `NOT (${Object.entries(operatorValue2).map(([op, val]) => processOperator(key, op, val).sql).join(" AND ")})`,
465
+ values: Object.entries(operatorValue2).flatMap(
466
+ ([op, val]) => processOperator(key, op, val).values
467
+ )
468
+ } : processOperator(key, operator2, operatorValue2)
469
+ );
470
+ return {
471
+ sql: `(${results.map((r) => r.sql).join(" AND ")})`,
472
+ values: results.flatMap((r) => r.values)
473
+ };
474
+ }
475
+ const [[operator, operatorValue] = []] = Object.entries(value);
476
+ return processOperator(key, operator, operatorValue);
477
+ }
478
+ var processOperator = (key, operator, operatorValue) => {
479
+ if (!operator.startsWith("$") || !FILTER_OPERATORS[operator]) {
480
+ throw new Error(`Invalid operator: ${operator}`);
481
+ }
482
+ const operatorFn = FILTER_OPERATORS[operator];
483
+ const operatorResult = operatorFn(key, operatorValue);
484
+ if (!operatorResult.needsValue) {
485
+ return { sql: operatorResult.sql, values: [] };
486
+ }
487
+ const transformed = operatorResult.transformValue ? operatorResult.transformValue() : operatorValue;
488
+ if (isFilterResult(transformed)) {
489
+ return transformed;
490
+ }
491
+ return {
492
+ sql: operatorResult.sql,
493
+ values: Array.isArray(transformed) ? transformed : [transformed]
494
+ };
495
+ };
496
+
497
+ // src/vector/index.ts
498
+ var LibSQLVector = class extends MastraVector {
499
+ turso;
500
+ maxRetries;
501
+ initialBackoffMs;
502
+ constructor({
503
+ connectionUrl,
504
+ authToken,
505
+ syncUrl,
506
+ syncInterval,
507
+ maxRetries = 5,
508
+ initialBackoffMs = 100,
509
+ id
510
+ }) {
511
+ super({ id });
512
+ this.turso = createClient({
513
+ url: connectionUrl,
514
+ syncUrl,
515
+ authToken,
516
+ syncInterval
517
+ });
518
+ this.maxRetries = maxRetries;
519
+ this.initialBackoffMs = initialBackoffMs;
520
+ if (connectionUrl.includes(`file:`) || connectionUrl.includes(`:memory:`)) {
521
+ 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));
522
+ 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));
523
+ }
524
+ }
525
+ async executeWriteOperationWithRetry(operation, isTransaction = false) {
526
+ let attempts = 0;
527
+ let backoff = this.initialBackoffMs;
528
+ while (attempts < this.maxRetries) {
529
+ try {
530
+ return await operation();
531
+ } catch (error) {
532
+ if (error.code === "SQLITE_BUSY" || error.message && error.message.toLowerCase().includes("database is locked")) {
533
+ attempts++;
534
+ if (attempts >= this.maxRetries) {
535
+ this.logger.error(
536
+ `LibSQLVector: Operation failed after ${this.maxRetries} attempts due to: ${error.message}`,
537
+ error
538
+ );
539
+ throw error;
540
+ }
541
+ this.logger.warn(
542
+ `LibSQLVector: Attempt ${attempts} failed due to ${isTransaction ? "transaction " : ""}database lock. Retrying in ${backoff}ms...`
543
+ );
544
+ await new Promise((resolve) => setTimeout(resolve, backoff));
545
+ backoff *= 2;
546
+ } else {
547
+ throw error;
548
+ }
549
+ }
550
+ }
551
+ throw new Error("LibSQLVector: Max retries reached, but no error was re-thrown from the loop.");
552
+ }
553
+ transformFilter(filter) {
554
+ const translator = new LibSQLFilterTranslator();
555
+ return translator.translate(filter);
556
+ }
557
+ async query({
558
+ indexName,
559
+ queryVector,
560
+ topK = 10,
561
+ filter,
562
+ includeVector = false,
563
+ minScore = -1
564
+ // Default to -1 to include all results (cosine similarity ranges from -1 to 1)
565
+ }) {
566
+ try {
567
+ if (!Number.isInteger(topK) || topK <= 0) {
568
+ throw new Error("topK must be a positive integer");
569
+ }
570
+ if (!Array.isArray(queryVector) || !queryVector.every((x) => typeof x === "number" && Number.isFinite(x))) {
571
+ throw new Error("queryVector must be an array of finite numbers");
572
+ }
573
+ } catch (error) {
574
+ throw new MastraError(
575
+ {
576
+ id: "LIBSQL_VECTOR_QUERY_INVALID_ARGS",
577
+ domain: ErrorDomain.STORAGE,
578
+ category: ErrorCategory.USER
579
+ },
580
+ error
581
+ );
582
+ }
583
+ try {
584
+ const parsedIndexName = parseSqlIdentifier(indexName, "index name");
585
+ const vectorStr = `[${queryVector.join(",")}]`;
586
+ const translatedFilter = this.transformFilter(filter);
587
+ const { sql: filterQuery, values: filterValues } = buildFilterQuery(translatedFilter);
588
+ filterValues.push(minScore);
589
+ filterValues.push(topK);
590
+ const query = `
591
+ WITH vector_scores AS (
592
+ SELECT
593
+ vector_id as id,
594
+ (1-vector_distance_cos(embedding, '${vectorStr}')) as score,
595
+ metadata
596
+ ${includeVector ? ", vector_extract(embedding) as embedding" : ""}
597
+ FROM ${parsedIndexName}
598
+ ${filterQuery}
599
+ )
600
+ SELECT *
601
+ FROM vector_scores
602
+ WHERE score > ?
603
+ ORDER BY score DESC
604
+ LIMIT ?`;
605
+ const result = await this.turso.execute({
606
+ sql: query,
607
+ args: filterValues
608
+ });
609
+ return result.rows.map(({ id, score, metadata, embedding }) => ({
610
+ id,
611
+ score,
612
+ metadata: JSON.parse(metadata ?? "{}"),
613
+ ...includeVector && embedding && { vector: JSON.parse(embedding) }
614
+ }));
615
+ } catch (error) {
616
+ throw new MastraError(
617
+ {
618
+ id: "LIBSQL_VECTOR_QUERY_FAILED",
619
+ domain: ErrorDomain.STORAGE,
620
+ category: ErrorCategory.THIRD_PARTY
621
+ },
622
+ error
623
+ );
624
+ }
625
+ }
626
+ upsert(args) {
627
+ try {
628
+ return this.executeWriteOperationWithRetry(() => this.doUpsert(args), true);
629
+ } catch (error) {
630
+ throw new MastraError(
631
+ {
632
+ id: "LIBSQL_VECTOR_UPSERT_FAILED",
633
+ domain: ErrorDomain.STORAGE,
634
+ category: ErrorCategory.THIRD_PARTY
635
+ },
636
+ error
637
+ );
638
+ }
639
+ }
640
+ async doUpsert({ indexName, vectors, metadata, ids }) {
641
+ const tx = await this.turso.transaction("write");
642
+ try {
643
+ const parsedIndexName = parseSqlIdentifier(indexName, "index name");
644
+ const vectorIds = ids || vectors.map(() => crypto.randomUUID());
645
+ for (let i = 0; i < vectors.length; i++) {
646
+ const query = `
647
+ INSERT INTO ${parsedIndexName} (vector_id, embedding, metadata)
648
+ VALUES (?, vector32(?), ?)
649
+ ON CONFLICT(vector_id) DO UPDATE SET
650
+ embedding = vector32(?),
651
+ metadata = ?
652
+ `;
653
+ await tx.execute({
654
+ sql: query,
655
+ args: [
656
+ vectorIds[i],
657
+ JSON.stringify(vectors[i]),
658
+ JSON.stringify(metadata?.[i] || {}),
659
+ JSON.stringify(vectors[i]),
660
+ JSON.stringify(metadata?.[i] || {})
661
+ ]
662
+ });
663
+ }
664
+ await tx.commit();
665
+ return vectorIds;
666
+ } catch (error) {
667
+ !tx.closed && await tx.rollback();
668
+ if (error instanceof Error && error.message?.includes("dimensions are different")) {
669
+ const match = error.message.match(/dimensions are different: (\d+) != (\d+)/);
670
+ if (match) {
671
+ const [, actual, expected] = match;
672
+ throw new Error(
673
+ `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.`
674
+ );
675
+ }
676
+ }
677
+ throw error;
678
+ }
679
+ }
680
+ createIndex(args) {
681
+ try {
682
+ return this.executeWriteOperationWithRetry(() => this.doCreateIndex(args));
683
+ } catch (error) {
684
+ throw new MastraError(
685
+ {
686
+ id: "LIBSQL_VECTOR_CREATE_INDEX_FAILED",
687
+ domain: ErrorDomain.STORAGE,
688
+ category: ErrorCategory.THIRD_PARTY,
689
+ details: { indexName: args.indexName, dimension: args.dimension }
690
+ },
691
+ error
692
+ );
693
+ }
694
+ }
695
+ async doCreateIndex({ indexName, dimension }) {
696
+ if (!Number.isInteger(dimension) || dimension <= 0) {
697
+ throw new Error("Dimension must be a positive integer");
698
+ }
699
+ const parsedIndexName = parseSqlIdentifier(indexName, "index name");
700
+ await this.turso.execute({
701
+ sql: `
702
+ CREATE TABLE IF NOT EXISTS ${parsedIndexName} (
703
+ id SERIAL PRIMARY KEY,
704
+ vector_id TEXT UNIQUE NOT NULL,
705
+ embedding F32_BLOB(${dimension}),
706
+ metadata TEXT DEFAULT '{}'
707
+ );
708
+ `,
709
+ args: []
710
+ });
711
+ await this.turso.execute({
712
+ sql: `
713
+ CREATE INDEX IF NOT EXISTS ${parsedIndexName}_vector_idx
714
+ ON ${parsedIndexName} (libsql_vector_idx(embedding))
715
+ `,
716
+ args: []
717
+ });
718
+ }
719
+ deleteIndex(args) {
720
+ try {
721
+ return this.executeWriteOperationWithRetry(() => this.doDeleteIndex(args));
722
+ } catch (error) {
723
+ throw new MastraError(
724
+ {
725
+ id: "LIBSQL_VECTOR_DELETE_INDEX_FAILED",
726
+ domain: ErrorDomain.STORAGE,
727
+ category: ErrorCategory.THIRD_PARTY,
728
+ details: { indexName: args.indexName }
729
+ },
730
+ error
731
+ );
732
+ }
733
+ }
734
+ async doDeleteIndex({ indexName }) {
735
+ const parsedIndexName = parseSqlIdentifier(indexName, "index name");
736
+ await this.turso.execute({
737
+ sql: `DROP TABLE IF EXISTS ${parsedIndexName}`,
738
+ args: []
739
+ });
740
+ }
741
+ async listIndexes() {
742
+ try {
743
+ const vectorTablesQuery = `
744
+ SELECT name FROM sqlite_master
745
+ WHERE type='table'
746
+ AND sql LIKE '%F32_BLOB%';
747
+ `;
748
+ const result = await this.turso.execute({
749
+ sql: vectorTablesQuery,
750
+ args: []
751
+ });
752
+ return result.rows.map((row) => row.name);
753
+ } catch (error) {
754
+ throw new MastraError(
755
+ {
756
+ id: "LIBSQL_VECTOR_LIST_INDEXES_FAILED",
757
+ domain: ErrorDomain.STORAGE,
758
+ category: ErrorCategory.THIRD_PARTY
759
+ },
760
+ error
761
+ );
762
+ }
763
+ }
764
+ /**
765
+ * Retrieves statistics about a vector index.
766
+ *
767
+ * @param {string} indexName - The name of the index to describe
768
+ * @returns A promise that resolves to the index statistics including dimension, count and metric
769
+ */
770
+ async describeIndex({ indexName }) {
771
+ try {
772
+ const parsedIndexName = parseSqlIdentifier(indexName, "index name");
773
+ const tableInfoQuery = `
774
+ SELECT sql
775
+ FROM sqlite_master
776
+ WHERE type='table'
777
+ AND name = ?;
778
+ `;
779
+ const tableInfo = await this.turso.execute({
780
+ sql: tableInfoQuery,
781
+ args: [parsedIndexName]
782
+ });
783
+ if (!tableInfo.rows[0]?.sql) {
784
+ throw new Error(`Table ${parsedIndexName} not found`);
785
+ }
786
+ const dimension = parseInt(tableInfo.rows[0].sql.match(/F32_BLOB\((\d+)\)/)?.[1] || "0");
787
+ const countQuery = `
788
+ SELECT COUNT(*) as count
789
+ FROM ${parsedIndexName};
790
+ `;
791
+ const countResult = await this.turso.execute({
792
+ sql: countQuery,
793
+ args: []
794
+ });
795
+ const metric = "cosine";
796
+ return {
797
+ dimension,
798
+ count: countResult?.rows?.[0]?.count ?? 0,
799
+ metric
800
+ };
801
+ } catch (e) {
802
+ throw new MastraError(
803
+ {
804
+ id: "LIBSQL_VECTOR_DESCRIBE_INDEX_FAILED",
805
+ domain: ErrorDomain.STORAGE,
806
+ category: ErrorCategory.THIRD_PARTY,
807
+ details: { indexName }
808
+ },
809
+ e
810
+ );
811
+ }
812
+ }
813
+ /**
814
+ * Updates a vector by its ID with the provided vector and/or metadata.
815
+ *
816
+ * @param indexName - The name of the index containing the vector.
817
+ * @param id - The ID of the vector to update.
818
+ * @param update - An object containing the vector and/or metadata to update.
819
+ * @param update.vector - An optional array of numbers representing the new vector.
820
+ * @param update.metadata - An optional record containing the new metadata.
821
+ * @returns A promise that resolves when the update is complete.
822
+ * @throws Will throw an error if no updates are provided or if the update operation fails.
823
+ */
824
+ updateVector(args) {
825
+ return this.executeWriteOperationWithRetry(() => this.doUpdateVector(args));
826
+ }
827
+ async doUpdateVector({ indexName, id, update }) {
828
+ const parsedIndexName = parseSqlIdentifier(indexName, "index name");
829
+ const updates = [];
830
+ const args = [];
831
+ if (update.vector) {
832
+ updates.push("embedding = vector32(?)");
833
+ args.push(JSON.stringify(update.vector));
834
+ }
835
+ if (update.metadata) {
836
+ updates.push("metadata = ?");
837
+ args.push(JSON.stringify(update.metadata));
838
+ }
839
+ if (updates.length === 0) {
840
+ throw new MastraError({
841
+ id: "LIBSQL_VECTOR_UPDATE_VECTOR_INVALID_ARGS",
842
+ domain: ErrorDomain.STORAGE,
843
+ category: ErrorCategory.USER,
844
+ details: { indexName, id },
845
+ text: "No updates provided"
846
+ });
847
+ }
848
+ args.push(id);
849
+ const query = `
850
+ UPDATE ${parsedIndexName}
851
+ SET ${updates.join(", ")}
852
+ WHERE vector_id = ?;
853
+ `;
854
+ try {
855
+ await this.turso.execute({
856
+ sql: query,
857
+ args
858
+ });
859
+ } catch (error) {
860
+ throw new MastraError(
861
+ {
862
+ id: "LIBSQL_VECTOR_UPDATE_VECTOR_FAILED",
863
+ domain: ErrorDomain.STORAGE,
864
+ category: ErrorCategory.THIRD_PARTY,
865
+ details: { indexName, id }
866
+ },
867
+ error
868
+ );
869
+ }
870
+ }
871
+ /**
872
+ * Deletes a vector by its ID.
873
+ * @param indexName - The name of the index containing the vector.
874
+ * @param id - The ID of the vector to delete.
875
+ * @returns A promise that resolves when the deletion is complete.
876
+ * @throws Will throw an error if the deletion operation fails.
877
+ */
878
+ deleteVector(args) {
879
+ try {
880
+ return this.executeWriteOperationWithRetry(() => this.doDeleteVector(args));
881
+ } catch (error) {
882
+ throw new MastraError(
883
+ {
884
+ id: "LIBSQL_VECTOR_DELETE_VECTOR_FAILED",
885
+ domain: ErrorDomain.STORAGE,
886
+ category: ErrorCategory.THIRD_PARTY,
887
+ details: { indexName: args.indexName, id: args.id }
888
+ },
889
+ error
890
+ );
891
+ }
892
+ }
893
+ async doDeleteVector({ indexName, id }) {
894
+ const parsedIndexName = parseSqlIdentifier(indexName, "index name");
895
+ await this.turso.execute({
896
+ sql: `DELETE FROM ${parsedIndexName} WHERE vector_id = ?`,
897
+ args: [id]
898
+ });
899
+ }
900
+ truncateIndex(args) {
901
+ try {
902
+ return this.executeWriteOperationWithRetry(() => this._doTruncateIndex(args));
903
+ } catch (error) {
904
+ throw new MastraError(
905
+ {
906
+ id: "LIBSQL_VECTOR_TRUNCATE_INDEX_FAILED",
907
+ domain: ErrorDomain.STORAGE,
908
+ category: ErrorCategory.THIRD_PARTY,
909
+ details: { indexName: args.indexName }
910
+ },
911
+ error
912
+ );
913
+ }
914
+ }
915
+ async _doTruncateIndex({ indexName }) {
916
+ await this.turso.execute({
917
+ sql: `DELETE FROM ${parseSqlIdentifier(indexName, "index name")}`,
918
+ args: []
919
+ });
920
+ }
921
+ };
922
+ var MemoryLibSQL = class extends MemoryStorage {
923
+ client;
924
+ operations;
925
+ constructor({ client, operations }) {
926
+ super();
927
+ this.client = client;
928
+ this.operations = operations;
929
+ }
930
+ parseRow(row) {
931
+ let content = row.content;
932
+ try {
933
+ content = JSON.parse(row.content);
934
+ } catch {
935
+ }
936
+ const result = {
937
+ id: row.id,
938
+ content,
939
+ role: row.role,
940
+ createdAt: new Date(row.createdAt),
941
+ threadId: row.thread_id,
942
+ resourceId: row.resourceId
943
+ };
944
+ if (row.type && row.type !== `v2`) result.type = row.type;
945
+ return result;
946
+ }
947
+ async _getIncludedMessages({
948
+ threadId,
949
+ include
950
+ }) {
951
+ if (!threadId.trim()) throw new Error("threadId must be a non-empty string");
952
+ if (!include) return null;
953
+ const unionQueries = [];
954
+ const params = [];
955
+ for (const inc of include) {
956
+ const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
957
+ const searchId = inc.threadId || threadId;
958
+ unionQueries.push(
959
+ `
960
+ SELECT * FROM (
961
+ WITH numbered_messages AS (
962
+ SELECT
963
+ id, content, role, type, "createdAt", thread_id, "resourceId",
964
+ ROW_NUMBER() OVER (ORDER BY "createdAt" ASC) as row_num
965
+ FROM "${TABLE_MESSAGES}"
966
+ WHERE thread_id = ?
967
+ ),
968
+ target_positions AS (
969
+ SELECT row_num as target_pos
970
+ FROM numbered_messages
971
+ WHERE id = ?
972
+ )
973
+ SELECT DISTINCT m.*
974
+ FROM numbered_messages m
975
+ CROSS JOIN target_positions t
976
+ WHERE m.row_num BETWEEN (t.target_pos - ?) AND (t.target_pos + ?)
977
+ )
978
+ `
979
+ // Keep ASC for final sorting after fetching context
980
+ );
981
+ params.push(searchId, id, withPreviousMessages, withNextMessages);
982
+ }
983
+ const finalQuery = unionQueries.join(" UNION ALL ") + ' ORDER BY "createdAt" ASC';
984
+ const includedResult = await this.client.execute({ sql: finalQuery, args: params });
985
+ const includedRows = includedResult.rows?.map((row) => this.parseRow(row));
986
+ const seen = /* @__PURE__ */ new Set();
987
+ const dedupedRows = includedRows.filter((row) => {
988
+ if (seen.has(row.id)) return false;
989
+ seen.add(row.id);
990
+ return true;
991
+ });
992
+ return dedupedRows;
993
+ }
994
+ async listMessagesById({ messageIds }) {
995
+ if (messageIds.length === 0) return { messages: [] };
996
+ try {
997
+ const sql = `
998
+ SELECT
999
+ id,
1000
+ content,
1001
+ role,
1002
+ type,
1003
+ "createdAt",
1004
+ thread_id,
1005
+ "resourceId"
1006
+ FROM "${TABLE_MESSAGES}"
1007
+ WHERE id IN (${messageIds.map(() => "?").join(", ")})
1008
+ ORDER BY "createdAt" DESC
1009
+ `;
1010
+ const result = await this.client.execute({ sql, args: messageIds });
1011
+ if (!result.rows) return { messages: [] };
1012
+ const list = new MessageList().add(result.rows.map(this.parseRow), "memory");
1013
+ return { messages: list.get.all.db() };
1014
+ } catch (error) {
1015
+ throw new MastraError(
1016
+ {
1017
+ id: "LIBSQL_STORE_LIST_MESSAGES_BY_ID_FAILED",
1018
+ domain: ErrorDomain.STORAGE,
1019
+ category: ErrorCategory.THIRD_PARTY,
1020
+ details: { messageIds: JSON.stringify(messageIds) }
1021
+ },
1022
+ error
1023
+ );
1024
+ }
1025
+ }
1026
+ async listMessages(args) {
1027
+ const { threadId, resourceId, include, filter, perPage: perPageInput, page = 0, orderBy } = args;
1028
+ if (!threadId.trim()) {
1029
+ throw new MastraError(
1030
+ {
1031
+ id: "STORAGE_LIBSQL_LIST_MESSAGES_INVALID_THREAD_ID",
1032
+ domain: ErrorDomain.STORAGE,
1033
+ category: ErrorCategory.THIRD_PARTY,
1034
+ details: { threadId }
1035
+ },
1036
+ new Error("threadId must be a non-empty string")
1037
+ );
1038
+ }
1039
+ if (page < 0) {
1040
+ throw new MastraError(
1041
+ {
1042
+ id: "LIBSQL_STORE_LIST_MESSAGES_INVALID_PAGE",
1043
+ domain: ErrorDomain.STORAGE,
1044
+ category: ErrorCategory.USER,
1045
+ details: { page }
1046
+ },
1047
+ new Error("page must be >= 0")
1048
+ );
1049
+ }
1050
+ const perPage = normalizePerPage(perPageInput, 40);
1051
+ const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
1052
+ try {
1053
+ const { field, direction } = this.parseOrderBy(orderBy, "ASC");
1054
+ const orderByStatement = `ORDER BY "${field}" ${direction}`;
1055
+ const conditions = [`thread_id = ?`];
1056
+ const queryParams = [threadId];
1057
+ if (resourceId) {
1058
+ conditions.push(`"resourceId" = ?`);
1059
+ queryParams.push(resourceId);
1060
+ }
1061
+ if (filter?.dateRange?.start) {
1062
+ conditions.push(`"createdAt" >= ?`);
1063
+ queryParams.push(
1064
+ filter.dateRange.start instanceof Date ? filter.dateRange.start.toISOString() : filter.dateRange.start
1065
+ );
1066
+ }
1067
+ if (filter?.dateRange?.end) {
1068
+ conditions.push(`"createdAt" <= ?`);
1069
+ queryParams.push(
1070
+ filter.dateRange.end instanceof Date ? filter.dateRange.end.toISOString() : filter.dateRange.end
1071
+ );
1072
+ }
1073
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1074
+ const countResult = await this.client.execute({
1075
+ sql: `SELECT COUNT(*) as count FROM ${TABLE_MESSAGES} ${whereClause}`,
1076
+ args: queryParams
1077
+ });
1078
+ const total = Number(countResult.rows?.[0]?.count ?? 0);
1079
+ const limitValue = perPageInput === false ? total : perPage;
1080
+ const dataResult = await this.client.execute({
1081
+ sql: `SELECT id, content, role, type, "createdAt", "resourceId", "thread_id" FROM ${TABLE_MESSAGES} ${whereClause} ${orderByStatement} LIMIT ? OFFSET ?`,
1082
+ args: [...queryParams, limitValue, offset]
1083
+ });
1084
+ const messages = (dataResult.rows || []).map((row) => this.parseRow(row));
1085
+ if (total === 0 && messages.length === 0 && (!include || include.length === 0)) {
1086
+ return {
1087
+ messages: [],
1088
+ total: 0,
1089
+ page,
1090
+ perPage: perPageForResponse,
1091
+ hasMore: false
1092
+ };
1093
+ }
1094
+ const messageIds = new Set(messages.map((m) => m.id));
1095
+ if (include && include.length > 0) {
1096
+ const includeMessages = await this._getIncludedMessages({ threadId, include });
1097
+ if (includeMessages) {
1098
+ for (const includeMsg of includeMessages) {
1099
+ if (!messageIds.has(includeMsg.id)) {
1100
+ messages.push(includeMsg);
1101
+ messageIds.add(includeMsg.id);
1102
+ }
1103
+ }
1104
+ }
1105
+ }
1106
+ const list = new MessageList().add(messages, "memory");
1107
+ let finalMessages = list.get.all.db();
1108
+ finalMessages = finalMessages.sort((a, b) => {
1109
+ const isDateField = field === "createdAt" || field === "updatedAt";
1110
+ const aValue = isDateField ? new Date(a[field]).getTime() : a[field];
1111
+ const bValue = isDateField ? new Date(b[field]).getTime() : b[field];
1112
+ if (typeof aValue === "number" && typeof bValue === "number") {
1113
+ return direction === "ASC" ? aValue - bValue : bValue - aValue;
1114
+ }
1115
+ return direction === "ASC" ? String(aValue).localeCompare(String(bValue)) : String(bValue).localeCompare(String(aValue));
1116
+ });
1117
+ const returnedThreadMessageIds = new Set(finalMessages.filter((m) => m.threadId === threadId).map((m) => m.id));
1118
+ const allThreadMessagesReturned = returnedThreadMessageIds.size >= total;
1119
+ const hasMore = perPageInput !== false && !allThreadMessagesReturned && offset + perPage < total;
1120
+ return {
1121
+ messages: finalMessages,
1122
+ total,
1123
+ page,
1124
+ perPage: perPageForResponse,
1125
+ hasMore
1126
+ };
1127
+ } catch (error) {
1128
+ const mastraError = new MastraError(
1129
+ {
1130
+ id: "LIBSQL_STORE_LIST_MESSAGES_FAILED",
1131
+ domain: ErrorDomain.STORAGE,
1132
+ category: ErrorCategory.THIRD_PARTY,
1133
+ details: {
1134
+ threadId,
1135
+ resourceId: resourceId ?? ""
1136
+ }
1137
+ },
1138
+ error
1139
+ );
1140
+ this.logger?.error?.(mastraError.toString());
1141
+ this.logger?.trackException?.(mastraError);
1142
+ return {
1143
+ messages: [],
1144
+ total: 0,
1145
+ page,
1146
+ perPage: perPageForResponse,
1147
+ hasMore: false
1148
+ };
1149
+ }
1150
+ }
1151
+ async saveMessages({ messages }) {
1152
+ if (messages.length === 0) return { messages };
1153
+ try {
1154
+ const threadId = messages[0]?.threadId;
1155
+ if (!threadId) {
1156
+ throw new Error("Thread ID is required");
1157
+ }
1158
+ const batchStatements = messages.map((message) => {
1159
+ const time = message.createdAt || /* @__PURE__ */ new Date();
1160
+ if (!message.threadId) {
1161
+ throw new Error(
1162
+ `Expected to find a threadId for message, but couldn't find one. An unexpected error has occurred.`
1163
+ );
1164
+ }
1165
+ if (!message.resourceId) {
1166
+ throw new Error(
1167
+ `Expected to find a resourceId for message, but couldn't find one. An unexpected error has occurred.`
1168
+ );
1169
+ }
1170
+ return {
1171
+ sql: `INSERT INTO "${TABLE_MESSAGES}" (id, thread_id, content, role, type, "createdAt", "resourceId")
1172
+ VALUES (?, ?, ?, ?, ?, ?, ?)
1173
+ ON CONFLICT(id) DO UPDATE SET
1174
+ thread_id=excluded.thread_id,
1175
+ content=excluded.content,
1176
+ role=excluded.role,
1177
+ type=excluded.type,
1178
+ "resourceId"=excluded."resourceId"
1179
+ `,
1180
+ args: [
1181
+ message.id,
1182
+ message.threadId,
1183
+ typeof message.content === "object" ? JSON.stringify(message.content) : message.content,
1184
+ message.role,
1185
+ message.type || "v2",
1186
+ time instanceof Date ? time.toISOString() : time,
1187
+ message.resourceId
1188
+ ]
1189
+ };
1190
+ });
1191
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1192
+ batchStatements.push({
1193
+ sql: `UPDATE "${TABLE_THREADS}" SET "updatedAt" = ? WHERE id = ?`,
1194
+ args: [now, threadId]
1195
+ });
1196
+ const BATCH_SIZE = 50;
1197
+ const messageStatements = batchStatements.slice(0, -1);
1198
+ const threadUpdateStatement = batchStatements[batchStatements.length - 1];
1199
+ for (let i = 0; i < messageStatements.length; i += BATCH_SIZE) {
1200
+ const batch = messageStatements.slice(i, i + BATCH_SIZE);
1201
+ if (batch.length > 0) {
1202
+ await this.client.batch(batch, "write");
1203
+ }
1204
+ }
1205
+ if (threadUpdateStatement) {
1206
+ await this.client.execute(threadUpdateStatement);
1207
+ }
1208
+ const list = new MessageList().add(messages, "memory");
1209
+ return { messages: list.get.all.db() };
1210
+ } catch (error) {
1211
+ throw new MastraError(
1212
+ {
1213
+ id: "LIBSQL_STORE_SAVE_MESSAGES_FAILED",
1214
+ domain: ErrorDomain.STORAGE,
1215
+ category: ErrorCategory.THIRD_PARTY
1216
+ },
1217
+ error
1218
+ );
1219
+ }
1220
+ }
1221
+ async updateMessages({
1222
+ messages
1223
+ }) {
1224
+ if (messages.length === 0) {
1225
+ return [];
1226
+ }
1227
+ const messageIds = messages.map((m) => m.id);
1228
+ const placeholders = messageIds.map(() => "?").join(",");
1229
+ const selectSql = `SELECT * FROM ${TABLE_MESSAGES} WHERE id IN (${placeholders})`;
1230
+ const existingResult = await this.client.execute({ sql: selectSql, args: messageIds });
1231
+ const existingMessages = existingResult.rows.map((row) => this.parseRow(row));
1232
+ if (existingMessages.length === 0) {
1233
+ return [];
1234
+ }
1235
+ const batchStatements = [];
1236
+ const threadIdsToUpdate = /* @__PURE__ */ new Set();
1237
+ const columnMapping = {
1238
+ threadId: "thread_id"
1239
+ };
1240
+ for (const existingMessage of existingMessages) {
1241
+ const updatePayload = messages.find((m) => m.id === existingMessage.id);
1242
+ if (!updatePayload) continue;
1243
+ const { id, ...fieldsToUpdate } = updatePayload;
1244
+ if (Object.keys(fieldsToUpdate).length === 0) continue;
1245
+ threadIdsToUpdate.add(existingMessage.threadId);
1246
+ if (updatePayload.threadId && updatePayload.threadId !== existingMessage.threadId) {
1247
+ threadIdsToUpdate.add(updatePayload.threadId);
1248
+ }
1249
+ const setClauses = [];
1250
+ const args = [];
1251
+ const updatableFields = { ...fieldsToUpdate };
1252
+ if (updatableFields.content) {
1253
+ const newContent = {
1254
+ ...existingMessage.content,
1255
+ ...updatableFields.content,
1256
+ // Deep merge metadata if it exists on both
1257
+ ...existingMessage.content?.metadata && updatableFields.content.metadata ? {
1258
+ metadata: {
1259
+ ...existingMessage.content.metadata,
1260
+ ...updatableFields.content.metadata
1261
+ }
1262
+ } : {}
1263
+ };
1264
+ setClauses.push(`${parseSqlIdentifier("content", "column name")} = ?`);
1265
+ args.push(JSON.stringify(newContent));
1266
+ delete updatableFields.content;
1267
+ }
1268
+ for (const key in updatableFields) {
1269
+ if (Object.prototype.hasOwnProperty.call(updatableFields, key)) {
1270
+ const dbKey = columnMapping[key] || key;
1271
+ setClauses.push(`${parseSqlIdentifier(dbKey, "column name")} = ?`);
1272
+ let value = updatableFields[key];
1273
+ if (typeof value === "object" && value !== null) {
1274
+ value = JSON.stringify(value);
1275
+ }
1276
+ args.push(value);
1277
+ }
1278
+ }
1279
+ if (setClauses.length === 0) continue;
1280
+ args.push(id);
1281
+ const sql = `UPDATE ${TABLE_MESSAGES} SET ${setClauses.join(", ")} WHERE id = ?`;
1282
+ batchStatements.push({ sql, args });
1283
+ }
1284
+ if (batchStatements.length === 0) {
1285
+ return existingMessages;
1286
+ }
1287
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1288
+ for (const threadId of threadIdsToUpdate) {
1289
+ if (threadId) {
1290
+ batchStatements.push({
1291
+ sql: `UPDATE ${TABLE_THREADS} SET updatedAt = ? WHERE id = ?`,
1292
+ args: [now, threadId]
1293
+ });
1294
+ }
1295
+ }
1296
+ await this.client.batch(batchStatements, "write");
1297
+ const updatedResult = await this.client.execute({ sql: selectSql, args: messageIds });
1298
+ return updatedResult.rows.map((row) => this.parseRow(row));
1299
+ }
1300
+ async deleteMessages(messageIds) {
1301
+ if (!messageIds || messageIds.length === 0) {
1302
+ return;
1303
+ }
1304
+ try {
1305
+ const BATCH_SIZE = 100;
1306
+ const threadIds = /* @__PURE__ */ new Set();
1307
+ const tx = await this.client.transaction("write");
1308
+ try {
1309
+ for (let i = 0; i < messageIds.length; i += BATCH_SIZE) {
1310
+ const batch = messageIds.slice(i, i + BATCH_SIZE);
1311
+ const placeholders = batch.map(() => "?").join(",");
1312
+ const result = await tx.execute({
1313
+ sql: `SELECT DISTINCT thread_id FROM "${TABLE_MESSAGES}" WHERE id IN (${placeholders})`,
1314
+ args: batch
1315
+ });
1316
+ result.rows?.forEach((row) => {
1317
+ if (row.thread_id) threadIds.add(row.thread_id);
1318
+ });
1319
+ await tx.execute({
1320
+ sql: `DELETE FROM "${TABLE_MESSAGES}" WHERE id IN (${placeholders})`,
1321
+ args: batch
1322
+ });
1323
+ }
1324
+ if (threadIds.size > 0) {
1325
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1326
+ for (const threadId of threadIds) {
1327
+ await tx.execute({
1328
+ sql: `UPDATE "${TABLE_THREADS}" SET "updatedAt" = ? WHERE id = ?`,
1329
+ args: [now, threadId]
1330
+ });
1331
+ }
1332
+ }
1333
+ await tx.commit();
1334
+ } catch (error) {
1335
+ await tx.rollback();
1336
+ throw error;
1337
+ }
1338
+ } catch (error) {
1339
+ throw new MastraError(
1340
+ {
1341
+ id: "LIBSQL_STORE_DELETE_MESSAGES_FAILED",
1342
+ domain: ErrorDomain.STORAGE,
1343
+ category: ErrorCategory.THIRD_PARTY,
1344
+ details: { messageIds: messageIds.join(", ") }
1345
+ },
1346
+ error
1347
+ );
1348
+ }
1349
+ }
1350
+ async getResourceById({ resourceId }) {
1351
+ const result = await this.operations.load({
1352
+ tableName: TABLE_RESOURCES,
1353
+ keys: { id: resourceId }
1354
+ });
1355
+ if (!result) {
1356
+ return null;
1357
+ }
1358
+ return {
1359
+ ...result,
1360
+ // Ensure workingMemory is always returned as a string, even if auto-parsed as JSON
1361
+ workingMemory: result.workingMemory && typeof result.workingMemory === "object" ? JSON.stringify(result.workingMemory) : result.workingMemory,
1362
+ metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata,
1363
+ createdAt: new Date(result.createdAt),
1364
+ updatedAt: new Date(result.updatedAt)
1365
+ };
1366
+ }
1367
+ async saveResource({ resource }) {
1368
+ await this.operations.insert({
1369
+ tableName: TABLE_RESOURCES,
1370
+ record: {
1371
+ ...resource,
1372
+ metadata: JSON.stringify(resource.metadata)
1373
+ }
1374
+ });
1375
+ return resource;
1376
+ }
1377
+ async updateResource({
1378
+ resourceId,
1379
+ workingMemory,
1380
+ metadata
1381
+ }) {
1382
+ const existingResource = await this.getResourceById({ resourceId });
1383
+ if (!existingResource) {
1384
+ const newResource = {
1385
+ id: resourceId,
1386
+ workingMemory,
1387
+ metadata: metadata || {},
1388
+ createdAt: /* @__PURE__ */ new Date(),
1389
+ updatedAt: /* @__PURE__ */ new Date()
1390
+ };
1391
+ return this.saveResource({ resource: newResource });
1392
+ }
1393
+ const updatedResource = {
1394
+ ...existingResource,
1395
+ workingMemory: workingMemory !== void 0 ? workingMemory : existingResource.workingMemory,
1396
+ metadata: {
1397
+ ...existingResource.metadata,
1398
+ ...metadata
1399
+ },
1400
+ updatedAt: /* @__PURE__ */ new Date()
1401
+ };
1402
+ const updates = [];
1403
+ const values = [];
1404
+ if (workingMemory !== void 0) {
1405
+ updates.push("workingMemory = ?");
1406
+ values.push(workingMemory);
1407
+ }
1408
+ if (metadata) {
1409
+ updates.push("metadata = ?");
1410
+ values.push(JSON.stringify(updatedResource.metadata));
1411
+ }
1412
+ updates.push("updatedAt = ?");
1413
+ values.push(updatedResource.updatedAt.toISOString());
1414
+ values.push(resourceId);
1415
+ await this.client.execute({
1416
+ sql: `UPDATE ${TABLE_RESOURCES} SET ${updates.join(", ")} WHERE id = ?`,
1417
+ args: values
1418
+ });
1419
+ return updatedResource;
1420
+ }
1421
+ async getThreadById({ threadId }) {
1422
+ try {
1423
+ const result = await this.operations.load({
1424
+ tableName: TABLE_THREADS,
1425
+ keys: { id: threadId }
1426
+ });
1427
+ if (!result) {
1428
+ return null;
1429
+ }
1430
+ return {
1431
+ ...result,
1432
+ metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata,
1433
+ createdAt: new Date(result.createdAt),
1434
+ updatedAt: new Date(result.updatedAt)
1435
+ };
1436
+ } catch (error) {
1437
+ throw new MastraError(
1438
+ {
1439
+ id: "LIBSQL_STORE_GET_THREAD_BY_ID_FAILED",
1440
+ domain: ErrorDomain.STORAGE,
1441
+ category: ErrorCategory.THIRD_PARTY,
1442
+ details: { threadId }
1443
+ },
1444
+ error
1445
+ );
1446
+ }
1447
+ }
1448
+ async listThreadsByResourceId(args) {
1449
+ const { resourceId, page = 0, perPage: perPageInput, orderBy } = args;
1450
+ if (page < 0) {
1451
+ throw new MastraError(
1452
+ {
1453
+ id: "LIBSQL_STORE_LIST_THREADS_BY_RESOURCE_ID_INVALID_PAGE",
1454
+ domain: ErrorDomain.STORAGE,
1455
+ category: ErrorCategory.USER,
1456
+ details: { page }
1457
+ },
1458
+ new Error("page must be >= 0")
1459
+ );
1460
+ }
1461
+ const perPage = normalizePerPage(perPageInput, 100);
1462
+ const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
1463
+ const { field, direction } = this.parseOrderBy(orderBy);
1464
+ try {
1465
+ const baseQuery = `FROM ${TABLE_THREADS} WHERE resourceId = ?`;
1466
+ const queryParams = [resourceId];
1467
+ const mapRowToStorageThreadType = (row) => ({
1468
+ id: row.id,
1469
+ resourceId: row.resourceId,
1470
+ title: row.title,
1471
+ createdAt: new Date(row.createdAt),
1472
+ // Convert string to Date
1473
+ updatedAt: new Date(row.updatedAt),
1474
+ // Convert string to Date
1475
+ metadata: typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata
1476
+ });
1477
+ const countResult = await this.client.execute({
1478
+ sql: `SELECT COUNT(*) as count ${baseQuery}`,
1479
+ args: queryParams
1480
+ });
1481
+ const total = Number(countResult.rows?.[0]?.count ?? 0);
1482
+ if (total === 0) {
1483
+ return {
1484
+ threads: [],
1485
+ total: 0,
1486
+ page,
1487
+ perPage: perPageForResponse,
1488
+ hasMore: false
1489
+ };
1490
+ }
1491
+ const limitValue = perPageInput === false ? total : perPage;
1492
+ const dataResult = await this.client.execute({
1493
+ sql: `SELECT * ${baseQuery} ORDER BY "${field}" ${direction} LIMIT ? OFFSET ?`,
1494
+ args: [...queryParams, limitValue, offset]
1495
+ });
1496
+ const threads = (dataResult.rows || []).map(mapRowToStorageThreadType);
1497
+ return {
1498
+ threads,
1499
+ total,
1500
+ page,
1501
+ perPage: perPageForResponse,
1502
+ hasMore: perPageInput === false ? false : offset + perPage < total
1503
+ };
1504
+ } catch (error) {
1505
+ const mastraError = new MastraError(
1506
+ {
1507
+ id: "LIBSQL_STORE_LIST_THREADS_BY_RESOURCE_ID_FAILED",
1508
+ domain: ErrorDomain.STORAGE,
1509
+ category: ErrorCategory.THIRD_PARTY,
1510
+ details: { resourceId }
1511
+ },
1512
+ error
1513
+ );
1514
+ this.logger?.trackException?.(mastraError);
1515
+ this.logger?.error?.(mastraError.toString());
1516
+ return {
1517
+ threads: [],
1518
+ total: 0,
1519
+ page,
1520
+ perPage: perPageForResponse,
1521
+ hasMore: false
1522
+ };
1523
+ }
1524
+ }
1525
+ async saveThread({ thread }) {
1526
+ try {
1527
+ await this.operations.insert({
1528
+ tableName: TABLE_THREADS,
1529
+ record: {
1530
+ ...thread,
1531
+ metadata: JSON.stringify(thread.metadata)
1532
+ }
1533
+ });
1534
+ return thread;
1535
+ } catch (error) {
1536
+ const mastraError = new MastraError(
1537
+ {
1538
+ id: "LIBSQL_STORE_SAVE_THREAD_FAILED",
1539
+ domain: ErrorDomain.STORAGE,
1540
+ category: ErrorCategory.THIRD_PARTY,
1541
+ details: { threadId: thread.id }
1542
+ },
1543
+ error
1544
+ );
1545
+ this.logger?.trackException?.(mastraError);
1546
+ this.logger?.error?.(mastraError.toString());
1547
+ throw mastraError;
1548
+ }
1549
+ }
1550
+ async updateThread({
1551
+ id,
1552
+ title,
1553
+ metadata
1554
+ }) {
1555
+ const thread = await this.getThreadById({ threadId: id });
1556
+ if (!thread) {
1557
+ throw new MastraError({
1558
+ id: "LIBSQL_STORE_UPDATE_THREAD_FAILED_THREAD_NOT_FOUND",
1559
+ domain: ErrorDomain.STORAGE,
1560
+ category: ErrorCategory.USER,
1561
+ text: `Thread ${id} not found`,
1562
+ details: {
1563
+ status: 404,
1564
+ threadId: id
1565
+ }
1566
+ });
1567
+ }
1568
+ const updatedThread = {
1569
+ ...thread,
1570
+ title,
1571
+ metadata: {
1572
+ ...thread.metadata,
1573
+ ...metadata
1574
+ }
1575
+ };
1576
+ try {
1577
+ await this.client.execute({
1578
+ sql: `UPDATE ${TABLE_THREADS} SET title = ?, metadata = ? WHERE id = ?`,
1579
+ args: [title, JSON.stringify(updatedThread.metadata), id]
1580
+ });
1581
+ return updatedThread;
1582
+ } catch (error) {
1583
+ throw new MastraError(
1584
+ {
1585
+ id: "LIBSQL_STORE_UPDATE_THREAD_FAILED",
1586
+ domain: ErrorDomain.STORAGE,
1587
+ category: ErrorCategory.THIRD_PARTY,
1588
+ text: `Failed to update thread ${id}`,
1589
+ details: { threadId: id }
1590
+ },
1591
+ error
1592
+ );
1593
+ }
1594
+ }
1595
+ async deleteThread({ threadId }) {
1596
+ try {
1597
+ await this.client.execute({
1598
+ sql: `DELETE FROM ${TABLE_MESSAGES} WHERE thread_id = ?`,
1599
+ args: [threadId]
1600
+ });
1601
+ await this.client.execute({
1602
+ sql: `DELETE FROM ${TABLE_THREADS} WHERE id = ?`,
1603
+ args: [threadId]
1604
+ });
1605
+ } catch (error) {
1606
+ throw new MastraError(
1607
+ {
1608
+ id: "LIBSQL_STORE_DELETE_THREAD_FAILED",
1609
+ domain: ErrorDomain.STORAGE,
1610
+ category: ErrorCategory.THIRD_PARTY,
1611
+ details: { threadId }
1612
+ },
1613
+ error
1614
+ );
1615
+ }
1616
+ }
1617
+ };
1618
+ function createExecuteWriteOperationWithRetry({
1619
+ logger,
1620
+ maxRetries,
1621
+ initialBackoffMs
1622
+ }) {
1623
+ return async function executeWriteOperationWithRetry(operationFn, operationDescription) {
1624
+ let retries = 0;
1625
+ while (true) {
1626
+ try {
1627
+ return await operationFn();
1628
+ } catch (error) {
1629
+ if (error.message && (error.message.includes("SQLITE_BUSY") || error.message.includes("database is locked")) && retries < maxRetries) {
1630
+ retries++;
1631
+ const backoffTime = initialBackoffMs * Math.pow(2, retries - 1);
1632
+ logger.warn(
1633
+ `LibSQLStore: Encountered SQLITE_BUSY during ${operationDescription}. Retrying (${retries}/${maxRetries}) in ${backoffTime}ms...`
1634
+ );
1635
+ await new Promise((resolve) => setTimeout(resolve, backoffTime));
1636
+ } else {
1637
+ logger.error(`LibSQLStore: Error during ${operationDescription} after ${retries} retries: ${error}`);
1638
+ throw error;
1639
+ }
1640
+ }
1641
+ }
1642
+ };
1643
+ }
1644
+ function prepareStatement({ tableName, record }) {
1645
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
1646
+ const columns = Object.keys(record).map((col) => parseSqlIdentifier(col, "column name"));
1647
+ const values = Object.values(record).map((v) => {
1648
+ if (typeof v === `undefined` || v === null) {
1649
+ return null;
1650
+ }
1651
+ if (v instanceof Date) {
1652
+ return v.toISOString();
1653
+ }
1654
+ return typeof v === "object" ? JSON.stringify(v) : v;
1655
+ });
1656
+ const placeholders = values.map(() => "?").join(", ");
1657
+ return {
1658
+ sql: `INSERT OR REPLACE INTO ${parsedTableName} (${columns.join(", ")}) VALUES (${placeholders})`,
1659
+ args: values
1660
+ };
1661
+ }
1662
+ function prepareUpdateStatement({
1663
+ tableName,
1664
+ updates,
1665
+ keys
1666
+ }) {
1667
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
1668
+ const schema = TABLE_SCHEMAS[tableName];
1669
+ const updateColumns = Object.keys(updates).map((col) => parseSqlIdentifier(col, "column name"));
1670
+ const updateValues = Object.values(updates).map(transformToSqlValue);
1671
+ const setClause = updateColumns.map((col) => `${col} = ?`).join(", ");
1672
+ const whereClause = prepareWhereClause(keys, schema);
1673
+ return {
1674
+ sql: `UPDATE ${parsedTableName} SET ${setClause}${whereClause.sql}`,
1675
+ args: [...updateValues, ...whereClause.args]
1676
+ };
1677
+ }
1678
+ function transformToSqlValue(value) {
1679
+ if (typeof value === "undefined" || value === null) {
1680
+ return null;
1681
+ }
1682
+ if (value instanceof Date) {
1683
+ return value.toISOString();
1684
+ }
1685
+ return typeof value === "object" ? JSON.stringify(value) : value;
1686
+ }
1687
+ function prepareDeleteStatement({ tableName, keys }) {
1688
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
1689
+ const whereClause = prepareWhereClause(keys, TABLE_SCHEMAS[tableName]);
1690
+ return {
1691
+ sql: `DELETE FROM ${parsedTableName}${whereClause.sql}`,
1692
+ args: whereClause.args
1693
+ };
1694
+ }
1695
+ function prepareWhereClause(filters, schema) {
1696
+ const conditions = [];
1697
+ const args = [];
1698
+ for (const [columnName, filterValue] of Object.entries(filters)) {
1699
+ const column = schema[columnName];
1700
+ if (!column) {
1701
+ throw new Error(`Unknown column: ${columnName}`);
1702
+ }
1703
+ const parsedColumn = parseSqlIdentifier(columnName, "column name");
1704
+ const result = buildCondition2(parsedColumn, filterValue);
1705
+ conditions.push(result.condition);
1706
+ args.push(...result.args);
1707
+ }
1708
+ return {
1709
+ sql: conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "",
1710
+ args
1711
+ };
1712
+ }
1713
+ function buildCondition2(columnName, filterValue) {
1714
+ if (filterValue === null) {
1715
+ return { condition: `${columnName} IS NULL`, args: [] };
1716
+ }
1717
+ if (typeof filterValue === "object" && filterValue !== null && ("startAt" in filterValue || "endAt" in filterValue)) {
1718
+ return buildDateRangeCondition(columnName, filterValue);
1719
+ }
1720
+ return {
1721
+ condition: `${columnName} = ?`,
1722
+ args: [transformToSqlValue(filterValue)]
1723
+ };
1724
+ }
1725
+ function buildDateRangeCondition(columnName, range) {
1726
+ const conditions = [];
1727
+ const args = [];
1728
+ if (range.startAt !== void 0) {
1729
+ conditions.push(`${columnName} >= ?`);
1730
+ args.push(transformToSqlValue(range.startAt));
1731
+ }
1732
+ if (range.endAt !== void 0) {
1733
+ conditions.push(`${columnName} <= ?`);
1734
+ args.push(transformToSqlValue(range.endAt));
1735
+ }
1736
+ if (conditions.length === 0) {
1737
+ throw new Error("Date range must specify at least startAt or endAt");
1738
+ }
1739
+ return {
1740
+ condition: conditions.join(" AND "),
1741
+ args
1742
+ };
1743
+ }
1744
+ function buildDateRangeFilter(dateRange, columnName = "createdAt") {
1745
+ if (!dateRange?.start && !dateRange?.end) {
1746
+ return {};
1747
+ }
1748
+ const filter = {};
1749
+ if (dateRange.start) {
1750
+ filter.startAt = new Date(dateRange.start).toISOString();
1751
+ }
1752
+ if (dateRange.end) {
1753
+ filter.endAt = new Date(dateRange.end).toISOString();
1754
+ }
1755
+ return { [columnName]: filter };
1756
+ }
1757
+ function transformFromSqlRow({
1758
+ tableName,
1759
+ sqlRow
1760
+ }) {
1761
+ const result = {};
1762
+ const jsonColumns = new Set(
1763
+ Object.keys(TABLE_SCHEMAS[tableName]).filter((key) => TABLE_SCHEMAS[tableName][key].type === "jsonb").map((key) => key)
1764
+ );
1765
+ const dateColumns = new Set(
1766
+ Object.keys(TABLE_SCHEMAS[tableName]).filter((key) => TABLE_SCHEMAS[tableName][key].type === "timestamp").map((key) => key)
1767
+ );
1768
+ for (const [key, value] of Object.entries(sqlRow)) {
1769
+ if (value === null || value === void 0) {
1770
+ result[key] = value;
1771
+ continue;
1772
+ }
1773
+ if (dateColumns.has(key) && typeof value === "string") {
1774
+ result[key] = new Date(value);
1775
+ continue;
1776
+ }
1777
+ if (jsonColumns.has(key) && typeof value === "string") {
1778
+ result[key] = safelyParseJSON(value);
1779
+ continue;
1780
+ }
1781
+ result[key] = value;
1782
+ }
1783
+ return result;
1784
+ }
1785
+
1786
+ // src/storage/domains/observability/index.ts
1787
+ var ObservabilityLibSQL = class extends ObservabilityStorage {
1788
+ operations;
1789
+ constructor({ operations }) {
1790
+ super();
1791
+ this.operations = operations;
1792
+ }
1793
+ async createSpan(span) {
1794
+ try {
1795
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1796
+ const record = {
1797
+ ...span,
1798
+ createdAt: now,
1799
+ updatedAt: now
1800
+ };
1801
+ return this.operations.insert({ tableName: TABLE_SPANS, record });
1802
+ } catch (error) {
1803
+ throw new MastraError(
1804
+ {
1805
+ id: "LIBSQL_STORE_CREATE_SPAN_FAILED",
1806
+ domain: ErrorDomain.STORAGE,
1807
+ category: ErrorCategory.USER,
1808
+ details: {
1809
+ spanId: span.spanId,
1810
+ traceId: span.traceId,
1811
+ spanType: span.spanType,
1812
+ spanName: span.name
1813
+ }
1814
+ },
1815
+ error
1816
+ );
1817
+ }
1818
+ }
1819
+ async getTrace(traceId) {
1820
+ try {
1821
+ const spans = await this.operations.loadMany({
1822
+ tableName: TABLE_SPANS,
1823
+ whereClause: { sql: " WHERE traceId = ?", args: [traceId] },
1824
+ orderBy: "startedAt DESC"
1825
+ });
1826
+ if (!spans || spans.length === 0) {
1827
+ return null;
1828
+ }
1829
+ return {
1830
+ traceId,
1831
+ spans: spans.map((span) => transformFromSqlRow({ tableName: TABLE_SPANS, sqlRow: span }))
1832
+ };
1833
+ } catch (error) {
1834
+ throw new MastraError(
1835
+ {
1836
+ id: "LIBSQL_STORE_GET_TRACE_FAILED",
1837
+ domain: ErrorDomain.STORAGE,
1838
+ category: ErrorCategory.USER,
1839
+ details: {
1840
+ traceId
1841
+ }
1842
+ },
1843
+ error
1844
+ );
1845
+ }
1846
+ }
1847
+ async updateSpan({
1848
+ spanId,
1849
+ traceId,
1850
+ updates
1851
+ }) {
1852
+ try {
1853
+ await this.operations.update({
1854
+ tableName: TABLE_SPANS,
1855
+ keys: { spanId, traceId },
1856
+ data: { ...updates, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }
1857
+ });
1858
+ } catch (error) {
1859
+ throw new MastraError(
1860
+ {
1861
+ id: "LIBSQL_STORE_UPDATE_SPAN_FAILED",
1862
+ domain: ErrorDomain.STORAGE,
1863
+ category: ErrorCategory.USER,
1864
+ details: {
1865
+ spanId,
1866
+ traceId
1867
+ }
1868
+ },
1869
+ error
1870
+ );
1871
+ }
1872
+ }
1873
+ async getTracesPaginated({
1874
+ filters,
1875
+ pagination
1876
+ }) {
1877
+ const page = pagination?.page ?? 0;
1878
+ const perPage = pagination?.perPage ?? 10;
1879
+ const { entityId, entityType, ...actualFilters } = filters || {};
1880
+ const filtersWithDateRange = {
1881
+ ...actualFilters,
1882
+ ...buildDateRangeFilter(pagination?.dateRange, "startedAt"),
1883
+ parentSpanId: null
1884
+ };
1885
+ const whereClause = prepareWhereClause(filtersWithDateRange, SPAN_SCHEMA);
1886
+ let actualWhereClause = whereClause.sql || "";
1887
+ if (entityId && entityType) {
1888
+ const statement = `name = ?`;
1889
+ let name = "";
1890
+ if (entityType === "workflow") {
1891
+ name = `workflow run: '${entityId}'`;
1892
+ } else if (entityType === "agent") {
1893
+ name = `agent run: '${entityId}'`;
1894
+ } else {
1895
+ const error = new MastraError({
1896
+ id: "LIBSQL_STORE_GET_TRACES_PAGINATED_FAILED",
1897
+ domain: ErrorDomain.STORAGE,
1898
+ category: ErrorCategory.USER,
1899
+ details: {
1900
+ entityType
1901
+ },
1902
+ text: `Cannot filter by entity type: ${entityType}`
1903
+ });
1904
+ this.logger?.trackException(error);
1905
+ throw error;
1906
+ }
1907
+ whereClause.args.push(name);
1908
+ if (actualWhereClause) {
1909
+ actualWhereClause += ` AND ${statement}`;
1910
+ } else {
1911
+ actualWhereClause += `WHERE ${statement}`;
1912
+ }
1913
+ }
1914
+ const orderBy = "startedAt DESC";
1915
+ let count = 0;
1916
+ try {
1917
+ count = await this.operations.loadTotalCount({
1918
+ tableName: TABLE_SPANS,
1919
+ whereClause: { sql: actualWhereClause, args: whereClause.args }
1920
+ });
1921
+ } catch (error) {
1922
+ throw new MastraError(
1923
+ {
1924
+ id: "LIBSQL_STORE_GET_TRACES_PAGINATED_COUNT_FAILED",
1925
+ domain: ErrorDomain.STORAGE,
1926
+ category: ErrorCategory.USER
1927
+ },
1928
+ error
1929
+ );
1930
+ }
1931
+ if (count === 0) {
1932
+ return {
1933
+ pagination: {
1934
+ total: 0,
1935
+ page,
1936
+ perPage,
1937
+ hasMore: false
1938
+ },
1939
+ spans: []
1940
+ };
1941
+ }
1942
+ try {
1943
+ const spans = await this.operations.loadMany({
1944
+ tableName: TABLE_SPANS,
1945
+ whereClause: {
1946
+ sql: actualWhereClause,
1947
+ args: whereClause.args
1948
+ },
1949
+ orderBy,
1950
+ offset: page * perPage,
1951
+ limit: perPage
1952
+ });
1953
+ return {
1954
+ pagination: {
1955
+ total: count,
1956
+ page,
1957
+ perPage,
1958
+ hasMore: spans.length === perPage
1959
+ },
1960
+ spans: spans.map((span) => transformFromSqlRow({ tableName: TABLE_SPANS, sqlRow: span }))
1961
+ };
1962
+ } catch (error) {
1963
+ throw new MastraError(
1964
+ {
1965
+ id: "LIBSQL_STORE_GET_TRACES_PAGINATED_FAILED",
1966
+ domain: ErrorDomain.STORAGE,
1967
+ category: ErrorCategory.USER
1968
+ },
1969
+ error
1970
+ );
1971
+ }
1972
+ }
1973
+ async batchCreateSpans(args) {
1974
+ try {
1975
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1976
+ return this.operations.batchInsert({
1977
+ tableName: TABLE_SPANS,
1978
+ records: args.records.map((record) => ({
1979
+ ...record,
1980
+ createdAt: now,
1981
+ updatedAt: now
1982
+ }))
1983
+ });
1984
+ } catch (error) {
1985
+ throw new MastraError(
1986
+ {
1987
+ id: "LIBSQL_STORE_BATCH_CREATE_SPANS_FAILED",
1988
+ domain: ErrorDomain.STORAGE,
1989
+ category: ErrorCategory.USER
1990
+ },
1991
+ error
1992
+ );
1993
+ }
1994
+ }
1995
+ async batchUpdateSpans(args) {
1996
+ try {
1997
+ return this.operations.batchUpdate({
1998
+ tableName: TABLE_SPANS,
1999
+ updates: args.records.map((record) => ({
2000
+ keys: { spanId: record.spanId, traceId: record.traceId },
2001
+ data: { ...record.updates, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }
2002
+ }))
2003
+ });
2004
+ } catch (error) {
2005
+ throw new MastraError(
2006
+ {
2007
+ id: "LIBSQL_STORE_BATCH_UPDATE_SPANS_FAILED",
2008
+ domain: ErrorDomain.STORAGE,
2009
+ category: ErrorCategory.USER
2010
+ },
2011
+ error
2012
+ );
2013
+ }
2014
+ }
2015
+ async batchDeleteTraces(args) {
2016
+ try {
2017
+ const keys = args.traceIds.map((traceId) => ({ traceId }));
2018
+ return this.operations.batchDelete({
2019
+ tableName: TABLE_SPANS,
2020
+ keys
2021
+ });
2022
+ } catch (error) {
2023
+ throw new MastraError(
2024
+ {
2025
+ id: "LIBSQL_STORE_BATCH_DELETE_TRACES_FAILED",
2026
+ domain: ErrorDomain.STORAGE,
2027
+ category: ErrorCategory.USER
2028
+ },
2029
+ error
2030
+ );
2031
+ }
2032
+ }
2033
+ };
2034
+ var StoreOperationsLibSQL = class extends StoreOperations {
2035
+ client;
2036
+ /**
2037
+ * Maximum number of retries for write operations if an SQLITE_BUSY error occurs.
2038
+ * @default 5
2039
+ */
2040
+ maxRetries;
2041
+ /**
2042
+ * Initial backoff time in milliseconds for retrying write operations on SQLITE_BUSY.
2043
+ * The backoff time will double with each retry (exponential backoff).
2044
+ * @default 100
2045
+ */
2046
+ initialBackoffMs;
2047
+ constructor({
2048
+ client,
2049
+ maxRetries,
2050
+ initialBackoffMs
2051
+ }) {
2052
+ super();
2053
+ this.client = client;
2054
+ this.maxRetries = maxRetries ?? 5;
2055
+ this.initialBackoffMs = initialBackoffMs ?? 100;
2056
+ }
2057
+ async hasColumn(table, column) {
2058
+ const result = await this.client.execute({
2059
+ sql: `PRAGMA table_info(${table})`
2060
+ });
2061
+ return (await result.rows)?.some((row) => row.name === column);
2062
+ }
2063
+ getCreateTableSQL(tableName, schema) {
2064
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
2065
+ const columns = Object.entries(schema).map(([name, col]) => {
2066
+ const parsedColumnName = parseSqlIdentifier(name, "column name");
2067
+ let type = col.type.toUpperCase();
2068
+ if (type === "TEXT") type = "TEXT";
2069
+ if (type === "TIMESTAMP") type = "TEXT";
2070
+ const nullable = col.nullable ? "" : "NOT NULL";
2071
+ const primaryKey = col.primaryKey ? "PRIMARY KEY" : "";
2072
+ return `${parsedColumnName} ${type} ${nullable} ${primaryKey}`.trim();
2073
+ });
2074
+ if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
2075
+ const stmnt = `CREATE TABLE IF NOT EXISTS ${parsedTableName} (
2076
+ ${columns.join(",\n")},
2077
+ PRIMARY KEY (workflow_name, run_id)
2078
+ )`;
2079
+ return stmnt;
2080
+ }
2081
+ if (tableName === TABLE_SPANS) {
2082
+ const stmnt = `CREATE TABLE IF NOT EXISTS ${parsedTableName} (
2083
+ ${columns.join(",\n")},
2084
+ PRIMARY KEY (traceId, spanId)
2085
+ )`;
2086
+ return stmnt;
2087
+ }
2088
+ return `CREATE TABLE IF NOT EXISTS ${parsedTableName} (${columns.join(", ")})`;
2089
+ }
2090
+ async createTable({
2091
+ tableName,
2092
+ schema
2093
+ }) {
2094
+ try {
2095
+ this.logger.debug(`Creating database table`, { tableName, operation: "schema init" });
2096
+ const sql = this.getCreateTableSQL(tableName, schema);
2097
+ await this.client.execute(sql);
2098
+ } catch (error) {
2099
+ throw new MastraError(
2100
+ {
2101
+ id: "LIBSQL_STORE_CREATE_TABLE_FAILED",
2102
+ domain: ErrorDomain.STORAGE,
2103
+ category: ErrorCategory.THIRD_PARTY,
2104
+ details: {
2105
+ tableName
2106
+ }
2107
+ },
2108
+ error
2109
+ );
2110
+ }
2111
+ }
2112
+ getSqlType(type) {
2113
+ switch (type) {
2114
+ case "bigint":
2115
+ return "INTEGER";
2116
+ // SQLite uses INTEGER for all integer sizes
2117
+ case "jsonb":
2118
+ return "TEXT";
2119
+ // Store JSON as TEXT in SQLite
2120
+ default:
2121
+ return super.getSqlType(type);
2122
+ }
2123
+ }
2124
+ async doInsert({
2125
+ tableName,
2126
+ record
2127
+ }) {
2128
+ await this.client.execute(
2129
+ prepareStatement({
2130
+ tableName,
2131
+ record
2132
+ })
2133
+ );
2134
+ }
2135
+ insert(args) {
2136
+ const executeWriteOperationWithRetry = createExecuteWriteOperationWithRetry({
2137
+ logger: this.logger,
2138
+ maxRetries: this.maxRetries,
2139
+ initialBackoffMs: this.initialBackoffMs
2140
+ });
2141
+ return executeWriteOperationWithRetry(() => this.doInsert(args), `insert into table ${args.tableName}`);
2142
+ }
2143
+ async load({ tableName, keys }) {
2144
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
2145
+ const parsedKeys = Object.keys(keys).map((key) => parseSqlIdentifier(key, "column name"));
2146
+ const conditions = parsedKeys.map((key) => `${key} = ?`).join(" AND ");
2147
+ const values = Object.values(keys);
2148
+ const result = await this.client.execute({
2149
+ sql: `SELECT * FROM ${parsedTableName} WHERE ${conditions} ORDER BY createdAt DESC LIMIT 1`,
2150
+ args: values
2151
+ });
2152
+ if (!result.rows || result.rows.length === 0) {
2153
+ return null;
2154
+ }
2155
+ const row = result.rows[0];
2156
+ const parsed = Object.fromEntries(
2157
+ Object.entries(row || {}).map(([k, v]) => {
2158
+ try {
2159
+ return [k, typeof v === "string" ? v.startsWith("{") || v.startsWith("[") ? JSON.parse(v) : v : v];
2160
+ } catch {
2161
+ return [k, v];
2162
+ }
2163
+ })
2164
+ );
2165
+ return parsed;
2166
+ }
2167
+ async loadMany({
2168
+ tableName,
2169
+ whereClause,
2170
+ orderBy,
2171
+ offset,
2172
+ limit,
2173
+ args
2174
+ }) {
2175
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
2176
+ let statement = `SELECT * FROM ${parsedTableName}`;
2177
+ if (whereClause?.sql) {
2178
+ statement += `${whereClause.sql}`;
2179
+ }
2180
+ if (orderBy) {
2181
+ statement += ` ORDER BY ${orderBy}`;
2182
+ }
2183
+ if (limit) {
2184
+ statement += ` LIMIT ${limit}`;
2185
+ }
2186
+ if (offset) {
2187
+ statement += ` OFFSET ${offset}`;
2188
+ }
2189
+ const result = await this.client.execute({
2190
+ sql: statement,
2191
+ args: [...whereClause?.args ?? [], ...args ?? []]
2192
+ });
2193
+ return result.rows;
2194
+ }
2195
+ async loadTotalCount({
2196
+ tableName,
2197
+ whereClause
2198
+ }) {
2199
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
2200
+ const statement = `SELECT COUNT(*) as count FROM ${parsedTableName} ${whereClause ? `${whereClause.sql}` : ""}`;
2201
+ const result = await this.client.execute({
2202
+ sql: statement,
2203
+ args: whereClause?.args ?? []
2204
+ });
2205
+ if (!result.rows || result.rows.length === 0) {
2206
+ return 0;
2207
+ }
2208
+ return result.rows[0]?.count ?? 0;
2209
+ }
2210
+ update(args) {
2211
+ const executeWriteOperationWithRetry = createExecuteWriteOperationWithRetry({
2212
+ logger: this.logger,
2213
+ maxRetries: this.maxRetries,
2214
+ initialBackoffMs: this.initialBackoffMs
2215
+ });
2216
+ return executeWriteOperationWithRetry(() => this.executeUpdate(args), `update table ${args.tableName}`);
2217
+ }
2218
+ async executeUpdate({
2219
+ tableName,
2220
+ keys,
2221
+ data
2222
+ }) {
2223
+ await this.client.execute(prepareUpdateStatement({ tableName, updates: data, keys }));
2224
+ }
2225
+ async doBatchInsert({
2226
+ tableName,
2227
+ records
2228
+ }) {
2229
+ if (records.length === 0) return;
2230
+ const batchStatements = records.map((r) => prepareStatement({ tableName, record: r }));
2231
+ await this.client.batch(batchStatements, "write");
2232
+ }
2233
+ batchInsert(args) {
2234
+ const executeWriteOperationWithRetry = createExecuteWriteOperationWithRetry({
2235
+ logger: this.logger,
2236
+ maxRetries: this.maxRetries,
2237
+ initialBackoffMs: this.initialBackoffMs
2238
+ });
2239
+ return executeWriteOperationWithRetry(
2240
+ () => this.doBatchInsert(args),
2241
+ `batch insert into table ${args.tableName}`
2242
+ ).catch((error) => {
2243
+ throw new MastraError(
2244
+ {
2245
+ id: "LIBSQL_STORE_BATCH_INSERT_FAILED",
2246
+ domain: ErrorDomain.STORAGE,
2247
+ category: ErrorCategory.THIRD_PARTY,
2248
+ details: {
2249
+ tableName: args.tableName
2250
+ }
2251
+ },
2252
+ error
2253
+ );
2254
+ });
2255
+ }
2256
+ /**
2257
+ * Public batch update method with retry logic
2258
+ */
2259
+ batchUpdate(args) {
2260
+ const executeWriteOperationWithRetry = createExecuteWriteOperationWithRetry({
2261
+ logger: this.logger,
2262
+ maxRetries: this.maxRetries,
2263
+ initialBackoffMs: this.initialBackoffMs
2264
+ });
2265
+ return executeWriteOperationWithRetry(
2266
+ () => this.executeBatchUpdate(args),
2267
+ `batch update in table ${args.tableName}`
2268
+ ).catch((error) => {
2269
+ throw new MastraError(
2270
+ {
2271
+ id: "LIBSQL_STORE_BATCH_UPDATE_FAILED",
2272
+ domain: ErrorDomain.STORAGE,
2273
+ category: ErrorCategory.THIRD_PARTY,
2274
+ details: {
2275
+ tableName: args.tableName
2276
+ }
2277
+ },
2278
+ error
2279
+ );
2280
+ });
2281
+ }
2282
+ /**
2283
+ * Updates multiple records in batch. Each record can be updated based on single or composite keys.
2284
+ */
2285
+ async executeBatchUpdate({
2286
+ tableName,
2287
+ updates
2288
+ }) {
2289
+ if (updates.length === 0) return;
2290
+ const batchStatements = updates.map(
2291
+ ({ keys, data }) => prepareUpdateStatement({
2292
+ tableName,
2293
+ updates: data,
2294
+ keys
2295
+ })
2296
+ );
2297
+ await this.client.batch(batchStatements, "write");
2298
+ }
2299
+ /**
2300
+ * Public batch delete method with retry logic
2301
+ */
2302
+ batchDelete({ tableName, keys }) {
2303
+ const executeWriteOperationWithRetry = createExecuteWriteOperationWithRetry({
2304
+ logger: this.logger,
2305
+ maxRetries: this.maxRetries,
2306
+ initialBackoffMs: this.initialBackoffMs
2307
+ });
2308
+ return executeWriteOperationWithRetry(
2309
+ () => this.executeBatchDelete({ tableName, keys }),
2310
+ `batch delete from table ${tableName}`
2311
+ ).catch((error) => {
2312
+ throw new MastraError(
2313
+ {
2314
+ id: "LIBSQL_STORE_BATCH_DELETE_FAILED",
2315
+ domain: ErrorDomain.STORAGE,
2316
+ category: ErrorCategory.THIRD_PARTY,
2317
+ details: {
2318
+ tableName
2319
+ }
2320
+ },
2321
+ error
2322
+ );
2323
+ });
2324
+ }
2325
+ /**
2326
+ * Deletes multiple records in batch. Each record can be deleted based on single or composite keys.
2327
+ */
2328
+ async executeBatchDelete({
2329
+ tableName,
2330
+ keys
2331
+ }) {
2332
+ if (keys.length === 0) return;
2333
+ const batchStatements = keys.map(
2334
+ (keyObj) => prepareDeleteStatement({
2335
+ tableName,
2336
+ keys: keyObj
2337
+ })
2338
+ );
2339
+ await this.client.batch(batchStatements, "write");
2340
+ }
2341
+ /**
2342
+ * Alters table schema to add columns if they don't exist
2343
+ * @param tableName Name of the table
2344
+ * @param schema Schema of the table
2345
+ * @param ifNotExists Array of column names to add if they don't exist
2346
+ */
2347
+ async alterTable({
2348
+ tableName,
2349
+ schema,
2350
+ ifNotExists
2351
+ }) {
2352
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
2353
+ try {
2354
+ const pragmaQuery = `PRAGMA table_info(${parsedTableName})`;
2355
+ const result = await this.client.execute(pragmaQuery);
2356
+ const existingColumnNames = new Set(result.rows.map((row) => row.name.toLowerCase()));
2357
+ for (const columnName of ifNotExists) {
2358
+ if (!existingColumnNames.has(columnName.toLowerCase()) && schema[columnName]) {
2359
+ const columnDef = schema[columnName];
2360
+ const sqlType = this.getSqlType(columnDef.type);
2361
+ const nullable = columnDef.nullable === false ? "NOT NULL" : "";
2362
+ const defaultValue = columnDef.nullable === false ? this.getDefaultValue(columnDef.type) : "";
2363
+ const alterSql = `ALTER TABLE ${parsedTableName} ADD COLUMN "${columnName}" ${sqlType} ${nullable} ${defaultValue}`.trim();
2364
+ await this.client.execute(alterSql);
2365
+ this.logger?.debug?.(`Added column ${columnName} to table ${parsedTableName}`);
2366
+ }
2367
+ }
2368
+ } catch (error) {
2369
+ throw new MastraError(
2370
+ {
2371
+ id: "LIBSQL_STORE_ALTER_TABLE_FAILED",
2372
+ domain: ErrorDomain.STORAGE,
2373
+ category: ErrorCategory.THIRD_PARTY,
2374
+ details: {
2375
+ tableName
2376
+ }
2377
+ },
2378
+ error
2379
+ );
2380
+ }
2381
+ }
2382
+ async clearTable({ tableName }) {
2383
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
2384
+ try {
2385
+ await this.client.execute(`DELETE FROM ${parsedTableName}`);
2386
+ } catch (e) {
2387
+ const mastraError = new MastraError(
2388
+ {
2389
+ id: "LIBSQL_STORE_CLEAR_TABLE_FAILED",
2390
+ domain: ErrorDomain.STORAGE,
2391
+ category: ErrorCategory.THIRD_PARTY,
2392
+ details: {
2393
+ tableName
2394
+ }
2395
+ },
2396
+ e
2397
+ );
2398
+ this.logger?.trackException?.(mastraError);
2399
+ this.logger?.error?.(mastraError.toString());
2400
+ }
2401
+ }
2402
+ async dropTable({ tableName }) {
2403
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
2404
+ try {
2405
+ await this.client.execute(`DROP TABLE IF EXISTS ${parsedTableName}`);
2406
+ } catch (e) {
2407
+ throw new MastraError(
2408
+ {
2409
+ id: "LIBSQL_STORE_DROP_TABLE_FAILED",
2410
+ domain: ErrorDomain.STORAGE,
2411
+ category: ErrorCategory.THIRD_PARTY,
2412
+ details: {
2413
+ tableName
2414
+ }
2415
+ },
2416
+ e
2417
+ );
2418
+ }
2419
+ }
2420
+ };
2421
+ var ScoresLibSQL = class extends ScoresStorage {
2422
+ operations;
2423
+ client;
2424
+ constructor({ client, operations }) {
2425
+ super();
2426
+ this.operations = operations;
2427
+ this.client = client;
2428
+ }
2429
+ async listScoresByRunId({
2430
+ runId,
2431
+ pagination
2432
+ }) {
2433
+ try {
2434
+ const { page, perPage: perPageInput } = pagination;
2435
+ const countResult = await this.client.execute({
2436
+ sql: `SELECT COUNT(*) as count FROM ${TABLE_SCORERS} WHERE runId = ?`,
2437
+ args: [runId]
2438
+ });
2439
+ const total = Number(countResult.rows?.[0]?.count ?? 0);
2440
+ if (total === 0) {
2441
+ return {
2442
+ pagination: {
2443
+ total: 0,
2444
+ page,
2445
+ perPage: perPageInput,
2446
+ hasMore: false
2447
+ },
2448
+ scores: []
2449
+ };
2450
+ }
2451
+ const perPage = normalizePerPage(perPageInput, 100);
2452
+ const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
2453
+ const limitValue = perPageInput === false ? total : perPage;
2454
+ const end = perPageInput === false ? total : start + perPage;
2455
+ const result = await this.client.execute({
2456
+ sql: `SELECT * FROM ${TABLE_SCORERS} WHERE runId = ? ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
2457
+ args: [runId, limitValue, start]
2458
+ });
2459
+ const scores = result.rows?.map((row) => this.transformScoreRow(row)) ?? [];
2460
+ return {
2461
+ scores,
2462
+ pagination: {
2463
+ total,
2464
+ page,
2465
+ perPage: perPageForResponse,
2466
+ hasMore: end < total
2467
+ }
2468
+ };
2469
+ } catch (error) {
2470
+ throw new MastraError(
2471
+ {
2472
+ id: "LIBSQL_STORE_GET_SCORES_BY_RUN_ID_FAILED",
2473
+ domain: ErrorDomain.STORAGE,
2474
+ category: ErrorCategory.THIRD_PARTY
2475
+ },
2476
+ error
2477
+ );
2478
+ }
2479
+ }
2480
+ async listScoresByScorerId({
2481
+ scorerId,
2482
+ entityId,
2483
+ entityType,
2484
+ source,
2485
+ pagination
2486
+ }) {
2487
+ try {
2488
+ const { page, perPage: perPageInput } = pagination;
2489
+ const conditions = [];
2490
+ const queryParams = [];
2491
+ if (scorerId) {
2492
+ conditions.push(`scorerId = ?`);
2493
+ queryParams.push(scorerId);
2494
+ }
2495
+ if (entityId) {
2496
+ conditions.push(`entityId = ?`);
2497
+ queryParams.push(entityId);
2498
+ }
2499
+ if (entityType) {
2500
+ conditions.push(`entityType = ?`);
2501
+ queryParams.push(entityType);
2502
+ }
2503
+ if (source) {
2504
+ conditions.push(`source = ?`);
2505
+ queryParams.push(source);
2506
+ }
2507
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2508
+ const countResult = await this.client.execute({
2509
+ sql: `SELECT COUNT(*) as count FROM ${TABLE_SCORERS} ${whereClause}`,
2510
+ args: queryParams
2511
+ });
2512
+ const total = Number(countResult.rows?.[0]?.count ?? 0);
2513
+ if (total === 0) {
2514
+ return {
2515
+ pagination: {
2516
+ total: 0,
2517
+ page,
2518
+ perPage: perPageInput,
2519
+ hasMore: false
2520
+ },
2521
+ scores: []
2522
+ };
2523
+ }
2524
+ const perPage = normalizePerPage(perPageInput, 100);
2525
+ const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
2526
+ const limitValue = perPageInput === false ? total : perPage;
2527
+ const end = perPageInput === false ? total : start + perPage;
2528
+ const result = await this.client.execute({
2529
+ sql: `SELECT * FROM ${TABLE_SCORERS} ${whereClause} ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
2530
+ args: [...queryParams, limitValue, start]
2531
+ });
2532
+ const scores = result.rows?.map((row) => this.transformScoreRow(row)) ?? [];
2533
+ return {
2534
+ scores,
2535
+ pagination: {
2536
+ total,
2537
+ page,
2538
+ perPage: perPageForResponse,
2539
+ hasMore: end < total
2540
+ }
2541
+ };
2542
+ } catch (error) {
2543
+ throw new MastraError(
2544
+ {
2545
+ id: "LIBSQL_STORE_GET_SCORES_BY_SCORER_ID_FAILED",
2546
+ domain: ErrorDomain.STORAGE,
2547
+ category: ErrorCategory.THIRD_PARTY
2548
+ },
2549
+ error
2550
+ );
2551
+ }
2552
+ }
2553
+ transformScoreRow(row) {
2554
+ const scorerValue = safelyParseJSON(row.scorer);
2555
+ const inputValue = safelyParseJSON(row.input ?? "{}");
2556
+ const outputValue = safelyParseJSON(row.output ?? "{}");
2557
+ const additionalLLMContextValue = row.additionalLLMContext ? safelyParseJSON(row.additionalLLMContext) : null;
2558
+ const requestContextValue = row.requestContext ? safelyParseJSON(row.requestContext) : null;
2559
+ const metadataValue = row.metadata ? safelyParseJSON(row.metadata) : null;
2560
+ const entityValue = row.entity ? safelyParseJSON(row.entity) : null;
2561
+ const preprocessStepResultValue = row.preprocessStepResult ? safelyParseJSON(row.preprocessStepResult) : null;
2562
+ const analyzeStepResultValue = row.analyzeStepResult ? safelyParseJSON(row.analyzeStepResult) : null;
2563
+ return {
2564
+ id: row.id,
2565
+ traceId: row.traceId,
2566
+ spanId: row.spanId,
2567
+ runId: row.runId,
2568
+ scorer: scorerValue,
2569
+ score: row.score,
2570
+ reason: row.reason,
2571
+ preprocessStepResult: preprocessStepResultValue,
2572
+ analyzeStepResult: analyzeStepResultValue,
2573
+ analyzePrompt: row.analyzePrompt,
2574
+ preprocessPrompt: row.preprocessPrompt,
2575
+ generateScorePrompt: row.generateScorePrompt,
2576
+ generateReasonPrompt: row.generateReasonPrompt,
2577
+ metadata: metadataValue,
2578
+ input: inputValue,
2579
+ output: outputValue,
2580
+ additionalContext: additionalLLMContextValue,
2581
+ requestContext: requestContextValue,
2582
+ entityType: row.entityType,
2583
+ entity: entityValue,
2584
+ entityId: row.entityId,
2585
+ scorerId: row.scorerId,
2586
+ source: row.source,
2587
+ resourceId: row.resourceId,
2588
+ threadId: row.threadId,
2589
+ createdAt: row.createdAt,
2590
+ updatedAt: row.updatedAt
2591
+ };
2592
+ }
2593
+ async getScoreById({ id }) {
2594
+ const result = await this.client.execute({
2595
+ sql: `SELECT * FROM ${TABLE_SCORERS} WHERE id = ?`,
2596
+ args: [id]
2597
+ });
2598
+ return result.rows?.[0] ? this.transformScoreRow(result.rows[0]) : null;
2599
+ }
2600
+ async saveScore(score) {
2601
+ let parsedScore;
2602
+ try {
2603
+ parsedScore = saveScorePayloadSchema.parse(score);
2604
+ } catch (error) {
2605
+ throw new MastraError(
2606
+ {
2607
+ id: "LIBSQL_STORE_SAVE_SCORE_FAILED_INVALID_SCORE_PAYLOAD",
2608
+ domain: ErrorDomain.STORAGE,
2609
+ category: ErrorCategory.USER,
2610
+ details: {
2611
+ scorer: score.scorer.id,
2612
+ entityId: score.entityId,
2613
+ entityType: score.entityType,
2614
+ traceId: score.traceId || "",
2615
+ spanId: score.spanId || ""
2616
+ }
2617
+ },
2618
+ error
2619
+ );
2620
+ }
2621
+ try {
2622
+ const id = crypto.randomUUID();
2623
+ await this.operations.insert({
2624
+ tableName: TABLE_SCORERS,
2625
+ record: {
2626
+ id,
2627
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2628
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2629
+ ...parsedScore
2630
+ }
2631
+ });
2632
+ const scoreFromDb = await this.getScoreById({ id });
2633
+ return { score: scoreFromDb };
2634
+ } catch (error) {
2635
+ throw new MastraError(
2636
+ {
2637
+ id: "LIBSQL_STORE_SAVE_SCORE_FAILED",
2638
+ domain: ErrorDomain.STORAGE,
2639
+ category: ErrorCategory.THIRD_PARTY
2640
+ },
2641
+ error
2642
+ );
2643
+ }
2644
+ }
2645
+ async listScoresByEntityId({
2646
+ entityId,
2647
+ entityType,
2648
+ pagination
2649
+ }) {
2650
+ try {
2651
+ const { page, perPage: perPageInput } = pagination;
2652
+ const countResult = await this.client.execute({
2653
+ sql: `SELECT COUNT(*) as count FROM ${TABLE_SCORERS} WHERE entityId = ? AND entityType = ?`,
2654
+ args: [entityId, entityType]
2655
+ });
2656
+ const total = Number(countResult.rows?.[0]?.count ?? 0);
2657
+ if (total === 0) {
2658
+ return {
2659
+ pagination: {
2660
+ total: 0,
2661
+ page,
2662
+ perPage: perPageInput,
2663
+ hasMore: false
2664
+ },
2665
+ scores: []
2666
+ };
2667
+ }
2668
+ const perPage = normalizePerPage(perPageInput, 100);
2669
+ const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
2670
+ const limitValue = perPageInput === false ? total : perPage;
2671
+ const end = perPageInput === false ? total : start + perPage;
2672
+ const result = await this.client.execute({
2673
+ sql: `SELECT * FROM ${TABLE_SCORERS} WHERE entityId = ? AND entityType = ? ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
2674
+ args: [entityId, entityType, limitValue, start]
2675
+ });
2676
+ const scores = result.rows?.map((row) => this.transformScoreRow(row)) ?? [];
2677
+ return {
2678
+ scores,
2679
+ pagination: {
2680
+ total,
2681
+ page,
2682
+ perPage: perPageForResponse,
2683
+ hasMore: end < total
2684
+ }
2685
+ };
2686
+ } catch (error) {
2687
+ throw new MastraError(
2688
+ {
2689
+ id: "LIBSQL_STORE_GET_SCORES_BY_ENTITY_ID_FAILED",
2690
+ domain: ErrorDomain.STORAGE,
2691
+ category: ErrorCategory.THIRD_PARTY
2692
+ },
2693
+ error
2694
+ );
2695
+ }
2696
+ }
2697
+ async listScoresBySpan({
2698
+ traceId,
2699
+ spanId,
2700
+ pagination
2701
+ }) {
2702
+ try {
2703
+ const { page, perPage: perPageInput } = pagination;
2704
+ const perPage = normalizePerPage(perPageInput, 100);
2705
+ const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
2706
+ const countSQLResult = await this.client.execute({
2707
+ sql: `SELECT COUNT(*) as count FROM ${TABLE_SCORERS} WHERE traceId = ? AND spanId = ?`,
2708
+ args: [traceId, spanId]
2709
+ });
2710
+ const total = Number(countSQLResult.rows?.[0]?.count ?? 0);
2711
+ const limitValue = perPageInput === false ? total : perPage;
2712
+ const end = perPageInput === false ? total : start + perPage;
2713
+ const result = await this.client.execute({
2714
+ sql: `SELECT * FROM ${TABLE_SCORERS} WHERE traceId = ? AND spanId = ? ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
2715
+ args: [traceId, spanId, limitValue, start]
2716
+ });
2717
+ const scores = result.rows?.map((row) => this.transformScoreRow(row)) ?? [];
2718
+ return {
2719
+ scores,
2720
+ pagination: {
2721
+ total,
2722
+ page,
2723
+ perPage: perPageForResponse,
2724
+ hasMore: end < total
2725
+ }
2726
+ };
2727
+ } catch (error) {
2728
+ throw new MastraError(
2729
+ {
2730
+ id: "LIBSQL_STORE_GET_SCORES_BY_SPAN_FAILED",
2731
+ domain: ErrorDomain.STORAGE,
2732
+ category: ErrorCategory.THIRD_PARTY
2733
+ },
2734
+ error
2735
+ );
2736
+ }
2737
+ }
2738
+ };
2739
+ function parseWorkflowRun(row) {
2740
+ let parsedSnapshot = row.snapshot;
2741
+ if (typeof parsedSnapshot === "string") {
2742
+ try {
2743
+ parsedSnapshot = JSON.parse(row.snapshot);
2744
+ } catch (e) {
2745
+ console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
2746
+ }
2747
+ }
2748
+ return {
2749
+ workflowName: row.workflow_name,
2750
+ runId: row.run_id,
2751
+ snapshot: parsedSnapshot,
2752
+ resourceId: row.resourceId,
2753
+ createdAt: new Date(row.createdAt),
2754
+ updatedAt: new Date(row.updatedAt)
2755
+ };
2756
+ }
2757
+ var WorkflowsLibSQL = class extends WorkflowsStorage {
2758
+ operations;
2759
+ client;
2760
+ maxRetries;
2761
+ initialBackoffMs;
2762
+ constructor({
2763
+ operations,
2764
+ client,
2765
+ maxRetries = 5,
2766
+ initialBackoffMs = 500
2767
+ }) {
2768
+ super();
2769
+ this.operations = operations;
2770
+ this.client = client;
2771
+ this.maxRetries = maxRetries;
2772
+ this.initialBackoffMs = initialBackoffMs;
2773
+ this.setupPragmaSettings().catch(
2774
+ (err) => this.logger.warn("LibSQL Workflows: Failed to setup PRAGMA settings.", err)
2775
+ );
2776
+ }
2777
+ async setupPragmaSettings() {
2778
+ try {
2779
+ await this.client.execute("PRAGMA busy_timeout = 10000;");
2780
+ this.logger.debug("LibSQL Workflows: PRAGMA busy_timeout=10000 set.");
2781
+ try {
2782
+ await this.client.execute("PRAGMA journal_mode = WAL;");
2783
+ this.logger.debug("LibSQL Workflows: PRAGMA journal_mode=WAL set.");
2784
+ } catch {
2785
+ this.logger.debug("LibSQL Workflows: WAL mode not supported, using default journal mode.");
2786
+ }
2787
+ try {
2788
+ await this.client.execute("PRAGMA synchronous = NORMAL;");
2789
+ this.logger.debug("LibSQL Workflows: PRAGMA synchronous=NORMAL set.");
2790
+ } catch {
2791
+ this.logger.debug("LibSQL Workflows: Failed to set synchronous mode.");
2792
+ }
2793
+ } catch (err) {
2794
+ this.logger.warn("LibSQL Workflows: Failed to set PRAGMA settings.", err);
2795
+ }
2796
+ }
2797
+ async executeWithRetry(operation) {
2798
+ let attempts = 0;
2799
+ let backoff = this.initialBackoffMs;
2800
+ while (attempts < this.maxRetries) {
2801
+ try {
2802
+ return await operation();
2803
+ } catch (error) {
2804
+ this.logger.debug("LibSQL Workflows: Error caught in retry loop", {
2805
+ errorType: error.constructor.name,
2806
+ errorCode: error.code,
2807
+ errorMessage: error.message,
2808
+ attempts,
2809
+ maxRetries: this.maxRetries
2810
+ });
2811
+ const isLockError = error.code === "SQLITE_BUSY" || error.code === "SQLITE_LOCKED" || error.message?.toLowerCase().includes("database is locked") || error.message?.toLowerCase().includes("database table is locked") || error.message?.toLowerCase().includes("table is locked") || error.constructor.name === "SqliteError" && error.message?.toLowerCase().includes("locked");
2812
+ if (isLockError) {
2813
+ attempts++;
2814
+ if (attempts >= this.maxRetries) {
2815
+ this.logger.error(
2816
+ `LibSQL Workflows: Operation failed after ${this.maxRetries} attempts due to database lock: ${error.message}`,
2817
+ { error, attempts, maxRetries: this.maxRetries }
2818
+ );
2819
+ throw error;
2820
+ }
2821
+ this.logger.warn(
2822
+ `LibSQL Workflows: Attempt ${attempts} failed due to database lock. Retrying in ${backoff}ms...`,
2823
+ { errorMessage: error.message, attempts, backoff, maxRetries: this.maxRetries }
2824
+ );
2825
+ await new Promise((resolve) => setTimeout(resolve, backoff));
2826
+ backoff *= 2;
2827
+ } else {
2828
+ this.logger.error("LibSQL Workflows: Non-lock error occurred, not retrying", { error });
2829
+ throw error;
2830
+ }
2831
+ }
2832
+ }
2833
+ throw new Error("LibSQL Workflows: Max retries reached, but no error was re-thrown from the loop.");
2834
+ }
2835
+ async updateWorkflowResults({
2836
+ workflowName,
2837
+ runId,
2838
+ stepId,
2839
+ result,
2840
+ requestContext
2841
+ }) {
2842
+ return this.executeWithRetry(async () => {
2843
+ const tx = await this.client.transaction("write");
2844
+ try {
2845
+ const existingSnapshotResult = await tx.execute({
2846
+ sql: `SELECT snapshot FROM ${TABLE_WORKFLOW_SNAPSHOT} WHERE workflow_name = ? AND run_id = ?`,
2847
+ args: [workflowName, runId]
2848
+ });
2849
+ let snapshot;
2850
+ if (!existingSnapshotResult.rows?.[0]) {
2851
+ snapshot = {
2852
+ context: {},
2853
+ activePaths: [],
2854
+ timestamp: Date.now(),
2855
+ suspendedPaths: {},
2856
+ resumeLabels: {},
2857
+ serializedStepGraph: [],
2858
+ value: {},
2859
+ waitingPaths: {},
2860
+ status: "pending",
2861
+ runId,
2862
+ requestContext: {}
2863
+ };
2864
+ } else {
2865
+ const existingSnapshot = existingSnapshotResult.rows[0].snapshot;
2866
+ snapshot = typeof existingSnapshot === "string" ? JSON.parse(existingSnapshot) : existingSnapshot;
2867
+ }
2868
+ snapshot.context[stepId] = result;
2869
+ snapshot.requestContext = { ...snapshot.requestContext, ...requestContext };
2870
+ await tx.execute({
2871
+ sql: `UPDATE ${TABLE_WORKFLOW_SNAPSHOT} SET snapshot = ? WHERE workflow_name = ? AND run_id = ?`,
2872
+ args: [JSON.stringify(snapshot), workflowName, runId]
2873
+ });
2874
+ await tx.commit();
2875
+ return snapshot.context;
2876
+ } catch (error) {
2877
+ if (!tx.closed) {
2878
+ await tx.rollback();
2879
+ }
2880
+ throw error;
2881
+ }
2882
+ });
2883
+ }
2884
+ async updateWorkflowState({
2885
+ workflowName,
2886
+ runId,
2887
+ opts
2888
+ }) {
2889
+ return this.executeWithRetry(async () => {
2890
+ const tx = await this.client.transaction("write");
2891
+ try {
2892
+ const existingSnapshotResult = await tx.execute({
2893
+ sql: `SELECT snapshot FROM ${TABLE_WORKFLOW_SNAPSHOT} WHERE workflow_name = ? AND run_id = ?`,
2894
+ args: [workflowName, runId]
2895
+ });
2896
+ if (!existingSnapshotResult.rows?.[0]) {
2897
+ await tx.rollback();
2898
+ return void 0;
2899
+ }
2900
+ const existingSnapshot = existingSnapshotResult.rows[0].snapshot;
2901
+ const snapshot = typeof existingSnapshot === "string" ? JSON.parse(existingSnapshot) : existingSnapshot;
2902
+ if (!snapshot || !snapshot?.context) {
2903
+ await tx.rollback();
2904
+ throw new Error(`Snapshot not found for runId ${runId}`);
2905
+ }
2906
+ const updatedSnapshot = { ...snapshot, ...opts };
2907
+ await tx.execute({
2908
+ sql: `UPDATE ${TABLE_WORKFLOW_SNAPSHOT} SET snapshot = ? WHERE workflow_name = ? AND run_id = ?`,
2909
+ args: [JSON.stringify(updatedSnapshot), workflowName, runId]
2910
+ });
2911
+ await tx.commit();
2912
+ return updatedSnapshot;
2913
+ } catch (error) {
2914
+ if (!tx.closed) {
2915
+ await tx.rollback();
2916
+ }
2917
+ throw error;
2918
+ }
2919
+ });
2920
+ }
2921
+ async persistWorkflowSnapshot({
2922
+ workflowName,
2923
+ runId,
2924
+ resourceId,
2925
+ snapshot
2926
+ }) {
2927
+ const data = {
2928
+ workflow_name: workflowName,
2929
+ run_id: runId,
2930
+ resourceId,
2931
+ snapshot,
2932
+ createdAt: /* @__PURE__ */ new Date(),
2933
+ updatedAt: /* @__PURE__ */ new Date()
2934
+ };
2935
+ this.logger.debug("Persisting workflow snapshot", { workflowName, runId, data });
2936
+ await this.operations.insert({
2937
+ tableName: TABLE_WORKFLOW_SNAPSHOT,
2938
+ record: data
2939
+ });
2940
+ }
2941
+ async loadWorkflowSnapshot({
2942
+ workflowName,
2943
+ runId
2944
+ }) {
2945
+ this.logger.debug("Loading workflow snapshot", { workflowName, runId });
2946
+ const d = await this.operations.load({
2947
+ tableName: TABLE_WORKFLOW_SNAPSHOT,
2948
+ keys: { workflow_name: workflowName, run_id: runId }
2949
+ });
2950
+ return d ? d.snapshot : null;
2951
+ }
2952
+ async getWorkflowRunById({
2953
+ runId,
2954
+ workflowName
2955
+ }) {
2956
+ const conditions = [];
2957
+ const args = [];
2958
+ if (runId) {
2959
+ conditions.push("run_id = ?");
2960
+ args.push(runId);
2961
+ }
2962
+ if (workflowName) {
2963
+ conditions.push("workflow_name = ?");
2964
+ args.push(workflowName);
2965
+ }
2966
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2967
+ try {
2968
+ const result = await this.client.execute({
2969
+ sql: `SELECT * FROM ${TABLE_WORKFLOW_SNAPSHOT} ${whereClause} ORDER BY createdAt DESC LIMIT 1`,
2970
+ args
2971
+ });
2972
+ if (!result.rows?.[0]) {
2973
+ return null;
2974
+ }
2975
+ return parseWorkflowRun(result.rows[0]);
2976
+ } catch (error) {
2977
+ throw new MastraError(
2978
+ {
2979
+ id: "LIBSQL_STORE_GET_WORKFLOW_RUN_BY_ID_FAILED",
2980
+ domain: ErrorDomain.STORAGE,
2981
+ category: ErrorCategory.THIRD_PARTY
2982
+ },
2983
+ error
2984
+ );
2985
+ }
2986
+ }
2987
+ async listWorkflowRuns({
2988
+ workflowName,
2989
+ fromDate,
2990
+ toDate,
2991
+ page,
2992
+ perPage,
2993
+ resourceId
2994
+ } = {}) {
2995
+ try {
2996
+ const conditions = [];
2997
+ const args = [];
2998
+ if (workflowName) {
2999
+ conditions.push("workflow_name = ?");
3000
+ args.push(workflowName);
3001
+ }
3002
+ if (fromDate) {
3003
+ conditions.push("createdAt >= ?");
3004
+ args.push(fromDate.toISOString());
3005
+ }
3006
+ if (toDate) {
3007
+ conditions.push("createdAt <= ?");
3008
+ args.push(toDate.toISOString());
3009
+ }
3010
+ if (resourceId) {
3011
+ const hasResourceId = await this.operations.hasColumn(TABLE_WORKFLOW_SNAPSHOT, "resourceId");
3012
+ if (hasResourceId) {
3013
+ conditions.push("resourceId = ?");
3014
+ args.push(resourceId);
3015
+ } else {
3016
+ console.warn(`[${TABLE_WORKFLOW_SNAPSHOT}] resourceId column not found. Skipping resourceId filter.`);
3017
+ }
3018
+ }
3019
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
3020
+ let total = 0;
3021
+ const usePagination = typeof perPage === "number" && typeof page === "number";
3022
+ if (usePagination) {
3023
+ const countResult = await this.client.execute({
3024
+ sql: `SELECT COUNT(*) as count FROM ${TABLE_WORKFLOW_SNAPSHOT} ${whereClause}`,
3025
+ args
3026
+ });
3027
+ total = Number(countResult.rows?.[0]?.count ?? 0);
3028
+ }
3029
+ const normalizedPerPage = usePagination ? normalizePerPage(perPage, Number.MAX_SAFE_INTEGER) : 0;
3030
+ const offset = usePagination ? page * normalizedPerPage : 0;
3031
+ const result = await this.client.execute({
3032
+ sql: `SELECT * FROM ${TABLE_WORKFLOW_SNAPSHOT} ${whereClause} ORDER BY createdAt DESC${usePagination ? ` LIMIT ? OFFSET ?` : ""}`,
3033
+ args: usePagination ? [...args, normalizedPerPage, offset] : args
3034
+ });
3035
+ const runs = (result.rows || []).map((row) => parseWorkflowRun(row));
3036
+ return { runs, total: total || runs.length };
3037
+ } catch (error) {
3038
+ throw new MastraError(
3039
+ {
3040
+ id: "LIBSQL_STORE_LIST_WORKFLOW_RUNS_FAILED",
3041
+ domain: ErrorDomain.STORAGE,
3042
+ category: ErrorCategory.THIRD_PARTY
3043
+ },
3044
+ error
3045
+ );
3046
+ }
3047
+ }
3048
+ };
3049
+
3050
+ // src/storage/index.ts
3051
+ var LibSQLStore = class extends MastraStorage {
3052
+ client;
3053
+ maxRetries;
3054
+ initialBackoffMs;
3055
+ stores;
3056
+ constructor(config) {
3057
+ if (!config.id || typeof config.id !== "string" || config.id.trim() === "") {
3058
+ throw new Error("LibSQLStore: id must be provided and cannot be empty.");
3059
+ }
3060
+ super({ id: config.id, name: `LibSQLStore` });
3061
+ this.maxRetries = config.maxRetries ?? 5;
3062
+ this.initialBackoffMs = config.initialBackoffMs ?? 100;
3063
+ if ("url" in config) {
3064
+ if (config.url.endsWith(":memory:")) {
3065
+ this.shouldCacheInit = false;
3066
+ }
3067
+ this.client = createClient({
3068
+ url: config.url,
3069
+ ...config.authToken ? { authToken: config.authToken } : {}
3070
+ });
3071
+ if (config.url.startsWith("file:") || config.url.includes(":memory:")) {
3072
+ this.client.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));
3073
+ this.client.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.", err));
3074
+ }
3075
+ } else {
3076
+ this.client = config.client;
3077
+ }
3078
+ const operations = new StoreOperationsLibSQL({
3079
+ client: this.client,
3080
+ maxRetries: this.maxRetries,
3081
+ initialBackoffMs: this.initialBackoffMs
3082
+ });
3083
+ const scores = new ScoresLibSQL({ client: this.client, operations });
3084
+ const workflows = new WorkflowsLibSQL({ client: this.client, operations });
3085
+ const memory = new MemoryLibSQL({ client: this.client, operations });
3086
+ const observability = new ObservabilityLibSQL({ operations });
3087
+ this.stores = {
3088
+ operations,
3089
+ scores,
3090
+ workflows,
3091
+ memory,
3092
+ observability
3093
+ };
3094
+ }
3095
+ get supports() {
3096
+ return {
3097
+ selectByIncludeResourceScope: true,
3098
+ resourceWorkingMemory: true,
3099
+ hasColumn: true,
3100
+ createTable: true,
3101
+ deleteMessages: true,
3102
+ observabilityInstance: true,
3103
+ listScoresBySpan: true
3104
+ };
3105
+ }
3106
+ async createTable({
3107
+ tableName,
3108
+ schema
3109
+ }) {
3110
+ await this.stores.operations.createTable({ tableName, schema });
3111
+ }
3112
+ /**
3113
+ * Alters table schema to add columns if they don't exist
3114
+ * @param tableName Name of the table
3115
+ * @param schema Schema of the table
3116
+ * @param ifNotExists Array of column names to add if they don't exist
3117
+ */
3118
+ async alterTable({
3119
+ tableName,
3120
+ schema,
3121
+ ifNotExists
3122
+ }) {
3123
+ await this.stores.operations.alterTable({ tableName, schema, ifNotExists });
3124
+ }
3125
+ async clearTable({ tableName }) {
3126
+ await this.stores.operations.clearTable({ tableName });
3127
+ }
3128
+ async dropTable({ tableName }) {
3129
+ await this.stores.operations.dropTable({ tableName });
3130
+ }
3131
+ insert(args) {
3132
+ return this.stores.operations.insert(args);
3133
+ }
3134
+ batchInsert(args) {
3135
+ return this.stores.operations.batchInsert(args);
3136
+ }
3137
+ async load({ tableName, keys }) {
3138
+ return this.stores.operations.load({ tableName, keys });
3139
+ }
3140
+ async getThreadById({ threadId }) {
3141
+ return this.stores.memory.getThreadById({ threadId });
3142
+ }
3143
+ async saveThread({ thread }) {
3144
+ return this.stores.memory.saveThread({ thread });
3145
+ }
3146
+ async updateThread({
3147
+ id,
3148
+ title,
3149
+ metadata
3150
+ }) {
3151
+ return this.stores.memory.updateThread({ id, title, metadata });
3152
+ }
3153
+ async deleteThread({ threadId }) {
3154
+ return this.stores.memory.deleteThread({ threadId });
3155
+ }
3156
+ async listMessagesById({ messageIds }) {
3157
+ return this.stores.memory.listMessagesById({ messageIds });
3158
+ }
3159
+ async saveMessages(args) {
3160
+ const result = await this.stores.memory.saveMessages({ messages: args.messages });
3161
+ return { messages: result.messages };
3162
+ }
3163
+ async updateMessages({
3164
+ messages
3165
+ }) {
3166
+ return this.stores.memory.updateMessages({ messages });
3167
+ }
3168
+ async deleteMessages(messageIds) {
3169
+ return this.stores.memory.deleteMessages(messageIds);
3170
+ }
3171
+ async getScoreById({ id }) {
3172
+ return this.stores.scores.getScoreById({ id });
3173
+ }
3174
+ async saveScore(score) {
3175
+ return this.stores.scores.saveScore(score);
3176
+ }
3177
+ async listScoresByScorerId({
3178
+ scorerId,
3179
+ entityId,
3180
+ entityType,
3181
+ source,
3182
+ pagination
3183
+ }) {
3184
+ return this.stores.scores.listScoresByScorerId({ scorerId, entityId, entityType, source, pagination });
3185
+ }
3186
+ async listScoresByRunId({
3187
+ runId,
3188
+ pagination
3189
+ }) {
3190
+ return this.stores.scores.listScoresByRunId({ runId, pagination });
3191
+ }
3192
+ async listScoresByEntityId({
3193
+ entityId,
3194
+ entityType,
3195
+ pagination
3196
+ }) {
3197
+ return this.stores.scores.listScoresByEntityId({ entityId, entityType, pagination });
3198
+ }
3199
+ /**
3200
+ * WORKFLOWS
3201
+ */
3202
+ async updateWorkflowResults({
3203
+ workflowName,
3204
+ runId,
3205
+ stepId,
3206
+ result,
3207
+ requestContext
3208
+ }) {
3209
+ return this.stores.workflows.updateWorkflowResults({ workflowName, runId, stepId, result, requestContext });
3210
+ }
3211
+ async updateWorkflowState({
3212
+ workflowName,
3213
+ runId,
3214
+ opts
3215
+ }) {
3216
+ return this.stores.workflows.updateWorkflowState({ workflowName, runId, opts });
3217
+ }
3218
+ async persistWorkflowSnapshot({
3219
+ workflowName,
3220
+ runId,
3221
+ resourceId,
3222
+ snapshot
3223
+ }) {
3224
+ return this.stores.workflows.persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot });
3225
+ }
3226
+ async loadWorkflowSnapshot({
3227
+ workflowName,
3228
+ runId
3229
+ }) {
3230
+ return this.stores.workflows.loadWorkflowSnapshot({ workflowName, runId });
3231
+ }
3232
+ async listWorkflowRuns({
3233
+ workflowName,
3234
+ fromDate,
3235
+ toDate,
3236
+ perPage,
3237
+ page,
3238
+ resourceId
3239
+ } = {}) {
3240
+ return this.stores.workflows.listWorkflowRuns({ workflowName, fromDate, toDate, perPage, page, resourceId });
3241
+ }
3242
+ async getWorkflowRunById({
3243
+ runId,
3244
+ workflowName
3245
+ }) {
3246
+ return this.stores.workflows.getWorkflowRunById({ runId, workflowName });
3247
+ }
3248
+ async getResourceById({ resourceId }) {
3249
+ return this.stores.memory.getResourceById({ resourceId });
3250
+ }
3251
+ async saveResource({ resource }) {
3252
+ return this.stores.memory.saveResource({ resource });
3253
+ }
3254
+ async updateResource({
3255
+ resourceId,
3256
+ workingMemory,
3257
+ metadata
3258
+ }) {
3259
+ return this.stores.memory.updateResource({ resourceId, workingMemory, metadata });
3260
+ }
3261
+ async createSpan(span) {
3262
+ return this.stores.observability.createSpan(span);
3263
+ }
3264
+ async updateSpan(params) {
3265
+ return this.stores.observability.updateSpan(params);
3266
+ }
3267
+ async getTrace(traceId) {
3268
+ return this.stores.observability.getTrace(traceId);
3269
+ }
3270
+ async getTracesPaginated(args) {
3271
+ return this.stores.observability.getTracesPaginated(args);
3272
+ }
3273
+ async listScoresBySpan({
3274
+ traceId,
3275
+ spanId,
3276
+ pagination
3277
+ }) {
3278
+ return this.stores.scores.listScoresBySpan({ traceId, spanId, pagination });
3279
+ }
3280
+ async batchCreateSpans(args) {
3281
+ return this.stores.observability.batchCreateSpans(args);
3282
+ }
3283
+ async batchUpdateSpans(args) {
3284
+ return this.stores.observability.batchUpdateSpans(args);
3285
+ }
3286
+ };
3287
+
3288
+ // src/vector/prompt.ts
3289
+ var LIBSQL_PROMPT = `When querying LibSQL Vector, you can ONLY use the operators listed below. Any other operators will be rejected.
3290
+ Important: Don't explain how to construct the filter - use the specified operators and fields to search the content and return relevant results.
3291
+ 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.
3292
+
3293
+ Basic Comparison Operators:
3294
+ - $eq: Exact match (default when using field: value)
3295
+ Example: { "category": "electronics" }
3296
+ - $ne: Not equal
3297
+ Example: { "category": { "$ne": "electronics" } }
3298
+ - $gt: Greater than
3299
+ Example: { "price": { "$gt": 100 } }
3300
+ - $gte: Greater than or equal
3301
+ Example: { "price": { "$gte": 100 } }
3302
+ - $lt: Less than
3303
+ Example: { "price": { "$lt": 100 } }
3304
+ - $lte: Less than or equal
3305
+ Example: { "price": { "$lte": 100 } }
3306
+
3307
+ Array Operators:
3308
+ - $in: Match any value in array
3309
+ Example: { "category": { "$in": ["electronics", "books"] } }
3310
+ - $nin: Does not match any value in array
3311
+ Example: { "category": { "$nin": ["electronics", "books"] } }
3312
+ - $all: Match all values in array
3313
+ Example: { "tags": { "$all": ["premium", "sale"] } }
3314
+ - $elemMatch: Match array elements that meet all specified conditions
3315
+ Example: { "items": { "$elemMatch": { "price": { "$gt": 100 } } } }
3316
+ - $contains: Check if array contains value
3317
+ Example: { "tags": { "$contains": "premium" } }
3318
+
3319
+ Logical Operators:
3320
+ - $and: Logical AND (implicit when using multiple conditions)
3321
+ Example: { "$and": [{ "price": { "$gt": 100 } }, { "category": "electronics" }] }
3322
+ - $or: Logical OR
3323
+ Example: { "$or": [{ "price": { "$lt": 50 } }, { "category": "books" }] }
3324
+ - $not: Logical NOT
3325
+ Example: { "$not": { "category": "electronics" } }
3326
+ - $nor: Logical NOR
3327
+ Example: { "$nor": [{ "price": { "$lt": 50 } }, { "category": "books" }] }
3328
+
3329
+ Element Operators:
3330
+ - $exists: Check if field exists
3331
+ Example: { "rating": { "$exists": true } }
3332
+
3333
+ Special Operators:
3334
+ - $size: Array length check
3335
+ Example: { "tags": { "$size": 2 } }
3336
+
3337
+ Restrictions:
3338
+ - Regex patterns are not supported
3339
+ - Direct RegExp patterns will throw an error
3340
+ - Nested fields are supported using dot notation
3341
+ - Multiple conditions on the same field are supported with both implicit and explicit $and
3342
+ - Array operations work on array fields only
3343
+ - Basic operators handle array values as JSON strings
3344
+ - Empty arrays in conditions are handled gracefully
3345
+ - Only logical operators ($and, $or, $not, $nor) can be used at the top level
3346
+ - All other operators must be used within a field condition
3347
+ Valid: { "field": { "$gt": 100 } }
3348
+ Valid: { "$and": [...] }
3349
+ Invalid: { "$gt": 100 }
3350
+ Invalid: { "$contains": "value" }
3351
+ - Logical operators must contain field conditions, not direct operators
3352
+ Valid: { "$and": [{ "field": { "$gt": 100 } }] }
3353
+ Invalid: { "$and": [{ "$gt": 100 }] }
3354
+ - $not operator:
3355
+ - Must be an object
3356
+ - Cannot be empty
3357
+ - Can be used at field level or top level
3358
+ - Valid: { "$not": { "field": "value" } }
3359
+ - Valid: { "field": { "$not": { "$eq": "value" } } }
3360
+ - Other logical operators ($and, $or, $nor):
3361
+ - Can only be used at top level or nested within other logical operators
3362
+ - Can not be used on a field level, or be nested inside a field
3363
+ - Can not be used inside an operator
3364
+ - Valid: { "$and": [{ "field": { "$gt": 100 } }] }
3365
+ - Valid: { "$or": [{ "$and": [{ "field": { "$gt": 100 } }] }] }
3366
+ - Invalid: { "field": { "$and": [{ "$gt": 100 }] } }
3367
+ - Invalid: { "field": { "$or": [{ "$gt": 100 }] } }
3368
+ - Invalid: { "field": { "$gt": { "$and": [{...}] } } }
3369
+ - $elemMatch requires an object with conditions
3370
+ Valid: { "array": { "$elemMatch": { "field": "value" } } }
3371
+ Invalid: { "array": { "$elemMatch": "value" } }
3372
+
3373
+ Example Complex Query:
3374
+ {
3375
+ "$and": [
3376
+ { "category": { "$in": ["electronics", "computers"] } },
3377
+ { "price": { "$gte": 100, "$lte": 1000 } },
3378
+ { "tags": { "$all": ["premium", "sale"] } },
3379
+ { "items": { "$elemMatch": { "price": { "$gt": 50 }, "inStock": true } } },
3380
+ { "$or": [
3381
+ { "stock": { "$gt": 0 } },
3382
+ { "preorder": true }
3383
+ ]}
3384
+ ]
3385
+ }`;
3386
+
3387
+ export { LibSQLStore as DefaultStorage, LIBSQL_PROMPT, LibSQLStore, LibSQLVector };
3388
+ //# sourceMappingURL=index.js.map
3389
+ //# sourceMappingURL=index.js.map