@mastra/pg 0.10.1 → 0.10.2-alpha.1
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 +28 -0
- package/dist/_tsup-dts-rollup.d.cts +58 -5
- package/dist/_tsup-dts-rollup.d.ts +58 -5
- package/dist/index.cjs +319 -126
- package/dist/index.js +319 -126
- package/package.json +12 -11
- package/src/storage/index.test.ts +558 -120
- package/src/storage/index.ts +405 -160
package/src/storage/index.ts
CHANGED
|
@@ -12,11 +12,13 @@ import {
|
|
|
12
12
|
} from '@mastra/core/storage';
|
|
13
13
|
import type {
|
|
14
14
|
EvalRow,
|
|
15
|
+
PaginationInfo,
|
|
15
16
|
StorageColumn,
|
|
16
17
|
StorageGetMessagesArg,
|
|
17
18
|
TABLE_NAMES,
|
|
18
19
|
WorkflowRun,
|
|
19
20
|
WorkflowRuns,
|
|
21
|
+
PaginationArgs,
|
|
20
22
|
} from '@mastra/core/storage';
|
|
21
23
|
import { parseSqlIdentifier } from '@mastra/core/utils';
|
|
22
24
|
import type { WorkflowRunState } from '@mastra/core/workflows';
|
|
@@ -91,6 +93,7 @@ export class PostgresStore extends MastraStorage {
|
|
|
91
93
|
return parsedSchemaName ? `${parsedSchemaName}."${parsedIndexName}"` : `"${parsedIndexName}"`;
|
|
92
94
|
}
|
|
93
95
|
|
|
96
|
+
/** @deprecated use getEvals instead */
|
|
94
97
|
async getEvalsByAgentName(agentName: string, type?: 'test' | 'live'): Promise<EvalRow[]> {
|
|
95
98
|
try {
|
|
96
99
|
const baseQuery = `SELECT * FROM ${this.getTableName(TABLE_EVALS)} WHERE agent_name = $1`;
|
|
@@ -153,115 +156,107 @@ export class PostgresStore extends MastraStorage {
|
|
|
153
156
|
}
|
|
154
157
|
}
|
|
155
158
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
perPage,
|
|
161
|
-
attributes,
|
|
162
|
-
filters,
|
|
163
|
-
fromDate,
|
|
164
|
-
toDate,
|
|
165
|
-
}: {
|
|
159
|
+
/**
|
|
160
|
+
* @deprecated use getTracesPaginated instead
|
|
161
|
+
*/
|
|
162
|
+
public async getTraces(args: {
|
|
166
163
|
name?: string;
|
|
167
164
|
scope?: string;
|
|
168
|
-
page: number;
|
|
169
|
-
perPage: number;
|
|
170
165
|
attributes?: Record<string, string>;
|
|
171
166
|
filters?: Record<string, any>;
|
|
167
|
+
page: number;
|
|
168
|
+
perPage?: number;
|
|
172
169
|
fromDate?: Date;
|
|
173
170
|
toDate?: Date;
|
|
174
171
|
}): Promise<any[]> {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
172
|
+
if (args.fromDate || args.toDate) {
|
|
173
|
+
(args as any).dateRange = {
|
|
174
|
+
start: args.fromDate,
|
|
175
|
+
end: args.toDate,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
const result = await this.getTracesPaginated(args);
|
|
179
|
+
return result.traces;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
public async getTracesPaginated(
|
|
183
|
+
args: {
|
|
184
|
+
name?: string;
|
|
185
|
+
scope?: string;
|
|
186
|
+
attributes?: Record<string, string>;
|
|
187
|
+
filters?: Record<string, any>;
|
|
188
|
+
} & PaginationArgs,
|
|
189
|
+
): Promise<
|
|
190
|
+
PaginationInfo & {
|
|
191
|
+
traces: any[];
|
|
192
|
+
}
|
|
193
|
+
> {
|
|
194
|
+
const { name, scope, page = 0, perPage: perPageInput, attributes, filters, dateRange } = args;
|
|
195
|
+
const fromDate = dateRange?.start;
|
|
196
|
+
const toDate = dateRange?.end;
|
|
178
197
|
|
|
179
|
-
const
|
|
198
|
+
const perPage = perPageInput !== undefined ? perPageInput : 100; // Default perPage
|
|
199
|
+
const currentOffset = page * perPage;
|
|
180
200
|
|
|
201
|
+
const queryParams: any[] = [];
|
|
181
202
|
const conditions: string[] = [];
|
|
203
|
+
let paramIndex = 1;
|
|
204
|
+
|
|
182
205
|
if (name) {
|
|
183
|
-
conditions.push(`name LIKE
|
|
206
|
+
conditions.push(`name LIKE $${paramIndex++}`);
|
|
207
|
+
queryParams.push(`${name}%`); // Add wildcard for LIKE
|
|
184
208
|
}
|
|
185
209
|
if (scope) {
|
|
186
|
-
conditions.push(`scope =
|
|
210
|
+
conditions.push(`scope = $${paramIndex++}`);
|
|
211
|
+
queryParams.push(scope);
|
|
187
212
|
}
|
|
188
213
|
if (attributes) {
|
|
189
|
-
Object.
|
|
214
|
+
Object.entries(attributes).forEach(([key, value]) => {
|
|
190
215
|
const parsedKey = parseSqlIdentifier(key, 'attribute key');
|
|
191
|
-
conditions.push(`attributes->>'${parsedKey}' =
|
|
216
|
+
conditions.push(`attributes->>'${parsedKey}' = $${paramIndex++}`);
|
|
217
|
+
queryParams.push(value);
|
|
192
218
|
});
|
|
193
219
|
}
|
|
194
|
-
|
|
195
220
|
if (filters) {
|
|
196
|
-
Object.entries(filters).forEach(([key]) => {
|
|
221
|
+
Object.entries(filters).forEach(([key, value]) => {
|
|
197
222
|
const parsedKey = parseSqlIdentifier(key, 'filter key');
|
|
198
|
-
conditions.push(
|
|
223
|
+
conditions.push(`"${parsedKey}" = $${paramIndex++}`); // Ensure filter keys are quoted if they are column names
|
|
224
|
+
queryParams.push(value);
|
|
199
225
|
});
|
|
200
226
|
}
|
|
201
|
-
|
|
202
227
|
if (fromDate) {
|
|
203
|
-
conditions.push(`createdAt >=
|
|
228
|
+
conditions.push(`"createdAt" >= $${paramIndex++}`);
|
|
229
|
+
queryParams.push(fromDate);
|
|
204
230
|
}
|
|
205
|
-
|
|
206
231
|
if (toDate) {
|
|
207
|
-
conditions.push(`createdAt <=
|
|
232
|
+
conditions.push(`"createdAt" <= $${paramIndex++}`);
|
|
233
|
+
queryParams.push(toDate);
|
|
208
234
|
}
|
|
209
235
|
|
|
210
236
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
211
237
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if (scope) {
|
|
217
|
-
args.push(scope);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (attributes) {
|
|
221
|
-
for (const [_key, value] of Object.entries(attributes)) {
|
|
222
|
-
args.push(value);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
if (filters) {
|
|
227
|
-
for (const [, value] of Object.entries(filters)) {
|
|
228
|
-
args.push(value);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
238
|
+
// Get total count
|
|
239
|
+
const countQuery = `SELECT COUNT(*) FROM ${this.getTableName(TABLE_TRACES)} ${whereClause}`;
|
|
240
|
+
const countResult = await this.db.one(countQuery, queryParams);
|
|
241
|
+
const total = parseInt(countResult.count, 10);
|
|
231
242
|
|
|
232
|
-
if (
|
|
233
|
-
|
|
243
|
+
if (total === 0) {
|
|
244
|
+
return {
|
|
245
|
+
traces: [],
|
|
246
|
+
total: 0,
|
|
247
|
+
page,
|
|
248
|
+
perPage,
|
|
249
|
+
hasMore: false,
|
|
250
|
+
};
|
|
234
251
|
}
|
|
235
252
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const result = await this.db.manyOrNone<{
|
|
241
|
-
id: string;
|
|
242
|
-
parentSpanId: string;
|
|
243
|
-
traceId: string;
|
|
244
|
-
name: string;
|
|
245
|
-
scope: string;
|
|
246
|
-
kind: string;
|
|
247
|
-
events: any[];
|
|
248
|
-
links: any[];
|
|
249
|
-
status: any;
|
|
250
|
-
attributes: Record<string, any>;
|
|
251
|
-
startTime: string;
|
|
252
|
-
endTime: string;
|
|
253
|
-
other: any;
|
|
254
|
-
createdAt: string;
|
|
255
|
-
}>(
|
|
256
|
-
`SELECT * FROM ${this.getTableName(TABLE_TRACES)} ${whereClause} ORDER BY "createdAt" DESC LIMIT ${limit} OFFSET ${offset}`,
|
|
257
|
-
args,
|
|
258
|
-
);
|
|
253
|
+
const dataQuery = `SELECT * FROM ${this.getTableName(
|
|
254
|
+
TABLE_TRACES,
|
|
255
|
+
)} ${whereClause} ORDER BY "createdAt" DESC LIMIT $${paramIndex++} OFFSET $${paramIndex++}`;
|
|
256
|
+
const finalQueryParams = [...queryParams, perPage, currentOffset];
|
|
259
257
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
return result.map(row => ({
|
|
258
|
+
const rows = await this.db.manyOrNone<any>(dataQuery, finalQueryParams);
|
|
259
|
+
const traces = rows.map(row => ({
|
|
265
260
|
id: row.id,
|
|
266
261
|
parentSpanId: row.parentSpanId,
|
|
267
262
|
traceId: row.traceId,
|
|
@@ -276,7 +271,15 @@ export class PostgresStore extends MastraStorage {
|
|
|
276
271
|
endTime: row.endTime,
|
|
277
272
|
other: row.other,
|
|
278
273
|
createdAt: row.createdAt,
|
|
279
|
-
}))
|
|
274
|
+
}));
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
traces,
|
|
278
|
+
total,
|
|
279
|
+
page,
|
|
280
|
+
perPage,
|
|
281
|
+
hasMore: currentOffset + traces.length < total,
|
|
282
|
+
};
|
|
280
283
|
}
|
|
281
284
|
|
|
282
285
|
private async setupSchema() {
|
|
@@ -379,6 +382,57 @@ export class PostgresStore extends MastraStorage {
|
|
|
379
382
|
}
|
|
380
383
|
}
|
|
381
384
|
|
|
385
|
+
protected getDefaultValue(type: StorageColumn['type']): string {
|
|
386
|
+
switch (type) {
|
|
387
|
+
case 'timestamp':
|
|
388
|
+
return 'DEFAULT NOW()';
|
|
389
|
+
case 'jsonb':
|
|
390
|
+
return "DEFAULT '{}'::jsonb";
|
|
391
|
+
default:
|
|
392
|
+
return super.getDefaultValue(type);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Alters table schema to add columns if they don't exist
|
|
398
|
+
* @param tableName Name of the table
|
|
399
|
+
* @param schema Schema of the table
|
|
400
|
+
* @param ifNotExists Array of column names to add if they don't exist
|
|
401
|
+
*/
|
|
402
|
+
async alterTable({
|
|
403
|
+
tableName,
|
|
404
|
+
schema,
|
|
405
|
+
ifNotExists,
|
|
406
|
+
}: {
|
|
407
|
+
tableName: TABLE_NAMES;
|
|
408
|
+
schema: Record<string, StorageColumn>;
|
|
409
|
+
ifNotExists: string[];
|
|
410
|
+
}): Promise<void> {
|
|
411
|
+
const fullTableName = this.getTableName(tableName);
|
|
412
|
+
|
|
413
|
+
try {
|
|
414
|
+
for (const columnName of ifNotExists) {
|
|
415
|
+
if (schema[columnName]) {
|
|
416
|
+
const columnDef = schema[columnName];
|
|
417
|
+
const sqlType = this.getSqlType(columnDef.type);
|
|
418
|
+
const nullable = columnDef.nullable === false ? 'NOT NULL' : '';
|
|
419
|
+
const defaultValue = columnDef.nullable === false ? this.getDefaultValue(columnDef.type) : '';
|
|
420
|
+
const parsedColumnName = parseSqlIdentifier(columnName, 'column name');
|
|
421
|
+
const alterSql =
|
|
422
|
+
`ALTER TABLE ${fullTableName} ADD COLUMN IF NOT EXISTS "${parsedColumnName}" ${sqlType} ${nullable} ${defaultValue}`.trim();
|
|
423
|
+
|
|
424
|
+
await this.db.none(alterSql);
|
|
425
|
+
this.logger?.debug?.(`Ensured column ${parsedColumnName} exists in table ${fullTableName}`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
} catch (error) {
|
|
429
|
+
this.logger?.error?.(
|
|
430
|
+
`Error altering table ${tableName}: ${error instanceof Error ? error.message : String(error)}`,
|
|
431
|
+
);
|
|
432
|
+
throw new Error(`Failed to alter table ${tableName}: ${error}`);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
382
436
|
async clearTable({ tableName }: { tableName: TABLE_NAMES }): Promise<void> {
|
|
383
437
|
try {
|
|
384
438
|
await this.db.none(`TRUNCATE TABLE ${this.getTableName(tableName)} CASCADE`);
|
|
@@ -466,30 +520,76 @@ export class PostgresStore extends MastraStorage {
|
|
|
466
520
|
}
|
|
467
521
|
}
|
|
468
522
|
|
|
469
|
-
|
|
523
|
+
/**
|
|
524
|
+
* @deprecated use getThreadsByResourceIdPaginated instead
|
|
525
|
+
*/
|
|
526
|
+
public async getThreadsByResourceId(args: { resourceId: string }): Promise<StorageThreadType[]> {
|
|
527
|
+
const { resourceId } = args;
|
|
528
|
+
|
|
470
529
|
try {
|
|
471
|
-
const
|
|
472
|
-
|
|
473
|
-
id,
|
|
474
|
-
"resourceId",
|
|
475
|
-
title,
|
|
476
|
-
metadata,
|
|
477
|
-
"createdAt",
|
|
478
|
-
"updatedAt"
|
|
479
|
-
FROM ${this.getTableName(TABLE_THREADS)}
|
|
480
|
-
WHERE "resourceId" = $1`,
|
|
481
|
-
[resourceId],
|
|
482
|
-
);
|
|
530
|
+
const baseQuery = `FROM ${this.getTableName(TABLE_THREADS)} WHERE "resourceId" = $1`;
|
|
531
|
+
const queryParams: any[] = [resourceId];
|
|
483
532
|
|
|
484
|
-
|
|
533
|
+
const dataQuery = `SELECT id, "resourceId", title, metadata, "createdAt", "updatedAt" ${baseQuery} ORDER BY "createdAt" DESC`;
|
|
534
|
+
const rows = await this.db.manyOrNone(dataQuery, queryParams);
|
|
535
|
+
return (rows || []).map(thread => ({
|
|
485
536
|
...thread,
|
|
486
537
|
metadata: typeof thread.metadata === 'string' ? JSON.parse(thread.metadata) : thread.metadata,
|
|
487
538
|
createdAt: thread.createdAt,
|
|
488
539
|
updatedAt: thread.updatedAt,
|
|
489
540
|
}));
|
|
490
541
|
} catch (error) {
|
|
491
|
-
|
|
492
|
-
|
|
542
|
+
this.logger.error(`Error getting threads for resource ${resourceId}:`, error);
|
|
543
|
+
return [];
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
public async getThreadsByResourceIdPaginated(
|
|
548
|
+
args: {
|
|
549
|
+
resourceId: string;
|
|
550
|
+
} & PaginationArgs,
|
|
551
|
+
): Promise<PaginationInfo & { threads: StorageThreadType[] }> {
|
|
552
|
+
const { resourceId, page = 0, perPage: perPageInput } = args;
|
|
553
|
+
try {
|
|
554
|
+
const baseQuery = `FROM ${this.getTableName(TABLE_THREADS)} WHERE "resourceId" = $1`;
|
|
555
|
+
const queryParams: any[] = [resourceId];
|
|
556
|
+
const perPage = perPageInput !== undefined ? perPageInput : 100;
|
|
557
|
+
const currentOffset = page * perPage;
|
|
558
|
+
|
|
559
|
+
const countQuery = `SELECT COUNT(*) ${baseQuery}`;
|
|
560
|
+
const countResult = await this.db.one(countQuery, queryParams);
|
|
561
|
+
const total = parseInt(countResult.count, 10);
|
|
562
|
+
|
|
563
|
+
if (total === 0) {
|
|
564
|
+
return {
|
|
565
|
+
threads: [],
|
|
566
|
+
total: 0,
|
|
567
|
+
page,
|
|
568
|
+
perPage,
|
|
569
|
+
hasMore: false,
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const dataQuery = `SELECT id, "resourceId", title, metadata, "createdAt", "updatedAt" ${baseQuery} ORDER BY "createdAt" DESC LIMIT $2 OFFSET $3`;
|
|
574
|
+
const rows = await this.db.manyOrNone(dataQuery, [...queryParams, perPage, currentOffset]);
|
|
575
|
+
|
|
576
|
+
const threads = (rows || []).map(thread => ({
|
|
577
|
+
...thread,
|
|
578
|
+
metadata: typeof thread.metadata === 'string' ? JSON.parse(thread.metadata) : thread.metadata,
|
|
579
|
+
createdAt: thread.createdAt, // Assuming already Date objects or ISO strings
|
|
580
|
+
updatedAt: thread.updatedAt,
|
|
581
|
+
}));
|
|
582
|
+
|
|
583
|
+
return {
|
|
584
|
+
threads,
|
|
585
|
+
total,
|
|
586
|
+
page,
|
|
587
|
+
perPage,
|
|
588
|
+
hasMore: currentOffset + threads.length < total,
|
|
589
|
+
};
|
|
590
|
+
} catch (error) {
|
|
591
|
+
this.logger.error(`Error getting threads for resource ${resourceId}:`, error);
|
|
592
|
+
return { threads: [], total: 0, page, perPage: perPageInput || 100, hasMore: false };
|
|
493
593
|
}
|
|
494
594
|
}
|
|
495
595
|
|
|
@@ -586,95 +686,174 @@ export class PostgresStore extends MastraStorage {
|
|
|
586
686
|
}
|
|
587
687
|
}
|
|
588
688
|
|
|
589
|
-
|
|
689
|
+
/**
|
|
690
|
+
* @deprecated use getMessagesPaginated instead
|
|
691
|
+
*/
|
|
692
|
+
public async getMessages(args: StorageGetMessagesArg & { format?: 'v1' }): Promise<MastraMessageV1[]>;
|
|
693
|
+
public async getMessages(args: StorageGetMessagesArg & { format: 'v2' }): Promise<MastraMessageV2[]>;
|
|
694
|
+
public async getMessages(
|
|
695
|
+
args: StorageGetMessagesArg & {
|
|
696
|
+
format?: 'v1' | 'v2';
|
|
697
|
+
},
|
|
698
|
+
): Promise<MastraMessageV1[] | MastraMessageV2[]> {
|
|
699
|
+
const { threadId, format, selectBy } = args;
|
|
700
|
+
|
|
701
|
+
const selectStatement = `SELECT id, content, role, type, "createdAt", thread_id AS "threadId"`;
|
|
702
|
+
const orderByStatement = `ORDER BY "createdAt" DESC`;
|
|
703
|
+
|
|
590
704
|
try {
|
|
591
|
-
|
|
592
|
-
const limit = typeof selectBy?.last === `number` ? selectBy.last : 40;
|
|
705
|
+
let rows: any[] = [];
|
|
593
706
|
const include = selectBy?.include || [];
|
|
594
707
|
|
|
595
708
|
if (include.length) {
|
|
596
|
-
|
|
709
|
+
rows = await this.db.manyOrNone(
|
|
597
710
|
`
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
)
|
|
605
|
-
SELECT
|
|
606
|
-
m.id,
|
|
607
|
-
m.content,
|
|
608
|
-
m.role,
|
|
609
|
-
m.type,
|
|
610
|
-
m."createdAt",
|
|
611
|
-
m.thread_id AS "threadId"
|
|
612
|
-
FROM ordered_messages m
|
|
613
|
-
WHERE m.id = ANY($2)
|
|
614
|
-
OR EXISTS (
|
|
615
|
-
SELECT 1 FROM ordered_messages target
|
|
616
|
-
WHERE target.id = ANY($2)
|
|
617
|
-
AND (
|
|
618
|
-
-- Get previous messages based on the max withPreviousMessages
|
|
619
|
-
(m.row_num <= target.row_num + $3 AND m.row_num > target.row_num)
|
|
620
|
-
OR
|
|
621
|
-
-- Get next messages based on the max withNextMessages
|
|
622
|
-
(m.row_num >= target.row_num - $4 AND m.row_num < target.row_num)
|
|
711
|
+
WITH ordered_messages AS (
|
|
712
|
+
SELECT
|
|
713
|
+
*,
|
|
714
|
+
ROW_NUMBER() OVER (${orderByStatement}) as row_num
|
|
715
|
+
FROM ${this.getTableName(TABLE_MESSAGES)}
|
|
716
|
+
WHERE thread_id = $1
|
|
623
717
|
)
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
718
|
+
SELECT
|
|
719
|
+
m.id,
|
|
720
|
+
m.content,
|
|
721
|
+
m.role,
|
|
722
|
+
m.type,
|
|
723
|
+
m."createdAt",
|
|
724
|
+
m.thread_id AS "threadId"
|
|
725
|
+
FROM ordered_messages m
|
|
726
|
+
WHERE m.id = ANY($2)
|
|
727
|
+
OR EXISTS (
|
|
728
|
+
SELECT 1 FROM ordered_messages target
|
|
729
|
+
WHERE target.id = ANY($2)
|
|
730
|
+
AND (
|
|
731
|
+
-- Get previous messages based on the max withPreviousMessages
|
|
732
|
+
(m.row_num <= target.row_num + $3 AND m.row_num > target.row_num)
|
|
733
|
+
OR
|
|
734
|
+
-- Get next messages based on the max withNextMessages
|
|
735
|
+
(m.row_num >= target.row_num - $4 AND m.row_num < target.row_num)
|
|
736
|
+
)
|
|
737
|
+
)
|
|
738
|
+
ORDER BY m."createdAt" ASC
|
|
739
|
+
`, // Keep ASC for final sorting after fetching context
|
|
627
740
|
[
|
|
628
741
|
threadId,
|
|
629
742
|
include.map(i => i.id),
|
|
630
|
-
Math.max(...include.map(i => i.withPreviousMessages || 0)),
|
|
631
|
-
Math.max(...include.map(i => i.withNextMessages || 0)),
|
|
743
|
+
Math.max(0, ...include.map(i => i.withPreviousMessages || 0)), // Ensure non-negative
|
|
744
|
+
Math.max(0, ...include.map(i => i.withNextMessages || 0)), // Ensure non-negative
|
|
632
745
|
],
|
|
633
746
|
);
|
|
634
|
-
|
|
635
|
-
|
|
747
|
+
} else {
|
|
748
|
+
const limit = typeof selectBy?.last === `number` ? selectBy.last : 40;
|
|
749
|
+
if (limit === 0 && selectBy?.last !== false) {
|
|
750
|
+
// if last is explicitly false, we fetch all
|
|
751
|
+
// Do nothing, rows will be empty, and we return empty array later.
|
|
752
|
+
} else {
|
|
753
|
+
let query = `${selectStatement} FROM ${this.getTableName(
|
|
754
|
+
TABLE_MESSAGES,
|
|
755
|
+
)} WHERE thread_id = $1 ${orderByStatement}`;
|
|
756
|
+
const queryParams: any[] = [threadId];
|
|
757
|
+
if (limit !== undefined && selectBy?.last !== false) {
|
|
758
|
+
query += ` LIMIT $2`;
|
|
759
|
+
queryParams.push(limit);
|
|
760
|
+
}
|
|
761
|
+
rows = await this.db.manyOrNone(query, queryParams);
|
|
762
|
+
}
|
|
636
763
|
}
|
|
637
764
|
|
|
638
|
-
|
|
639
|
-
const result = await this.db.manyOrNone(
|
|
640
|
-
`
|
|
641
|
-
SELECT
|
|
642
|
-
id,
|
|
643
|
-
content,
|
|
644
|
-
role,
|
|
645
|
-
type,
|
|
646
|
-
"createdAt",
|
|
647
|
-
thread_id AS "threadId"
|
|
648
|
-
FROM ${this.getTableName(TABLE_MESSAGES)}
|
|
649
|
-
WHERE thread_id = $1
|
|
650
|
-
AND id != ALL($2)
|
|
651
|
-
ORDER BY "createdAt" DESC
|
|
652
|
-
LIMIT $3
|
|
653
|
-
`,
|
|
654
|
-
[threadId, messages.map(m => m.id), limit],
|
|
655
|
-
);
|
|
656
|
-
|
|
657
|
-
messages.push(...result);
|
|
658
|
-
|
|
659
|
-
// Sort all messages by creation date
|
|
660
|
-
messages.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
|
|
661
|
-
|
|
662
|
-
// Parse message content
|
|
663
|
-
messages.forEach(message => {
|
|
765
|
+
const fetchedMessages = (rows || []).map(message => {
|
|
664
766
|
if (typeof message.content === 'string') {
|
|
665
767
|
try {
|
|
666
768
|
message.content = JSON.parse(message.content);
|
|
667
769
|
} catch {
|
|
668
|
-
|
|
770
|
+
/* ignore */
|
|
669
771
|
}
|
|
670
772
|
}
|
|
671
|
-
if (message.type ===
|
|
773
|
+
if (message.type === 'v2') delete message.type;
|
|
774
|
+
return message as MastraMessageV1;
|
|
672
775
|
});
|
|
673
776
|
|
|
674
|
-
|
|
777
|
+
// Sort all messages by creation date
|
|
778
|
+
const sortedMessages = fetchedMessages.sort(
|
|
779
|
+
(a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(),
|
|
780
|
+
);
|
|
781
|
+
|
|
782
|
+
return format === 'v2'
|
|
783
|
+
? sortedMessages.map(
|
|
784
|
+
m =>
|
|
785
|
+
({ ...m, content: m.content || { format: 2, parts: [{ type: 'text', text: '' }] } }) as MastraMessageV2,
|
|
786
|
+
)
|
|
787
|
+
: sortedMessages;
|
|
675
788
|
} catch (error) {
|
|
676
|
-
|
|
677
|
-
|
|
789
|
+
this.logger.error('Error getting messages:', error);
|
|
790
|
+
return [];
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
public async getMessagesPaginated(
|
|
795
|
+
args: StorageGetMessagesArg & {
|
|
796
|
+
format?: 'v1' | 'v2';
|
|
797
|
+
},
|
|
798
|
+
): Promise<PaginationInfo & { messages: MastraMessageV1[] | MastraMessageV2[] }> {
|
|
799
|
+
const { threadId, format, selectBy } = args;
|
|
800
|
+
const { page = 0, perPage: perPageInput, dateRange } = selectBy?.pagination || {};
|
|
801
|
+
const fromDate = dateRange?.start;
|
|
802
|
+
const toDate = dateRange?.end;
|
|
803
|
+
|
|
804
|
+
const selectStatement = `SELECT id, content, role, type, "createdAt", thread_id AS "threadId"`;
|
|
805
|
+
const orderByStatement = `ORDER BY "createdAt" DESC`;
|
|
806
|
+
|
|
807
|
+
try {
|
|
808
|
+
const perPage = perPageInput !== undefined ? perPageInput : 40;
|
|
809
|
+
const currentOffset = page * perPage;
|
|
810
|
+
|
|
811
|
+
const conditions: string[] = [`thread_id = $1`];
|
|
812
|
+
const queryParams: any[] = [threadId];
|
|
813
|
+
let paramIndex = 2;
|
|
814
|
+
|
|
815
|
+
if (fromDate) {
|
|
816
|
+
conditions.push(`"createdAt" >= $${paramIndex++}`);
|
|
817
|
+
queryParams.push(fromDate);
|
|
818
|
+
}
|
|
819
|
+
if (toDate) {
|
|
820
|
+
conditions.push(`"createdAt" <= $${paramIndex++}`);
|
|
821
|
+
queryParams.push(toDate);
|
|
822
|
+
}
|
|
823
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
824
|
+
|
|
825
|
+
const countQuery = `SELECT COUNT(*) FROM ${this.getTableName(TABLE_MESSAGES)} ${whereClause}`;
|
|
826
|
+
const countResult = await this.db.one(countQuery, queryParams);
|
|
827
|
+
const total = parseInt(countResult.count, 10);
|
|
828
|
+
|
|
829
|
+
if (total === 0) {
|
|
830
|
+
return {
|
|
831
|
+
messages: [],
|
|
832
|
+
total: 0,
|
|
833
|
+
page,
|
|
834
|
+
perPage,
|
|
835
|
+
hasMore: false,
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
const dataQuery = `${selectStatement} FROM ${this.getTableName(
|
|
840
|
+
TABLE_MESSAGES,
|
|
841
|
+
)} ${whereClause} ${orderByStatement} LIMIT $${paramIndex++} OFFSET $${paramIndex++}`;
|
|
842
|
+
const rows = await this.db.manyOrNone(dataQuery, [...queryParams, perPage, currentOffset]);
|
|
843
|
+
|
|
844
|
+
const list = new MessageList().add(rows || [], 'memory');
|
|
845
|
+
const messagesToReturn = format === `v2` ? list.get.all.v2() : list.get.all.v1();
|
|
846
|
+
|
|
847
|
+
return {
|
|
848
|
+
messages: messagesToReturn,
|
|
849
|
+
total,
|
|
850
|
+
page,
|
|
851
|
+
perPage,
|
|
852
|
+
hasMore: currentOffset + rows.length < total,
|
|
853
|
+
};
|
|
854
|
+
} catch (error) {
|
|
855
|
+
this.logger.error('Error getting messages:', error);
|
|
856
|
+
return { messages: [], total: 0, page, perPage: perPageInput || 40, hasMore: false };
|
|
678
857
|
}
|
|
679
858
|
}
|
|
680
859
|
|
|
@@ -948,4 +1127,70 @@ export class PostgresStore extends MastraStorage {
|
|
|
948
1127
|
async close(): Promise<void> {
|
|
949
1128
|
this.pgp.end();
|
|
950
1129
|
}
|
|
1130
|
+
|
|
1131
|
+
async getEvals(
|
|
1132
|
+
options: {
|
|
1133
|
+
agentName?: string;
|
|
1134
|
+
type?: 'test' | 'live';
|
|
1135
|
+
} & PaginationArgs = {},
|
|
1136
|
+
): Promise<PaginationInfo & { evals: EvalRow[] }> {
|
|
1137
|
+
const { agentName, type, page = 0, perPage = 100, dateRange } = options;
|
|
1138
|
+
const fromDate = dateRange?.start;
|
|
1139
|
+
const toDate = dateRange?.end;
|
|
1140
|
+
|
|
1141
|
+
const conditions: string[] = [];
|
|
1142
|
+
const queryParams: any[] = [];
|
|
1143
|
+
let paramIndex = 1;
|
|
1144
|
+
|
|
1145
|
+
if (agentName) {
|
|
1146
|
+
conditions.push(`agent_name = $${paramIndex++}`);
|
|
1147
|
+
queryParams.push(agentName);
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
if (type === 'test') {
|
|
1151
|
+
conditions.push(`(test_info IS NOT NULL AND test_info->>'testPath' IS NOT NULL)`);
|
|
1152
|
+
} else if (type === 'live') {
|
|
1153
|
+
conditions.push(`(test_info IS NULL OR test_info->>'testPath' IS NULL)`);
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
if (fromDate) {
|
|
1157
|
+
conditions.push(`created_at >= $${paramIndex++}`);
|
|
1158
|
+
queryParams.push(fromDate);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
if (toDate) {
|
|
1162
|
+
conditions.push(`created_at <= $${paramIndex++}`);
|
|
1163
|
+
queryParams.push(toDate);
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
1167
|
+
|
|
1168
|
+
const countQuery = `SELECT COUNT(*) FROM ${this.getTableName(TABLE_EVALS)} ${whereClause}`;
|
|
1169
|
+
const countResult = await this.db.one(countQuery, queryParams);
|
|
1170
|
+
const total = parseInt(countResult.count, 10);
|
|
1171
|
+
const currentOffset = page * perPage;
|
|
1172
|
+
|
|
1173
|
+
if (total === 0) {
|
|
1174
|
+
return {
|
|
1175
|
+
evals: [],
|
|
1176
|
+
total: 0,
|
|
1177
|
+
page,
|
|
1178
|
+
perPage,
|
|
1179
|
+
hasMore: false,
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
const dataQuery = `SELECT * FROM ${this.getTableName(
|
|
1184
|
+
TABLE_EVALS,
|
|
1185
|
+
)} ${whereClause} ORDER BY created_at DESC LIMIT $${paramIndex++} OFFSET $${paramIndex++}`;
|
|
1186
|
+
const rows = await this.db.manyOrNone(dataQuery, [...queryParams, perPage, currentOffset]);
|
|
1187
|
+
|
|
1188
|
+
return {
|
|
1189
|
+
evals: rows?.map(row => this.transformEvalRow(row)) ?? [],
|
|
1190
|
+
total,
|
|
1191
|
+
page,
|
|
1192
|
+
perPage,
|
|
1193
|
+
hasMore: currentOffset + (rows?.length ?? 0) < total,
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
951
1196
|
}
|