@mastra/libsql 0.10.1-alpha.3 → 0.10.2-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +8 -8
- package/CHANGELOG.md +44 -0
- package/dist/_tsup-dts-rollup.d.cts +41 -3
- package/dist/_tsup-dts-rollup.d.ts +41 -3
- package/dist/index.cjs +295 -128
- package/dist/index.js +295 -128
- package/package.json +10 -10
- package/src/storage/index.test.ts +353 -6
- package/src/storage/index.ts +371 -155
package/src/storage/index.ts
CHANGED
|
@@ -6,20 +6,23 @@ import type { MetricResult, TestInfo } from '@mastra/core/eval';
|
|
|
6
6
|
import type { MastraMessageV1, StorageThreadType } from '@mastra/core/memory';
|
|
7
7
|
import {
|
|
8
8
|
MastraStorage,
|
|
9
|
+
TABLE_EVALS,
|
|
9
10
|
TABLE_MESSAGES,
|
|
10
11
|
TABLE_THREADS,
|
|
11
12
|
TABLE_TRACES,
|
|
12
13
|
TABLE_WORKFLOW_SNAPSHOT,
|
|
13
|
-
TABLE_EVALS,
|
|
14
14
|
} from '@mastra/core/storage';
|
|
15
15
|
import type {
|
|
16
16
|
EvalRow,
|
|
17
|
+
PaginationArgs,
|
|
18
|
+
PaginationInfo,
|
|
17
19
|
StorageColumn,
|
|
18
20
|
StorageGetMessagesArg,
|
|
19
21
|
TABLE_NAMES,
|
|
20
22
|
WorkflowRun,
|
|
21
23
|
WorkflowRuns,
|
|
22
24
|
} from '@mastra/core/storage';
|
|
25
|
+
import type { Trace } from '@mastra/core/telemetry';
|
|
23
26
|
import { parseSqlIdentifier } from '@mastra/core/utils';
|
|
24
27
|
import type { WorkflowRunState } from '@mastra/core/workflows';
|
|
25
28
|
|
|
@@ -274,24 +277,97 @@ export class LibSQLStore extends MastraStorage {
|
|
|
274
277
|
};
|
|
275
278
|
}
|
|
276
279
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}
|
|
280
|
+
/**
|
|
281
|
+
* @deprecated use getThreadsByResourceIdPaginated instead for paginated results.
|
|
282
|
+
*/
|
|
283
|
+
public async getThreadsByResourceId(args: { resourceId: string }): Promise<StorageThreadType[]> {
|
|
284
|
+
const { resourceId } = args;
|
|
285
|
+
|
|
286
|
+
try {
|
|
287
|
+
const baseQuery = `FROM ${TABLE_THREADS} WHERE resourceId = ?`;
|
|
288
|
+
const queryParams: InValue[] = [resourceId];
|
|
289
|
+
|
|
290
|
+
const mapRowToStorageThreadType = (row: any): StorageThreadType => ({
|
|
291
|
+
id: row.id as string,
|
|
292
|
+
resourceId: row.resourceId as string,
|
|
293
|
+
title: row.title as string,
|
|
294
|
+
createdAt: new Date(row.createdAt as string), // Convert string to Date
|
|
295
|
+
updatedAt: new Date(row.updatedAt as string), // Convert string to Date
|
|
296
|
+
metadata: typeof row.metadata === 'string' ? JSON.parse(row.metadata) : row.metadata,
|
|
297
|
+
});
|
|
282
298
|
|
|
283
|
-
|
|
299
|
+
// Non-paginated path
|
|
300
|
+
const result = await this.client.execute({
|
|
301
|
+
sql: `SELECT * ${baseQuery} ORDER BY createdAt DESC`,
|
|
302
|
+
args: queryParams,
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
if (!result.rows) {
|
|
306
|
+
return [];
|
|
307
|
+
}
|
|
308
|
+
return result.rows.map(mapRowToStorageThreadType);
|
|
309
|
+
} catch (error) {
|
|
310
|
+
this.logger.error(`Error getting threads for resource ${resourceId}:`, error);
|
|
284
311
|
return [];
|
|
285
312
|
}
|
|
313
|
+
}
|
|
286
314
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
resourceId:
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
315
|
+
public async getThreadsByResourceIdPaginated(
|
|
316
|
+
args: {
|
|
317
|
+
resourceId: string;
|
|
318
|
+
} & PaginationArgs,
|
|
319
|
+
): Promise<PaginationInfo & { threads: StorageThreadType[] }> {
|
|
320
|
+
const { resourceId, page = 0, perPage = 100 } = args;
|
|
321
|
+
|
|
322
|
+
try {
|
|
323
|
+
const baseQuery = `FROM ${TABLE_THREADS} WHERE resourceId = ?`;
|
|
324
|
+
const queryParams: InValue[] = [resourceId];
|
|
325
|
+
|
|
326
|
+
const mapRowToStorageThreadType = (row: any): StorageThreadType => ({
|
|
327
|
+
id: row.id as string,
|
|
328
|
+
resourceId: row.resourceId as string,
|
|
329
|
+
title: row.title as string,
|
|
330
|
+
createdAt: new Date(row.createdAt as string), // Convert string to Date
|
|
331
|
+
updatedAt: new Date(row.updatedAt as string), // Convert string to Date
|
|
332
|
+
metadata: typeof row.metadata === 'string' ? JSON.parse(row.metadata) : row.metadata,
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
const currentOffset = page * perPage;
|
|
336
|
+
|
|
337
|
+
const countResult = await this.client.execute({
|
|
338
|
+
sql: `SELECT COUNT(*) as count ${baseQuery}`,
|
|
339
|
+
args: queryParams,
|
|
340
|
+
});
|
|
341
|
+
const total = Number(countResult.rows?.[0]?.count ?? 0);
|
|
342
|
+
|
|
343
|
+
if (total === 0) {
|
|
344
|
+
return {
|
|
345
|
+
threads: [],
|
|
346
|
+
total: 0,
|
|
347
|
+
page,
|
|
348
|
+
perPage,
|
|
349
|
+
hasMore: false,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const dataResult = await this.client.execute({
|
|
354
|
+
sql: `SELECT * ${baseQuery} ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
|
|
355
|
+
args: [...queryParams, perPage, currentOffset],
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
const threads = (dataResult.rows || []).map(mapRowToStorageThreadType);
|
|
359
|
+
|
|
360
|
+
return {
|
|
361
|
+
threads,
|
|
362
|
+
total,
|
|
363
|
+
page,
|
|
364
|
+
perPage,
|
|
365
|
+
hasMore: currentOffset + threads.length < total,
|
|
366
|
+
};
|
|
367
|
+
} catch (error) {
|
|
368
|
+
this.logger.error(`Error getting threads for resource ${resourceId}:`, error);
|
|
369
|
+
return { threads: [], total: 0, page, perPage, hasMore: false };
|
|
370
|
+
}
|
|
295
371
|
}
|
|
296
372
|
|
|
297
373
|
async saveThread({ thread }: { thread: StorageThreadType }): Promise<StorageThreadType> {
|
|
@@ -363,87 +439,76 @@ export class LibSQLStore extends MastraStorage {
|
|
|
363
439
|
return result;
|
|
364
440
|
}
|
|
365
441
|
|
|
442
|
+
private async _getIncludedMessages(threadId: string, selectBy: StorageGetMessagesArg['selectBy']) {
|
|
443
|
+
const include = selectBy?.include;
|
|
444
|
+
if (!include) return null;
|
|
445
|
+
|
|
446
|
+
const includeIds = include.map(i => i.id);
|
|
447
|
+
const maxPrev = Math.max(...include.map(i => i.withPreviousMessages || 0));
|
|
448
|
+
const maxNext = Math.max(...include.map(i => i.withNextMessages || 0));
|
|
449
|
+
|
|
450
|
+
const includeResult = await this.client.execute({
|
|
451
|
+
sql: `
|
|
452
|
+
WITH numbered_messages AS (
|
|
453
|
+
SELECT
|
|
454
|
+
id, content, role, type, "createdAt", thread_id,
|
|
455
|
+
ROW_NUMBER() OVER (ORDER BY "createdAt" ASC) as row_num
|
|
456
|
+
FROM "${TABLE_MESSAGES}"
|
|
457
|
+
WHERE thread_id = ?
|
|
458
|
+
),
|
|
459
|
+
target_positions AS (
|
|
460
|
+
SELECT row_num as target_pos
|
|
461
|
+
FROM numbered_messages
|
|
462
|
+
WHERE id IN (${includeIds.map(() => '?').join(', ')})
|
|
463
|
+
)
|
|
464
|
+
SELECT DISTINCT m.*
|
|
465
|
+
FROM numbered_messages m
|
|
466
|
+
CROSS JOIN target_positions t
|
|
467
|
+
WHERE m.row_num BETWEEN (t.target_pos - ?) AND (t.target_pos + ?)
|
|
468
|
+
ORDER BY m."createdAt" ASC
|
|
469
|
+
`,
|
|
470
|
+
args: [threadId, ...includeIds, maxPrev, maxNext],
|
|
471
|
+
});
|
|
472
|
+
return includeResult.rows?.map((row: any) => this.parseRow(row));
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* @deprecated use getMessagesPaginated instead for paginated results.
|
|
477
|
+
*/
|
|
366
478
|
public async getMessages(args: StorageGetMessagesArg & { format?: 'v1' }): Promise<MastraMessageV1[]>;
|
|
367
479
|
public async getMessages(args: StorageGetMessagesArg & { format: 'v2' }): Promise<MastraMessageV2[]>;
|
|
368
480
|
public async getMessages({
|
|
369
481
|
threadId,
|
|
370
482
|
selectBy,
|
|
371
483
|
format,
|
|
372
|
-
}: StorageGetMessagesArg & {
|
|
484
|
+
}: StorageGetMessagesArg & {
|
|
485
|
+
format?: 'v1' | 'v2';
|
|
486
|
+
}): Promise<MastraMessageV1[] | MastraMessageV2[]> {
|
|
373
487
|
try {
|
|
374
488
|
const messages: MastraMessageV2[] = [];
|
|
375
489
|
const limit = typeof selectBy?.last === `number` ? selectBy.last : 40;
|
|
376
490
|
|
|
377
|
-
// If we have specific messages to select
|
|
378
491
|
if (selectBy?.include?.length) {
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
// Get messages around all specified IDs in one query using row numbers
|
|
384
|
-
const includeResult = await this.client.execute({
|
|
385
|
-
sql: `
|
|
386
|
-
WITH numbered_messages AS (
|
|
387
|
-
SELECT
|
|
388
|
-
id,
|
|
389
|
-
content,
|
|
390
|
-
role,
|
|
391
|
-
type,
|
|
392
|
-
"createdAt",
|
|
393
|
-
thread_id,
|
|
394
|
-
ROW_NUMBER() OVER (ORDER BY "createdAt" ASC) as row_num
|
|
395
|
-
FROM "${TABLE_MESSAGES}"
|
|
396
|
-
WHERE thread_id = ?
|
|
397
|
-
),
|
|
398
|
-
target_positions AS (
|
|
399
|
-
SELECT row_num as target_pos
|
|
400
|
-
FROM numbered_messages
|
|
401
|
-
WHERE id IN (${includeIds.map(() => '?').join(', ')})
|
|
402
|
-
)
|
|
403
|
-
SELECT DISTINCT m.*
|
|
404
|
-
FROM numbered_messages m
|
|
405
|
-
CROSS JOIN target_positions t
|
|
406
|
-
WHERE m.row_num BETWEEN (t.target_pos - ?) AND (t.target_pos + ?)
|
|
407
|
-
ORDER BY m."createdAt" ASC
|
|
408
|
-
`,
|
|
409
|
-
args: [threadId, ...includeIds, maxPrev, maxNext],
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
if (includeResult.rows) {
|
|
413
|
-
messages.push(...includeResult.rows.map((row: any) => this.parseRow(row)));
|
|
492
|
+
const includeMessages = await this._getIncludedMessages(threadId, selectBy);
|
|
493
|
+
if (includeMessages) {
|
|
494
|
+
messages.push(...includeMessages);
|
|
414
495
|
}
|
|
415
496
|
}
|
|
416
497
|
|
|
417
|
-
// Get remaining messages, excluding already fetched IDs
|
|
418
498
|
const excludeIds = messages.map(m => m.id);
|
|
419
499
|
const remainingSql = `
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
thread_id
|
|
427
|
-
FROM "${TABLE_MESSAGES}"
|
|
428
|
-
WHERE thread_id = ?
|
|
429
|
-
${excludeIds.length ? `AND id NOT IN (${excludeIds.map(() => '?').join(', ')})` : ''}
|
|
430
|
-
ORDER BY "createdAt" DESC
|
|
431
|
-
LIMIT ?
|
|
432
|
-
`;
|
|
500
|
+
SELECT id, content, role, type, "createdAt", thread_id
|
|
501
|
+
FROM "${TABLE_MESSAGES}"
|
|
502
|
+
WHERE thread_id = ?
|
|
503
|
+
${excludeIds.length ? `AND id NOT IN (${excludeIds.map(() => '?').join(', ')})` : ''}
|
|
504
|
+
ORDER BY "createdAt" DESC LIMIT ?
|
|
505
|
+
`;
|
|
433
506
|
const remainingArgs = [threadId, ...(excludeIds.length ? excludeIds : []), limit];
|
|
434
|
-
|
|
435
|
-
const remainingResult = await this.client.execute({
|
|
436
|
-
sql: remainingSql,
|
|
437
|
-
args: remainingArgs,
|
|
438
|
-
});
|
|
439
|
-
|
|
507
|
+
const remainingResult = await this.client.execute({ sql: remainingSql, args: remainingArgs });
|
|
440
508
|
if (remainingResult.rows) {
|
|
441
509
|
messages.push(...remainingResult.rows.map((row: any) => this.parseRow(row)));
|
|
442
510
|
}
|
|
443
|
-
|
|
444
|
-
// Sort all messages by creation date
|
|
445
511
|
messages.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
|
|
446
|
-
|
|
447
512
|
const list = new MessageList().add(messages, 'memory');
|
|
448
513
|
if (format === `v2`) return list.get.all.v2();
|
|
449
514
|
return list.get.all.v1();
|
|
@@ -453,6 +518,82 @@ export class LibSQLStore extends MastraStorage {
|
|
|
453
518
|
}
|
|
454
519
|
}
|
|
455
520
|
|
|
521
|
+
public async getMessagesPaginated(
|
|
522
|
+
args: StorageGetMessagesArg & {
|
|
523
|
+
format?: 'v1' | 'v2';
|
|
524
|
+
},
|
|
525
|
+
): Promise<PaginationInfo & { messages: MastraMessageV1[] | MastraMessageV2[] }> {
|
|
526
|
+
const { threadId, format, selectBy } = args;
|
|
527
|
+
const { page = 0, perPage = 40, dateRange } = selectBy?.pagination || {};
|
|
528
|
+
const fromDate = dateRange?.start;
|
|
529
|
+
const toDate = dateRange?.end;
|
|
530
|
+
|
|
531
|
+
const messages: MastraMessageV2[] = [];
|
|
532
|
+
|
|
533
|
+
if (selectBy?.include?.length) {
|
|
534
|
+
const includeMessages = await this._getIncludedMessages(threadId, selectBy);
|
|
535
|
+
if (includeMessages) {
|
|
536
|
+
messages.push(...includeMessages);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
try {
|
|
541
|
+
const currentOffset = page * perPage;
|
|
542
|
+
|
|
543
|
+
const conditions: string[] = [`thread_id = ?`];
|
|
544
|
+
const queryParams: InValue[] = [threadId];
|
|
545
|
+
|
|
546
|
+
if (fromDate) {
|
|
547
|
+
conditions.push(`"createdAt" >= ?`);
|
|
548
|
+
queryParams.push(fromDate.toISOString());
|
|
549
|
+
}
|
|
550
|
+
if (toDate) {
|
|
551
|
+
conditions.push(`"createdAt" <= ?`);
|
|
552
|
+
queryParams.push(toDate.toISOString());
|
|
553
|
+
}
|
|
554
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
555
|
+
|
|
556
|
+
const countResult = await this.client.execute({
|
|
557
|
+
sql: `SELECT COUNT(*) as count FROM ${TABLE_MESSAGES} ${whereClause}`,
|
|
558
|
+
args: queryParams,
|
|
559
|
+
});
|
|
560
|
+
const total = Number(countResult.rows?.[0]?.count ?? 0);
|
|
561
|
+
|
|
562
|
+
if (total === 0) {
|
|
563
|
+
return {
|
|
564
|
+
messages: [],
|
|
565
|
+
total: 0,
|
|
566
|
+
page,
|
|
567
|
+
perPage,
|
|
568
|
+
hasMore: false,
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const dataResult = await this.client.execute({
|
|
573
|
+
sql: `SELECT id, content, role, type, "createdAt", thread_id FROM ${TABLE_MESSAGES} ${whereClause} ORDER BY "createdAt" DESC LIMIT ? OFFSET ?`,
|
|
574
|
+
args: [...queryParams, perPage, currentOffset],
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
messages.push(...(dataResult.rows || []).map((row: any) => this.parseRow(row)));
|
|
578
|
+
|
|
579
|
+
const messagesToReturn =
|
|
580
|
+
format === 'v1'
|
|
581
|
+
? new MessageList().add(messages, 'memory').get.all.v1()
|
|
582
|
+
: new MessageList().add(messages, 'memory').get.all.v2();
|
|
583
|
+
|
|
584
|
+
return {
|
|
585
|
+
messages: messagesToReturn,
|
|
586
|
+
total,
|
|
587
|
+
page,
|
|
588
|
+
perPage,
|
|
589
|
+
hasMore: currentOffset + messages.length < total,
|
|
590
|
+
};
|
|
591
|
+
} catch (error) {
|
|
592
|
+
this.logger.error('Error getting paginated messages:', error as Error);
|
|
593
|
+
return { messages: [], total: 0, page, perPage, hasMore: false };
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
456
597
|
async saveMessages(args: { messages: MastraMessageV1[]; format?: undefined | 'v1' }): Promise<MastraMessageV1[]>;
|
|
457
598
|
async saveMessages(args: { messages: MastraMessageV2[]; format: 'v2' }): Promise<MastraMessageV2[]>;
|
|
458
599
|
async saveMessages({
|
|
@@ -526,6 +667,7 @@ export class LibSQLStore extends MastraStorage {
|
|
|
526
667
|
};
|
|
527
668
|
}
|
|
528
669
|
|
|
670
|
+
/** @deprecated use getEvals instead */
|
|
529
671
|
async getEvalsByAgentName(agentName: string, type?: 'test' | 'live'): Promise<EvalRow[]> {
|
|
530
672
|
try {
|
|
531
673
|
const baseQuery = `SELECT * FROM ${TABLE_EVALS} WHERE agent_name = ?`;
|
|
@@ -552,120 +694,194 @@ export class LibSQLStore extends MastraStorage {
|
|
|
552
694
|
}
|
|
553
695
|
}
|
|
554
696
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
697
|
+
async getEvals(
|
|
698
|
+
options: {
|
|
699
|
+
agentName?: string;
|
|
700
|
+
type?: 'test' | 'live';
|
|
701
|
+
} & PaginationArgs = {},
|
|
702
|
+
): Promise<PaginationInfo & { evals: EvalRow[] }> {
|
|
703
|
+
const { agentName, type, page = 0, perPage = 100, dateRange } = options;
|
|
704
|
+
const fromDate = dateRange?.start;
|
|
705
|
+
const toDate = dateRange?.end;
|
|
706
|
+
|
|
707
|
+
const conditions: string[] = [];
|
|
708
|
+
const queryParams: InValue[] = [];
|
|
709
|
+
|
|
710
|
+
if (agentName) {
|
|
711
|
+
conditions.push(`agent_name = ?`);
|
|
712
|
+
queryParams.push(agentName);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
if (type === 'test') {
|
|
716
|
+
conditions.push(`(test_info IS NOT NULL AND json_extract(test_info, '$.testPath') IS NOT NULL)`);
|
|
717
|
+
} else if (type === 'live') {
|
|
718
|
+
conditions.push(`(test_info IS NULL OR json_extract(test_info, '$.testPath') IS NULL)`);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
if (fromDate) {
|
|
722
|
+
conditions.push(`created_at >= ?`);
|
|
723
|
+
queryParams.push(fromDate.toISOString());
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
if (toDate) {
|
|
727
|
+
conditions.push(`created_at <= ?`);
|
|
728
|
+
queryParams.push(toDate.toISOString());
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
732
|
+
|
|
733
|
+
const countResult = await this.client.execute({
|
|
734
|
+
sql: `SELECT COUNT(*) as count FROM ${TABLE_EVALS} ${whereClause}`,
|
|
735
|
+
args: queryParams,
|
|
736
|
+
});
|
|
737
|
+
const total = Number(countResult.rows?.[0]?.count ?? 0);
|
|
738
|
+
|
|
739
|
+
const currentOffset = page * perPage;
|
|
740
|
+
const hasMore = currentOffset + perPage < total;
|
|
741
|
+
|
|
742
|
+
if (total === 0) {
|
|
743
|
+
return {
|
|
744
|
+
evals: [],
|
|
745
|
+
total: 0,
|
|
746
|
+
page,
|
|
747
|
+
perPage,
|
|
748
|
+
hasMore: false,
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
const dataResult = await this.client.execute({
|
|
753
|
+
sql: `SELECT * FROM ${TABLE_EVALS} ${whereClause} ORDER BY created_at DESC LIMIT ? OFFSET ?`,
|
|
754
|
+
args: [...queryParams, perPage, currentOffset],
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
return {
|
|
758
|
+
evals: dataResult.rows?.map(row => this.transformEvalRow(row)) ?? [],
|
|
759
|
+
total,
|
|
560
760
|
page,
|
|
561
761
|
perPage,
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
762
|
+
hasMore,
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* @deprecated use getTracesPaginated instead.
|
|
768
|
+
*/
|
|
769
|
+
public async getTraces(args: {
|
|
770
|
+
name?: string;
|
|
771
|
+
scope?: string;
|
|
772
|
+
page: number;
|
|
773
|
+
perPage: number;
|
|
774
|
+
attributes?: Record<string, string>;
|
|
775
|
+
filters?: Record<string, any>;
|
|
776
|
+
fromDate?: Date;
|
|
777
|
+
toDate?: Date;
|
|
778
|
+
}): Promise<Trace[]> {
|
|
779
|
+
if (args.fromDate || args.toDate) {
|
|
780
|
+
(args as any).dateRange = {
|
|
781
|
+
start: args.fromDate,
|
|
782
|
+
end: args.toDate,
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
const result = await this.getTracesPaginated(args);
|
|
786
|
+
return result.traces;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
public async getTracesPaginated(
|
|
790
|
+
args: {
|
|
567
791
|
name?: string;
|
|
568
792
|
scope?: string;
|
|
569
|
-
page: number;
|
|
570
|
-
perPage: number;
|
|
571
793
|
attributes?: Record<string, string>;
|
|
572
794
|
filters?: Record<string, any>;
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
} =
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
const
|
|
581
|
-
const offset = page * perPage;
|
|
582
|
-
|
|
583
|
-
const args: (string | number)[] = [];
|
|
584
|
-
|
|
795
|
+
} & PaginationArgs,
|
|
796
|
+
): Promise<PaginationInfo & { traces: Trace[] }> {
|
|
797
|
+
const { name, scope, page = 0, perPage = 100, attributes, filters, dateRange } = args;
|
|
798
|
+
const fromDate = dateRange?.start;
|
|
799
|
+
const toDate = dateRange?.end;
|
|
800
|
+
const currentOffset = page * perPage;
|
|
801
|
+
|
|
802
|
+
const queryArgs: InValue[] = [];
|
|
585
803
|
const conditions: string[] = [];
|
|
804
|
+
|
|
586
805
|
if (name) {
|
|
587
|
-
conditions.push(
|
|
806
|
+
conditions.push('name LIKE ?');
|
|
807
|
+
queryArgs.push(`${name}%`);
|
|
588
808
|
}
|
|
589
809
|
if (scope) {
|
|
590
810
|
conditions.push('scope = ?');
|
|
811
|
+
queryArgs.push(scope);
|
|
591
812
|
}
|
|
592
813
|
if (attributes) {
|
|
593
|
-
Object.
|
|
594
|
-
conditions.push(`attributes
|
|
814
|
+
Object.entries(attributes).forEach(([key, value]) => {
|
|
815
|
+
conditions.push(`json_extract(attributes, '$.${key}') = ?`);
|
|
816
|
+
queryArgs.push(value);
|
|
595
817
|
});
|
|
596
818
|
}
|
|
597
|
-
|
|
598
819
|
if (filters) {
|
|
599
|
-
Object.entries(filters).forEach(([key,
|
|
600
|
-
conditions.push(`${key} = ?`);
|
|
820
|
+
Object.entries(filters).forEach(([key, value]) => {
|
|
821
|
+
conditions.push(`${parseSqlIdentifier(key, 'filter key')} = ?`);
|
|
822
|
+
queryArgs.push(value);
|
|
601
823
|
});
|
|
602
824
|
}
|
|
603
|
-
|
|
604
825
|
if (fromDate) {
|
|
605
826
|
conditions.push('createdAt >= ?');
|
|
827
|
+
queryArgs.push(fromDate.toISOString());
|
|
606
828
|
}
|
|
607
|
-
|
|
608
829
|
if (toDate) {
|
|
609
830
|
conditions.push('createdAt <= ?');
|
|
831
|
+
queryArgs.push(toDate.toISOString());
|
|
610
832
|
}
|
|
611
833
|
|
|
612
834
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
613
835
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
args.push(scope);
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
if (attributes) {
|
|
623
|
-
for (const [, value] of Object.entries(attributes)) {
|
|
624
|
-
args.push(value);
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
if (filters) {
|
|
629
|
-
for (const [, value] of Object.entries(filters)) {
|
|
630
|
-
args.push(value);
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
if (fromDate) {
|
|
635
|
-
args.push(fromDate.toISOString());
|
|
636
|
-
}
|
|
836
|
+
const countResult = await this.client.execute({
|
|
837
|
+
sql: `SELECT COUNT(*) as count FROM ${TABLE_TRACES} ${whereClause}`,
|
|
838
|
+
args: queryArgs,
|
|
839
|
+
});
|
|
840
|
+
const total = Number(countResult.rows?.[0]?.count ?? 0);
|
|
637
841
|
|
|
638
|
-
if (
|
|
639
|
-
|
|
842
|
+
if (total === 0) {
|
|
843
|
+
return {
|
|
844
|
+
traces: [],
|
|
845
|
+
total: 0,
|
|
846
|
+
page,
|
|
847
|
+
perPage,
|
|
848
|
+
hasMore: false,
|
|
849
|
+
};
|
|
640
850
|
}
|
|
641
851
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
const result = await this.client.execute({
|
|
852
|
+
const dataResult = await this.client.execute({
|
|
645
853
|
sql: `SELECT * FROM ${TABLE_TRACES} ${whereClause} ORDER BY "startTime" DESC LIMIT ? OFFSET ?`,
|
|
646
|
-
args,
|
|
854
|
+
args: [...queryArgs, perPage, currentOffset],
|
|
647
855
|
});
|
|
648
856
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
857
|
+
const traces =
|
|
858
|
+
dataResult.rows?.map(
|
|
859
|
+
row =>
|
|
860
|
+
({
|
|
861
|
+
id: row.id,
|
|
862
|
+
parentSpanId: row.parentSpanId,
|
|
863
|
+
traceId: row.traceId,
|
|
864
|
+
name: row.name,
|
|
865
|
+
scope: row.scope,
|
|
866
|
+
kind: row.kind,
|
|
867
|
+
status: safelyParseJSON(row.status as string),
|
|
868
|
+
events: safelyParseJSON(row.events as string),
|
|
869
|
+
links: safelyParseJSON(row.links as string),
|
|
870
|
+
attributes: safelyParseJSON(row.attributes as string),
|
|
871
|
+
startTime: row.startTime,
|
|
872
|
+
endTime: row.endTime,
|
|
873
|
+
other: safelyParseJSON(row.other as string),
|
|
874
|
+
createdAt: row.createdAt,
|
|
875
|
+
}) as Trace,
|
|
876
|
+
) ?? [];
|
|
652
877
|
|
|
653
|
-
return
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
status: safelyParseJSON(row.status as string),
|
|
661
|
-
events: safelyParseJSON(row.events as string),
|
|
662
|
-
links: safelyParseJSON(row.links as string),
|
|
663
|
-
attributes: safelyParseJSON(row.attributes as string),
|
|
664
|
-
startTime: row.startTime,
|
|
665
|
-
endTime: row.endTime,
|
|
666
|
-
other: safelyParseJSON(row.other as string),
|
|
667
|
-
createdAt: row.createdAt,
|
|
668
|
-
})) as any;
|
|
878
|
+
return {
|
|
879
|
+
traces,
|
|
880
|
+
total,
|
|
881
|
+
page,
|
|
882
|
+
perPage,
|
|
883
|
+
hasMore: currentOffset + traces.length < total,
|
|
884
|
+
};
|
|
669
885
|
}
|
|
670
886
|
|
|
671
887
|
async getWorkflowRuns({
|