@mastra/libsql 0.13.8-alpha.0 → 0.13.8-alpha.2

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.
@@ -1,226 +0,0 @@
1
- import type { Client, InValue } from '@libsql/client';
2
- import { ErrorCategory, ErrorDomain, MastraError } from '@mastra/core/error';
3
- import type { ScoreRowData, ScoringSource } from '@mastra/core/scores';
4
- import { TABLE_SCORERS, ScoresStorage, safelyParseJSON } from '@mastra/core/storage';
5
- import type { PaginationInfo, StoragePagination } from '@mastra/core/storage';
6
- import type { StoreOperationsLibSQL } from '../operations';
7
-
8
- export class ScoresLibSQL extends ScoresStorage {
9
- private operations: StoreOperationsLibSQL;
10
- private client: Client;
11
- constructor({ client, operations }: { client: Client; operations: StoreOperationsLibSQL }) {
12
- super();
13
- this.operations = operations;
14
- this.client = client;
15
- }
16
-
17
- async getScoresByRunId({
18
- runId,
19
- pagination,
20
- }: {
21
- runId: string;
22
- pagination: StoragePagination;
23
- }): Promise<{ pagination: PaginationInfo; scores: ScoreRowData[] }> {
24
- try {
25
- const result = await this.client.execute({
26
- sql: `SELECT * FROM ${TABLE_SCORERS} WHERE runId = ? ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
27
- args: [runId, pagination.perPage + 1, pagination.page * pagination.perPage],
28
- });
29
- return {
30
- scores: result.rows?.slice(0, pagination.perPage).map(row => this.transformScoreRow(row)) ?? [],
31
- pagination: {
32
- total: result.rows?.length ?? 0,
33
- page: pagination.page,
34
- perPage: pagination.perPage,
35
- hasMore: result.rows?.length > pagination.perPage,
36
- },
37
- };
38
- } catch (error) {
39
- throw new MastraError(
40
- {
41
- id: 'LIBSQL_STORE_GET_SCORES_BY_RUN_ID_FAILED',
42
- domain: ErrorDomain.STORAGE,
43
- category: ErrorCategory.THIRD_PARTY,
44
- },
45
- error,
46
- );
47
- }
48
- }
49
-
50
- async getScoresByScorerId({
51
- scorerId,
52
- entityId,
53
- entityType,
54
- source,
55
- pagination,
56
- }: {
57
- scorerId: string;
58
- entityId?: string;
59
- entityType?: string;
60
- source?: ScoringSource;
61
- pagination: StoragePagination;
62
- }): Promise<{ pagination: PaginationInfo; scores: ScoreRowData[] }> {
63
- try {
64
- const conditions: string[] = [];
65
- const queryParams: InValue[] = [];
66
-
67
- if (scorerId) {
68
- conditions.push(`scorerId = ?`);
69
- queryParams.push(scorerId);
70
- }
71
-
72
- if (entityId) {
73
- conditions.push(`entityId = ?`);
74
- queryParams.push(entityId);
75
- }
76
-
77
- if (entityType) {
78
- conditions.push(`entityType = ?`);
79
- queryParams.push(entityType);
80
- }
81
-
82
- if (source) {
83
- conditions.push(`source = ?`);
84
- queryParams.push(source);
85
- }
86
-
87
- const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
88
-
89
- const result = await this.client.execute({
90
- sql: `SELECT * FROM ${TABLE_SCORERS} ${whereClause} ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
91
- args: [...queryParams, pagination.perPage + 1, pagination.page * pagination.perPage],
92
- });
93
-
94
- return {
95
- scores: result.rows?.slice(0, pagination.perPage).map(row => this.transformScoreRow(row)) ?? [],
96
- pagination: {
97
- total: result.rows?.length ?? 0,
98
- page: pagination.page,
99
- perPage: pagination.perPage,
100
- hasMore: result.rows?.length > pagination.perPage,
101
- },
102
- };
103
- } catch (error) {
104
- throw new MastraError(
105
- {
106
- id: 'LIBSQL_STORE_GET_SCORES_BY_SCORER_ID_FAILED',
107
- domain: ErrorDomain.STORAGE,
108
- category: ErrorCategory.THIRD_PARTY,
109
- },
110
- error,
111
- );
112
- }
113
- }
114
-
115
- private transformScoreRow(row: Record<string, any>): ScoreRowData {
116
- const scorerValue = safelyParseJSON(row.scorer);
117
- const inputValue = safelyParseJSON(row.input ?? '{}');
118
- const outputValue = safelyParseJSON(row.output ?? '{}');
119
- const additionalLLMContextValue = row.additionalLLMContext ? safelyParseJSON(row.additionalLLMContext) : null;
120
- const runtimeContextValue = row.runtimeContext ? safelyParseJSON(row.runtimeContext) : null;
121
- const metadataValue = row.metadata ? safelyParseJSON(row.metadata) : null;
122
- const entityValue = row.entity ? safelyParseJSON(row.entity) : null;
123
- const preprocessStepResultValue = row.preprocessStepResult ? safelyParseJSON(row.preprocessStepResult) : null;
124
- const analyzeStepResultValue = row.analyzeStepResult ? safelyParseJSON(row.analyzeStepResult) : null;
125
-
126
- return {
127
- id: row.id,
128
- traceId: row.traceId,
129
- runId: row.runId,
130
- scorer: scorerValue,
131
- score: row.score,
132
- reason: row.reason,
133
- preprocessStepResult: preprocessStepResultValue,
134
- analyzeStepResult: analyzeStepResultValue,
135
- analyzePrompt: row.analyzePrompt,
136
- preprocessPrompt: row.preprocessPrompt,
137
- generateScorePrompt: row.generateScorePrompt,
138
- generateReasonPrompt: row.generateReasonPrompt,
139
- metadata: metadataValue,
140
- input: inputValue,
141
- output: outputValue,
142
- additionalContext: additionalLLMContextValue,
143
- runtimeContext: runtimeContextValue,
144
- entityType: row.entityType,
145
- entity: entityValue,
146
- entityId: row.entityId,
147
- scorerId: row.scorerId,
148
- source: row.source,
149
- resourceId: row.resourceId,
150
- threadId: row.threadId,
151
- createdAt: row.createdAt,
152
- updatedAt: row.updatedAt,
153
- };
154
- }
155
-
156
- async getScoreById({ id }: { id: string }): Promise<ScoreRowData | null> {
157
- const result = await this.client.execute({
158
- sql: `SELECT * FROM ${TABLE_SCORERS} WHERE id = ?`,
159
- args: [id],
160
- });
161
- return result.rows?.[0] ? this.transformScoreRow(result.rows[0]) : null;
162
- }
163
-
164
- async saveScore(score: Omit<ScoreRowData, 'id' | 'createdAt' | 'updatedAt'>): Promise<{ score: ScoreRowData }> {
165
- try {
166
- const id = crypto.randomUUID();
167
-
168
- await this.operations.insert({
169
- tableName: TABLE_SCORERS,
170
- record: {
171
- id,
172
- createdAt: new Date().toISOString(),
173
- updatedAt: new Date().toISOString(),
174
- ...score,
175
- },
176
- });
177
-
178
- const scoreFromDb = await this.getScoreById({ id });
179
- return { score: scoreFromDb! };
180
- } catch (error) {
181
- throw new MastraError(
182
- {
183
- id: 'LIBSQL_STORE_SAVE_SCORE_FAILED',
184
- domain: ErrorDomain.STORAGE,
185
- category: ErrorCategory.THIRD_PARTY,
186
- },
187
- error,
188
- );
189
- }
190
- }
191
-
192
- async getScoresByEntityId({
193
- entityId,
194
- entityType,
195
- pagination,
196
- }: {
197
- pagination: StoragePagination;
198
- entityId: string;
199
- entityType: string;
200
- }): Promise<{ pagination: PaginationInfo; scores: ScoreRowData[] }> {
201
- try {
202
- const result = await this.client.execute({
203
- sql: `SELECT * FROM ${TABLE_SCORERS} WHERE entityId = ? AND entityType = ? ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
204
- args: [entityId, entityType, pagination.perPage + 1, pagination.page * pagination.perPage],
205
- });
206
- return {
207
- scores: result.rows?.slice(0, pagination.perPage).map(row => this.transformScoreRow(row)) ?? [],
208
- pagination: {
209
- total: result.rows?.length ?? 0,
210
- page: pagination.page,
211
- perPage: pagination.perPage,
212
- hasMore: result.rows?.length > pagination.perPage,
213
- },
214
- };
215
- } catch (error) {
216
- throw new MastraError(
217
- {
218
- id: 'LIBSQL_STORE_GET_SCORES_BY_ENTITY_ID_FAILED',
219
- domain: ErrorDomain.STORAGE,
220
- category: ErrorCategory.THIRD_PARTY,
221
- },
222
- error,
223
- );
224
- }
225
- }
226
- }
@@ -1,150 +0,0 @@
1
- import type { Client, InValue } from '@libsql/client';
2
- import { ErrorCategory, ErrorDomain, MastraError } from '@mastra/core/error';
3
- import { TABLE_TRACES, TracesStorage, safelyParseJSON } from '@mastra/core/storage';
4
- import type { StorageGetTracesArg, StorageGetTracesPaginatedArg, PaginationInfo } from '@mastra/core/storage';
5
- import type { Trace } from '@mastra/core/telemetry';
6
- import { parseSqlIdentifier } from '@mastra/core/utils';
7
- import type { StoreOperationsLibSQL } from '../operations';
8
-
9
- export class TracesLibSQL extends TracesStorage {
10
- private client: Client;
11
- private operations: StoreOperationsLibSQL;
12
-
13
- constructor({ client, operations }: { client: Client; operations: StoreOperationsLibSQL }) {
14
- super();
15
- this.client = client;
16
- this.operations = operations;
17
- }
18
-
19
- async getTraces(args: StorageGetTracesArg): Promise<Trace[]> {
20
- if (args.fromDate || args.toDate) {
21
- (args as any).dateRange = {
22
- start: args.fromDate,
23
- end: args.toDate,
24
- };
25
- }
26
- try {
27
- const result = await this.getTracesPaginated(args);
28
- return result.traces;
29
- } catch (error) {
30
- throw new MastraError(
31
- {
32
- id: 'LIBSQL_STORE_GET_TRACES_FAILED',
33
- domain: ErrorDomain.STORAGE,
34
- category: ErrorCategory.THIRD_PARTY,
35
- },
36
- error,
37
- );
38
- }
39
- }
40
-
41
- async getTracesPaginated(args: StorageGetTracesPaginatedArg): Promise<PaginationInfo & { traces: Trace[] }> {
42
- const { name, scope, page = 0, perPage = 100, attributes, filters, dateRange } = args;
43
- const fromDate = dateRange?.start;
44
- const toDate = dateRange?.end;
45
- const currentOffset = page * perPage;
46
-
47
- const queryArgs: InValue[] = [];
48
- const conditions: string[] = [];
49
-
50
- if (name) {
51
- conditions.push('name LIKE ?');
52
- queryArgs.push(`${name}%`);
53
- }
54
- if (scope) {
55
- conditions.push('scope = ?');
56
- queryArgs.push(scope);
57
- }
58
- if (attributes) {
59
- Object.entries(attributes).forEach(([key, value]) => {
60
- conditions.push(`json_extract(attributes, '$.${key}') = ?`);
61
- queryArgs.push(value);
62
- });
63
- }
64
- if (filters) {
65
- Object.entries(filters).forEach(([key, value]) => {
66
- conditions.push(`${parseSqlIdentifier(key, 'filter key')} = ?`);
67
- queryArgs.push(value);
68
- });
69
- }
70
- if (fromDate) {
71
- conditions.push('createdAt >= ?');
72
- queryArgs.push(fromDate.toISOString());
73
- }
74
- if (toDate) {
75
- conditions.push('createdAt <= ?');
76
- queryArgs.push(toDate.toISOString());
77
- }
78
-
79
- const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
80
-
81
- try {
82
- const countResult = await this.client.execute({
83
- sql: `SELECT COUNT(*) as count FROM ${TABLE_TRACES} ${whereClause}`,
84
- args: queryArgs,
85
- });
86
- const total = Number(countResult.rows?.[0]?.count ?? 0);
87
-
88
- if (total === 0) {
89
- return {
90
- traces: [],
91
- total: 0,
92
- page,
93
- perPage,
94
- hasMore: false,
95
- };
96
- }
97
-
98
- const dataResult = await this.client.execute({
99
- sql: `SELECT * FROM ${TABLE_TRACES} ${whereClause} ORDER BY "startTime" DESC LIMIT ? OFFSET ?`,
100
- args: [...queryArgs, perPage, currentOffset],
101
- });
102
-
103
- const traces =
104
- dataResult.rows?.map(
105
- row =>
106
- ({
107
- id: row.id,
108
- parentSpanId: row.parentSpanId,
109
- traceId: row.traceId,
110
- name: row.name,
111
- scope: row.scope,
112
- kind: row.kind,
113
- status: safelyParseJSON(row.status),
114
- events: safelyParseJSON(row.events),
115
- links: safelyParseJSON(row.links),
116
- attributes: safelyParseJSON(row.attributes),
117
- startTime: row.startTime,
118
- endTime: row.endTime,
119
- other: safelyParseJSON(row.other),
120
- createdAt: row.createdAt,
121
- }) as Trace,
122
- ) ?? [];
123
-
124
- return {
125
- traces,
126
- total,
127
- page,
128
- perPage,
129
- hasMore: currentOffset + traces.length < total,
130
- };
131
- } catch (error) {
132
- throw new MastraError(
133
- {
134
- id: 'LIBSQL_STORE_GET_TRACES_PAGINATED_FAILED',
135
- domain: ErrorDomain.STORAGE,
136
- category: ErrorCategory.THIRD_PARTY,
137
- },
138
- error,
139
- );
140
- }
141
- }
142
-
143
- async batchTraceInsert({ records }: { records: Record<string, any>[] }): Promise<void> {
144
- this.logger.debug('Batch inserting traces', { count: records.length });
145
- await this.operations.batchInsert({
146
- tableName: TABLE_TRACES,
147
- records,
148
- });
149
- }
150
- }
@@ -1,272 +0,0 @@
1
- import type { InValue } from '@libsql/client';
2
- import type { IMastraLogger } from '@mastra/core/logger';
3
- import { safelyParseJSON, TABLE_SCHEMAS } from '@mastra/core/storage';
4
- import type { PaginationArgs, StorageColumn, TABLE_NAMES } from '@mastra/core/storage';
5
- import { parseSqlIdentifier } from '@mastra/core/utils';
6
-
7
- export function createExecuteWriteOperationWithRetry({
8
- logger,
9
- maxRetries,
10
- initialBackoffMs,
11
- }: {
12
- logger: IMastraLogger;
13
- maxRetries: number;
14
- initialBackoffMs: number;
15
- }) {
16
- return async function executeWriteOperationWithRetry<T>(
17
- operationFn: () => Promise<T>,
18
- operationDescription: string,
19
- ): Promise<T> {
20
- let retries = 0;
21
-
22
- while (true) {
23
- try {
24
- return await operationFn();
25
- } catch (error: any) {
26
- if (
27
- error.message &&
28
- (error.message.includes('SQLITE_BUSY') || error.message.includes('database is locked')) &&
29
- retries < maxRetries
30
- ) {
31
- retries++;
32
- const backoffTime = initialBackoffMs * Math.pow(2, retries - 1);
33
- logger.warn(
34
- `LibSQLStore: Encountered SQLITE_BUSY during ${operationDescription}. Retrying (${retries}/${maxRetries}) in ${backoffTime}ms...`,
35
- );
36
- await new Promise(resolve => setTimeout(resolve, backoffTime));
37
- } else {
38
- logger.error(`LibSQLStore: Error during ${operationDescription} after ${retries} retries: ${error}`);
39
- throw error;
40
- }
41
- }
42
- }
43
- };
44
- }
45
-
46
- export function prepareStatement({ tableName, record }: { tableName: TABLE_NAMES; record: Record<string, any> }): {
47
- sql: string;
48
- args: InValue[];
49
- } {
50
- const parsedTableName = parseSqlIdentifier(tableName, 'table name');
51
- const columns = Object.keys(record).map(col => parseSqlIdentifier(col, 'column name'));
52
- const values = Object.values(record).map(v => {
53
- if (typeof v === `undefined` || v === null) {
54
- // returning an undefined value will cause libsql to throw
55
- return null;
56
- }
57
- if (v instanceof Date) {
58
- return v.toISOString();
59
- }
60
- return typeof v === 'object' ? JSON.stringify(v) : v;
61
- });
62
- const placeholders = values.map(() => '?').join(', ');
63
-
64
- return {
65
- sql: `INSERT OR REPLACE INTO ${parsedTableName} (${columns.join(', ')}) VALUES (${placeholders})`,
66
- args: values,
67
- };
68
- }
69
-
70
- export function prepareUpdateStatement({
71
- tableName,
72
- updates,
73
- keys,
74
- }: {
75
- tableName: TABLE_NAMES;
76
- updates: Record<string, any>;
77
- keys: Record<string, any>;
78
- }): {
79
- sql: string;
80
- args: InValue[];
81
- } {
82
- const parsedTableName = parseSqlIdentifier(tableName, 'table name');
83
- const schema = TABLE_SCHEMAS[tableName];
84
-
85
- // Prepare SET clause
86
- const updateColumns = Object.keys(updates).map(col => parseSqlIdentifier(col, 'column name'));
87
- const updateValues = Object.values(updates).map(transformToSqlValue);
88
- const setClause = updateColumns.map(col => `${col} = ?`).join(', ');
89
-
90
- const whereClause = prepareWhereClause(keys, schema);
91
-
92
- return {
93
- sql: `UPDATE ${parsedTableName} SET ${setClause}${whereClause.sql}`,
94
- args: [...updateValues, ...whereClause.args],
95
- };
96
- }
97
-
98
- export function transformToSqlValue(value: any): InValue {
99
- if (typeof value === 'undefined' || value === null) {
100
- return null;
101
- }
102
- if (value instanceof Date) {
103
- return value.toISOString();
104
- }
105
- return typeof value === 'object' ? JSON.stringify(value) : value;
106
- }
107
-
108
- export function prepareDeleteStatement({ tableName, keys }: { tableName: TABLE_NAMES; keys: Record<string, any> }): {
109
- sql: string;
110
- args: InValue[];
111
- } {
112
- const parsedTableName = parseSqlIdentifier(tableName, 'table name');
113
- const whereClause = prepareWhereClause(keys, TABLE_SCHEMAS[tableName]);
114
-
115
- return {
116
- sql: `DELETE FROM ${parsedTableName}${whereClause.sql}`,
117
- args: whereClause.args,
118
- };
119
- }
120
-
121
- type WhereValue = InValue | { startAt?: InValue; endAt?: InValue };
122
-
123
- export function prepareWhereClause(
124
- filters: Record<string, WhereValue>,
125
- schema: Record<string, StorageColumn>,
126
- ): {
127
- sql: string;
128
- args: InValue[];
129
- } {
130
- const conditions: string[] = [];
131
- const args: InValue[] = [];
132
-
133
- for (const [columnName, filterValue] of Object.entries(filters)) {
134
- const column = schema[columnName];
135
- if (!column) {
136
- throw new Error(`Unknown column: ${columnName}`);
137
- }
138
-
139
- const parsedColumn = parseSqlIdentifier(columnName, 'column name');
140
- const result = buildCondition(parsedColumn, filterValue);
141
-
142
- conditions.push(result.condition);
143
- args.push(...result.args);
144
- }
145
-
146
- return {
147
- sql: conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : '',
148
- args,
149
- };
150
- }
151
-
152
- function buildCondition(columnName: string, filterValue: WhereValue): { condition: string; args: InValue[] } {
153
- // Handle null values - IS NULL
154
- if (filterValue === null) {
155
- return { condition: `${columnName} IS NULL`, args: [] };
156
- }
157
-
158
- // Handle date range objects
159
- if (typeof filterValue === 'object' && filterValue !== null && ('startAt' in filterValue || 'endAt' in filterValue)) {
160
- return buildDateRangeCondition(columnName, filterValue);
161
- }
162
-
163
- // Handle exact match
164
- return {
165
- condition: `${columnName} = ?`,
166
- args: [transformToSqlValue(filterValue)],
167
- };
168
- }
169
-
170
- function buildDateRangeCondition(
171
- columnName: string,
172
- range: { startAt?: InValue; endAt?: InValue },
173
- ): { condition: string; args: InValue[] } {
174
- const conditions: string[] = [];
175
- const args: InValue[] = [];
176
-
177
- if (range.startAt !== undefined) {
178
- conditions.push(`${columnName} >= ?`);
179
- args.push(transformToSqlValue(range.startAt));
180
- }
181
-
182
- if (range.endAt !== undefined) {
183
- conditions.push(`${columnName} <= ?`);
184
- args.push(transformToSqlValue(range.endAt));
185
- }
186
-
187
- if (conditions.length === 0) {
188
- throw new Error('Date range must specify at least startAt or endAt');
189
- }
190
-
191
- return {
192
- condition: conditions.join(' AND '),
193
- args,
194
- };
195
- }
196
-
197
- type DateRangeFilter = {
198
- startAt?: string;
199
- endAt?: string;
200
- };
201
-
202
- /**
203
- * Converts pagination date range to where clause date range format
204
- * @param dateRange - The date range from pagination
205
- * @param columnName - The timestamp column to filter on (defaults to 'createdAt')
206
- * @returns Object with the date range filter, or empty object if no date range
207
- */
208
- export function buildDateRangeFilter(
209
- dateRange?: PaginationArgs['dateRange'],
210
- columnName: string = 'createdAt',
211
- ): Record<string, DateRangeFilter> {
212
- if (!dateRange?.start && !dateRange?.end) {
213
- return {};
214
- }
215
-
216
- const filter: DateRangeFilter = {};
217
-
218
- if (dateRange.start) {
219
- filter.startAt = new Date(dateRange.start).toISOString();
220
- }
221
-
222
- if (dateRange.end) {
223
- filter.endAt = new Date(dateRange.end).toISOString();
224
- }
225
-
226
- return { [columnName]: filter };
227
- }
228
-
229
- /**
230
- * Transforms SQL row data back to a typed object format
231
- * Reverses the transformations done in prepareStatement
232
- */
233
- export function transformFromSqlRow<T>({
234
- tableName,
235
- sqlRow,
236
- }: {
237
- tableName: TABLE_NAMES;
238
- sqlRow: Record<string, any>;
239
- }): T {
240
- const result: Record<string, any> = {};
241
- const jsonColumns = new Set(
242
- Object.keys(TABLE_SCHEMAS[tableName])
243
- .filter(key => TABLE_SCHEMAS[tableName][key]!.type === 'jsonb')
244
- .map(key => key),
245
- );
246
- const dateColumns = new Set(
247
- Object.keys(TABLE_SCHEMAS[tableName])
248
- .filter(key => TABLE_SCHEMAS[tableName][key]!.type === 'timestamp')
249
- .map(key => key),
250
- );
251
-
252
- for (const [key, value] of Object.entries(sqlRow)) {
253
- if (value === null || value === undefined) {
254
- result[key] = value;
255
- continue;
256
- }
257
-
258
- if (dateColumns.has(key) && typeof value === 'string') {
259
- result[key] = new Date(value);
260
- continue;
261
- }
262
-
263
- if (jsonColumns.has(key) && typeof value === 'string') {
264
- result[key] = safelyParseJSON(value);
265
- continue;
266
- }
267
-
268
- result[key] = value;
269
- }
270
-
271
- return result as T;
272
- }