@mastra/pg 0.10.1 → 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.
@@ -91,6 +91,7 @@ export class PostgresStore extends MastraStorage {
91
91
  return parsedSchemaName ? `${parsedSchemaName}."${parsedIndexName}"` : `"${parsedIndexName}"`;
92
92
  }
93
93
 
94
+ /** @deprecated use getEvals instead */
94
95
  async getEvalsByAgentName(agentName: string, type?: 'test' | 'live'): Promise<EvalRow[]> {
95
96
  try {
96
97
  const baseQuery = `SELECT * FROM ${this.getTableName(TABLE_EVALS)} WHERE agent_name = $1`;
@@ -153,115 +154,127 @@ export class PostgresStore extends MastraStorage {
153
154
  }
154
155
  }
155
156
 
156
- async getTraces({
157
- name,
158
- scope,
159
- page,
160
- perPage,
161
- attributes,
162
- filters,
163
- fromDate,
164
- toDate,
165
- }: {
157
+ public async getTraces(args: {
158
+ name?: string;
159
+ scope?: string;
160
+ attributes?: Record<string, string>;
161
+ filters?: Record<string, any>;
162
+ page: number;
163
+ perPage?: number;
164
+ fromDate?: Date;
165
+ toDate?: Date;
166
+ }): Promise<any[]>;
167
+ public async getTraces(args: {
166
168
  name?: string;
167
169
  scope?: string;
168
170
  page: number;
171
+ perPage?: number;
172
+ attributes?: Record<string, string>;
173
+ filters?: Record<string, any>;
174
+ fromDate?: Date;
175
+ toDate?: Date;
176
+ returnPaginationResults: true;
177
+ }): Promise<{
178
+ traces: any[];
179
+ total: number;
180
+ page: number;
169
181
  perPage: number;
182
+ hasMore: boolean;
183
+ }>;
184
+ public async getTraces(args: {
185
+ name?: string;
186
+ scope?: string;
187
+ page: number;
188
+ perPage?: number;
170
189
  attributes?: Record<string, string>;
171
190
  filters?: Record<string, any>;
172
191
  fromDate?: Date;
173
192
  toDate?: Date;
174
- }): Promise<any[]> {
175
- let idx = 1;
176
- const limit = perPage;
177
- const offset = page * perPage;
178
-
179
- const args: (string | number)[] = [];
180
-
193
+ returnPaginationResults?: boolean;
194
+ }): Promise<
195
+ | any[]
196
+ | {
197
+ traces: any[];
198
+ total: number;
199
+ page: number;
200
+ perPage: number;
201
+ hasMore: boolean;
202
+ }
203
+ > {
204
+ const {
205
+ name,
206
+ scope,
207
+ page,
208
+ perPage: perPageInput,
209
+ attributes,
210
+ filters,
211
+ fromDate,
212
+ toDate,
213
+ returnPaginationResults,
214
+ } = args;
215
+
216
+ const perPage = perPageInput !== undefined ? perPageInput : 100; // Default perPage
217
+ const currentOffset = page * perPage;
218
+
219
+ const queryParams: any[] = [];
181
220
  const conditions: string[] = [];
221
+ let paramIndex = 1;
222
+
182
223
  if (name) {
183
- conditions.push(`name LIKE CONCAT(\$${idx++}, '%')`);
224
+ conditions.push(`name LIKE $${paramIndex++}`);
225
+ queryParams.push(`${name}%`); // Add wildcard for LIKE
184
226
  }
185
227
  if (scope) {
186
- conditions.push(`scope = \$${idx++}`);
228
+ conditions.push(`scope = $${paramIndex++}`);
229
+ queryParams.push(scope);
187
230
  }
188
231
  if (attributes) {
189
- Object.keys(attributes).forEach(key => {
232
+ Object.entries(attributes).forEach(([key, value]) => {
190
233
  const parsedKey = parseSqlIdentifier(key, 'attribute key');
191
- conditions.push(`attributes->>'${parsedKey}' = \$${idx++}`);
234
+ conditions.push(`attributes->>'${parsedKey}' = $${paramIndex++}`);
235
+ queryParams.push(value);
192
236
  });
193
237
  }
194
-
195
238
  if (filters) {
196
- Object.entries(filters).forEach(([key]) => {
239
+ Object.entries(filters).forEach(([key, value]) => {
197
240
  const parsedKey = parseSqlIdentifier(key, 'filter key');
198
- conditions.push(`${parsedKey} = \$${idx++}`);
241
+ conditions.push(`"${parsedKey}" = $${paramIndex++}`); // Ensure filter keys are quoted if they are column names
242
+ queryParams.push(value);
199
243
  });
200
244
  }
201
-
202
245
  if (fromDate) {
203
- conditions.push(`createdAt >= \$${idx++}`);
246
+ conditions.push(`"createdAt" >= $${paramIndex++}`);
247
+ queryParams.push(fromDate);
204
248
  }
205
-
206
249
  if (toDate) {
207
- conditions.push(`createdAt <= \$${idx++}`);
250
+ conditions.push(`"createdAt" <= $${paramIndex++}`);
251
+ queryParams.push(toDate);
208
252
  }
209
253
 
210
254
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
211
255
 
212
- if (name) {
213
- args.push(name);
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
- }
231
-
232
- if (fromDate) {
233
- args.push(fromDate.toISOString());
234
- }
256
+ // Get total count
257
+ const countQuery = `SELECT COUNT(*) FROM ${this.getTableName(TABLE_TRACES)} ${whereClause}`;
258
+ const countResult = await this.db.one(countQuery, queryParams);
259
+ const total = parseInt(countResult.count, 10);
235
260
 
236
- if (toDate) {
237
- args.push(toDate.toISOString());
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
- );
259
-
260
- if (!result) {
261
+ if (total === 0 && returnPaginationResults) {
262
+ return {
263
+ traces: [],
264
+ total: 0,
265
+ page,
266
+ perPage,
267
+ hasMore: false,
268
+ };
269
+ } else if (total === 0) {
261
270
  return [];
262
271
  }
263
272
 
264
- return result.map(row => ({
273
+ const dataQuery = `SELECT * FROM ${this.getTableName(TABLE_TRACES)} ${whereClause} ORDER BY "createdAt" DESC LIMIT $${paramIndex++} OFFSET $${paramIndex++}`;
274
+ const finalQueryParams = [...queryParams, perPage, currentOffset];
275
+
276
+ const rows = await this.db.manyOrNone<any>(dataQuery, finalQueryParams);
277
+ const traces = rows.map(row => ({
265
278
  id: row.id,
266
279
  parentSpanId: row.parentSpanId,
267
280
  traceId: row.traceId,
@@ -276,7 +289,19 @@ export class PostgresStore extends MastraStorage {
276
289
  endTime: row.endTime,
277
290
  other: row.other,
278
291
  createdAt: row.createdAt,
279
- })) as any;
292
+ }));
293
+
294
+ if (returnPaginationResults) {
295
+ return {
296
+ traces,
297
+ total,
298
+ page,
299
+ perPage,
300
+ hasMore: currentOffset + traces.length < total,
301
+ };
302
+ } else {
303
+ return traces;
304
+ }
280
305
  }
281
306
 
282
307
  private async setupSchema() {
@@ -466,30 +491,82 @@ export class PostgresStore extends MastraStorage {
466
491
  }
467
492
  }
468
493
 
469
- async getThreadsByResourceId({ resourceId }: { resourceId: string }): Promise<StorageThreadType[]> {
494
+ public async getThreadsByResourceId(args: { resourceId: string }): Promise<StorageThreadType[]>;
495
+ public async getThreadsByResourceId(args: { resourceId: string; page: number; perPage?: number }): Promise<{
496
+ threads: StorageThreadType[];
497
+ total: number;
498
+ page: number;
499
+ perPage: number;
500
+ hasMore: boolean;
501
+ }>;
502
+ public async getThreadsByResourceId(args: { resourceId: string; page?: number; perPage?: number }): Promise<
503
+ | StorageThreadType[]
504
+ | {
505
+ threads: StorageThreadType[];
506
+ total: number;
507
+ page: number;
508
+ perPage: number;
509
+ hasMore: boolean;
510
+ }
511
+ > {
512
+ const { resourceId, page, perPage: perPageInput } = args;
513
+
470
514
  try {
471
- const threads = await this.db.manyOrNone<StorageThreadType>(
472
- `SELECT
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
- );
515
+ const baseQuery = `FROM ${this.getTableName(TABLE_THREADS)} WHERE "resourceId" = $1`;
516
+ const queryParams: any[] = [resourceId];
517
+
518
+ if (page !== undefined) {
519
+ const perPage = perPageInput !== undefined ? perPageInput : 100;
520
+ const currentOffset = page * perPage;
521
+
522
+ const countQuery = `SELECT COUNT(*) ${baseQuery}`;
523
+ const countResult = await this.db.one(countQuery, queryParams);
524
+ const total = parseInt(countResult.count, 10);
525
+
526
+ if (total === 0) {
527
+ return {
528
+ threads: [],
529
+ total: 0,
530
+ page,
531
+ perPage,
532
+ hasMore: false,
533
+ };
534
+ }
483
535
 
484
- return threads.map(thread => ({
485
- ...thread,
486
- metadata: typeof thread.metadata === 'string' ? JSON.parse(thread.metadata) : thread.metadata,
487
- createdAt: thread.createdAt,
488
- updatedAt: thread.updatedAt,
489
- }));
536
+ const dataQuery = `SELECT id, "resourceId", title, metadata, "createdAt", "updatedAt" ${baseQuery} ORDER BY "createdAt" DESC LIMIT $2 OFFSET $3`;
537
+ const rows = await this.db.manyOrNone(dataQuery, [...queryParams, perPage, currentOffset]);
538
+
539
+ const threads = (rows || []).map(thread => ({
540
+ ...thread,
541
+ metadata: typeof thread.metadata === 'string' ? JSON.parse(thread.metadata) : thread.metadata,
542
+ createdAt: thread.createdAt, // Assuming already Date objects or ISO strings
543
+ updatedAt: thread.updatedAt,
544
+ }));
545
+
546
+ return {
547
+ threads,
548
+ total,
549
+ page,
550
+ perPage,
551
+ hasMore: currentOffset + threads.length < total,
552
+ };
553
+ } else {
554
+ // Non-paginated path
555
+ const dataQuery = `SELECT id, "resourceId", title, metadata, "createdAt", "updatedAt" ${baseQuery} ORDER BY "createdAt" DESC`;
556
+ const rows = await this.db.manyOrNone(dataQuery, queryParams);
557
+ return (rows || []).map(thread => ({
558
+ ...thread,
559
+ metadata: typeof thread.metadata === 'string' ? JSON.parse(thread.metadata) : thread.metadata,
560
+ createdAt: thread.createdAt,
561
+ updatedAt: thread.updatedAt,
562
+ }));
563
+ }
490
564
  } catch (error) {
491
- console.error(`Error getting threads for resource ${resourceId}:`, error);
492
- throw error;
565
+ this.logger.error(`Error getting threads for resource ${resourceId}:`, error);
566
+ if (page !== undefined) {
567
+ return { threads: [], total: 0, page, perPage: perPageInput || 100, hasMore: false };
568
+ }
569
+ return [];
493
570
  }
494
571
  }
495
572
 
@@ -586,95 +663,203 @@ export class PostgresStore extends MastraStorage {
586
663
  }
587
664
  }
588
665
 
589
- async getMessages<T = unknown>({ threadId, selectBy }: StorageGetMessagesArg): Promise<T[]> {
590
- try {
591
- const messages: any[] = [];
592
- const limit = typeof selectBy?.last === `number` ? selectBy.last : 40;
593
- const include = selectBy?.include || [];
594
-
595
- if (include.length) {
596
- const includeResult = await this.db.manyOrNone(
597
- `
598
- WITH ordered_messages AS (
599
- SELECT
600
- *,
601
- ROW_NUMBER() OVER (ORDER BY "createdAt" DESC) as row_num
602
- FROM ${this.getTableName(TABLE_MESSAGES)}
603
- WHERE thread_id = $1
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)
623
- )
624
- )
625
- ORDER BY m."createdAt" DESC
626
- `,
627
- [
628
- threadId,
629
- include.map(i => i.id),
630
- Math.max(...include.map(i => i.withPreviousMessages || 0)),
631
- Math.max(...include.map(i => i.withNextMessages || 0)),
632
- ],
633
- );
634
-
635
- messages.push(...includeResult);
666
+ public async getMessages(args: StorageGetMessagesArg & { format?: 'v1' }): Promise<MastraMessageV1[]>;
667
+ public async getMessages(args: StorageGetMessagesArg & { format: 'v2' }): Promise<MastraMessageV2[]>;
668
+ public async getMessages(
669
+ args: StorageGetMessagesArg & {
670
+ format?: 'v1' | 'v2';
671
+ page: number;
672
+ perPage?: number;
673
+ fromDate?: Date;
674
+ toDate?: Date;
675
+ },
676
+ ): Promise<{
677
+ messages: MastraMessageV1[] | MastraMessageV2[];
678
+ total: number;
679
+ page: number;
680
+ perPage: number;
681
+ hasMore: boolean;
682
+ }>;
683
+ public async getMessages(
684
+ args: StorageGetMessagesArg & {
685
+ format?: 'v1' | 'v2';
686
+ page?: number;
687
+ perPage?: number;
688
+ fromDate?: Date;
689
+ toDate?: Date;
690
+ },
691
+ ): Promise<
692
+ | MastraMessageV1[]
693
+ | MastraMessageV2[]
694
+ | {
695
+ messages: MastraMessageV1[] | MastraMessageV2[];
696
+ total: number;
697
+ page: number;
698
+ perPage: number;
699
+ hasMore: boolean;
636
700
  }
701
+ > {
702
+ const { threadId, format, page, perPage: perPageInput, fromDate, toDate, selectBy } = args;
637
703
 
638
- // Then get the remaining messages, excluding the ids we just fetched
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
- );
704
+ const selectStatement = `SELECT id, content, role, type, "createdAt", thread_id AS "threadId"`;
705
+ const orderByStatement = `ORDER BY "createdAt" DESC`;
656
706
 
657
- messages.push(...result);
707
+ try {
708
+ if (page !== undefined) {
709
+ const perPage = perPageInput !== undefined ? perPageInput : 40;
710
+ const currentOffset = page * perPage;
658
711
 
659
- // Sort all messages by creation date
660
- messages.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
712
+ const conditions: string[] = [`thread_id = $1`];
713
+ const queryParams: any[] = [threadId];
714
+ let paramIndex = 2;
661
715
 
662
- // Parse message content
663
- messages.forEach(message => {
664
- if (typeof message.content === 'string') {
665
- try {
666
- message.content = JSON.parse(message.content);
667
- } catch {
668
- // If parsing fails, leave as string
716
+ if (fromDate) {
717
+ conditions.push(`"createdAt" >= $${paramIndex++}`);
718
+ queryParams.push(fromDate);
719
+ }
720
+ if (toDate) {
721
+ conditions.push(`"createdAt" <= $${paramIndex++}`);
722
+ queryParams.push(toDate);
723
+ }
724
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
725
+
726
+ const countQuery = `SELECT COUNT(*) FROM ${this.getTableName(TABLE_MESSAGES)} ${whereClause}`;
727
+ const countResult = await this.db.one(countQuery, queryParams);
728
+ const total = parseInt(countResult.count, 10);
729
+
730
+ if (total === 0) {
731
+ return {
732
+ messages: [],
733
+ total: 0,
734
+ page,
735
+ perPage,
736
+ hasMore: false,
737
+ };
738
+ }
739
+
740
+ const dataQuery = `${selectStatement} FROM ${this.getTableName(TABLE_MESSAGES)} ${whereClause} ${orderByStatement} LIMIT $${paramIndex++} OFFSET $${paramIndex++}`;
741
+ const rows = await this.db.manyOrNone(dataQuery, [...queryParams, perPage, currentOffset]);
742
+
743
+ const fetchedMessages = (rows || []).map(message => {
744
+ if (typeof message.content === 'string') {
745
+ try {
746
+ message.content = JSON.parse(message.content);
747
+ } catch {
748
+ /* ignore */
749
+ }
750
+ }
751
+ if (message.type === 'v2') delete message.type;
752
+ return message as MastraMessageV1;
753
+ });
754
+
755
+ const messagesToReturn =
756
+ format === 'v2'
757
+ ? fetchedMessages.map(
758
+ m =>
759
+ ({
760
+ ...m,
761
+ content: m.content || { format: 2, parts: [{ type: 'text', text: '' }] },
762
+ }) as MastraMessageV2,
763
+ )
764
+ : fetchedMessages;
765
+
766
+ return {
767
+ messages: messagesToReturn,
768
+ total,
769
+ page,
770
+ perPage,
771
+ hasMore: currentOffset + fetchedMessages.length < total,
772
+ };
773
+ } else {
774
+ // Non-paginated path: Handle selectBy.include or selectBy.last
775
+ let rows: any[] = [];
776
+ const include = selectBy?.include || [];
777
+
778
+ if (include.length) {
779
+ rows = await this.db.manyOrNone(
780
+ `
781
+ WITH ordered_messages AS (
782
+ SELECT
783
+ *,
784
+ ROW_NUMBER() OVER (${orderByStatement}) as row_num
785
+ FROM ${this.getTableName(TABLE_MESSAGES)}
786
+ WHERE thread_id = $1
787
+ )
788
+ SELECT
789
+ m.id,
790
+ m.content,
791
+ m.role,
792
+ m.type,
793
+ m."createdAt",
794
+ m.thread_id AS "threadId"
795
+ FROM ordered_messages m
796
+ WHERE m.id = ANY($2)
797
+ OR EXISTS (
798
+ SELECT 1 FROM ordered_messages target
799
+ WHERE target.id = ANY($2)
800
+ AND (
801
+ -- Get previous messages based on the max withPreviousMessages
802
+ (m.row_num <= target.row_num + $3 AND m.row_num > target.row_num)
803
+ OR
804
+ -- Get next messages based on the max withNextMessages
805
+ (m.row_num >= target.row_num - $4 AND m.row_num < target.row_num)
806
+ )
807
+ )
808
+ ORDER BY m."createdAt" ASC
809
+ `, // Keep ASC for final sorting after fetching context
810
+ [
811
+ threadId,
812
+ include.map(i => i.id),
813
+ Math.max(0, ...include.map(i => i.withPreviousMessages || 0)), // Ensure non-negative
814
+ Math.max(0, ...include.map(i => i.withNextMessages || 0)), // Ensure non-negative
815
+ ],
816
+ );
817
+ } else {
818
+ const limit = typeof selectBy?.last === `number` ? selectBy.last : 40;
819
+ if (limit === 0 && selectBy?.last !== false) {
820
+ // if last is explicitly false, we fetch all
821
+ // Do nothing, rows will be empty, and we return empty array later.
822
+ } else {
823
+ let query = `${selectStatement} FROM ${this.getTableName(TABLE_MESSAGES)} WHERE thread_id = $1 ${orderByStatement}`;
824
+ const queryParams: any[] = [threadId];
825
+ if (limit !== undefined && selectBy?.last !== false) {
826
+ query += ` LIMIT $2`;
827
+ queryParams.push(limit);
828
+ }
829
+ rows = await this.db.manyOrNone(query, queryParams);
669
830
  }
670
831
  }
671
- if (message.type === `v2`) delete message.type;
672
- });
673
832
 
674
- return messages as T[];
833
+ const fetchedMessages = (rows || []).map(message => {
834
+ if (typeof message.content === 'string') {
835
+ try {
836
+ message.content = JSON.parse(message.content);
837
+ } catch {
838
+ /* ignore */
839
+ }
840
+ }
841
+ if (message.type === 'v2') delete message.type;
842
+ return message as MastraMessageV1;
843
+ });
844
+
845
+ // Sort all messages by creation date
846
+ const sortedMessages = fetchedMessages.sort(
847
+ (a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(),
848
+ );
849
+
850
+ return format === 'v2'
851
+ ? sortedMessages.map(
852
+ m =>
853
+ ({ ...m, content: m.content || { format: 2, parts: [{ type: 'text', text: '' }] } }) as MastraMessageV2,
854
+ )
855
+ : sortedMessages;
856
+ }
675
857
  } catch (error) {
676
- console.error('Error getting messages:', error);
677
- throw error;
858
+ this.logger.error('Error getting messages:', error);
859
+ if (page !== undefined) {
860
+ return { messages: [], total: 0, page, perPage: perPageInput || 40, hasMore: false };
861
+ }
862
+ return [];
678
863
  }
679
864
  }
680
865
 
@@ -948,4 +1133,99 @@ export class PostgresStore extends MastraStorage {
948
1133
  async close(): Promise<void> {
949
1134
  this.pgp.end();
950
1135
  }
1136
+
1137
+ async getEvals(options?: {
1138
+ agentName?: string;
1139
+ type?: 'test' | 'live';
1140
+ page?: number;
1141
+ perPage?: number;
1142
+ limit?: number;
1143
+ offset?: number;
1144
+ fromDate?: Date;
1145
+ toDate?: Date;
1146
+ }): Promise<{
1147
+ evals: EvalRow[];
1148
+ total: number;
1149
+ page?: number;
1150
+ perPage?: number;
1151
+ hasMore?: boolean;
1152
+ }> {
1153
+ const { agentName, type, page, perPage, limit, offset, fromDate, toDate } = options || {};
1154
+
1155
+ const conditions: string[] = [];
1156
+ const queryParams: any[] = [];
1157
+ let paramIndex = 1;
1158
+
1159
+ if (agentName) {
1160
+ conditions.push(`agent_name = $${paramIndex++}`);
1161
+ queryParams.push(agentName);
1162
+ }
1163
+
1164
+ if (type === 'test') {
1165
+ conditions.push(`(test_info IS NOT NULL AND test_info->>'testPath' IS NOT NULL)`);
1166
+ } else if (type === 'live') {
1167
+ conditions.push(`(test_info IS NULL OR test_info->>'testPath' IS NULL)`);
1168
+ }
1169
+
1170
+ if (fromDate) {
1171
+ conditions.push(`created_at >= $${paramIndex++}`);
1172
+ queryParams.push(fromDate);
1173
+ }
1174
+
1175
+ if (toDate) {
1176
+ conditions.push(`created_at <= $${paramIndex++}`);
1177
+ queryParams.push(toDate);
1178
+ }
1179
+
1180
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
1181
+
1182
+ const countQuery = `SELECT COUNT(*) FROM ${this.getTableName(TABLE_EVALS)} ${whereClause}`;
1183
+ const countResult = await this.db.one(countQuery, queryParams);
1184
+ const total = parseInt(countResult.count, 10);
1185
+
1186
+ let currentLimit: number;
1187
+ let currentOffset: number;
1188
+ let currentPage: number | undefined = page;
1189
+ let currentPerPage: number | undefined = perPage;
1190
+ let hasMore = false;
1191
+
1192
+ if (limit !== undefined && offset !== undefined) {
1193
+ currentLimit = limit;
1194
+ currentOffset = offset;
1195
+ currentPage = undefined;
1196
+ currentPerPage = undefined;
1197
+ hasMore = currentOffset + currentLimit < total;
1198
+ } else if (page !== undefined && perPage !== undefined) {
1199
+ currentLimit = perPage;
1200
+ currentOffset = page * perPage;
1201
+ hasMore = currentOffset + currentLimit < total;
1202
+ } else {
1203
+ currentLimit = perPage || 100;
1204
+ currentOffset = (page || 0) * currentLimit;
1205
+ if (page === undefined) currentPage = 0;
1206
+ if (currentPerPage === undefined) currentPerPage = currentLimit;
1207
+ hasMore = currentOffset + currentLimit < total;
1208
+ }
1209
+
1210
+ if (total === 0) {
1211
+ return {
1212
+ evals: [],
1213
+ total: 0,
1214
+ page: currentPage,
1215
+ perPage: currentPerPage,
1216
+ hasMore: false,
1217
+ };
1218
+ }
1219
+
1220
+ const dataQuery = `SELECT * FROM ${this.getTableName(TABLE_EVALS)} ${whereClause} ORDER BY created_at DESC LIMIT $${paramIndex++} OFFSET $${paramIndex++}`;
1221
+ const rows = await this.db.manyOrNone(dataQuery, [...queryParams, currentLimit, currentOffset]);
1222
+
1223
+ return {
1224
+ evals: rows?.map(row => this.transformEvalRow(row)) ?? [],
1225
+ total,
1226
+ page: currentPage,
1227
+ perPage: currentPerPage,
1228
+ hasMore,
1229
+ };
1230
+ }
951
1231
  }