@mastra/pg 0.11.1-alpha.0 → 0.11.1-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,5 +1,6 @@
1
1
  import { MessageList } from '@mastra/core/agent';
2
2
  import type { MastraMessageContentV2, MastraMessageV2 } from '@mastra/core/agent';
3
+ import { ErrorCategory, ErrorDomain, MastraError } from '@mastra/core/error';
3
4
  import type { MetricResult } from '@mastra/core/eval';
4
5
  import type { MastraMessageV1, StorageThreadType } from '@mastra/core/memory';
5
6
  import {
@@ -7,6 +8,7 @@ import {
7
8
  TABLE_MESSAGES,
8
9
  TABLE_THREADS,
9
10
  TABLE_TRACES,
11
+ TABLE_RESOURCES,
10
12
  TABLE_WORKFLOW_SNAPSHOT,
11
13
  TABLE_EVALS,
12
14
  } from '@mastra/core/storage';
@@ -15,6 +17,7 @@ import type {
15
17
  PaginationInfo,
16
18
  StorageColumn,
17
19
  StorageGetMessagesArg,
20
+ StorageResourceType,
18
21
  TABLE_NAMES,
19
22
  WorkflowRun,
20
23
  WorkflowRuns,
@@ -50,48 +53,61 @@ export class PostgresStore extends MastraStorage {
50
53
 
51
54
  constructor(config: PostgresConfig) {
52
55
  // Validation: connectionString or host/database/user/password must not be empty
53
- if ('connectionString' in config) {
54
- if (
55
- !config.connectionString ||
56
- typeof config.connectionString !== 'string' ||
57
- config.connectionString.trim() === ''
58
- ) {
59
- throw new Error(
60
- 'PostgresStore: connectionString must be provided and cannot be empty. Passing an empty string may cause fallback to local Postgres defaults.',
61
- );
62
- }
63
- } else {
64
- const required = ['host', 'database', 'user', 'password'];
65
- for (const key of required) {
66
- if (!(key in config) || typeof (config as any)[key] !== 'string' || (config as any)[key].trim() === '') {
56
+ try {
57
+ if ('connectionString' in config) {
58
+ if (
59
+ !config.connectionString ||
60
+ typeof config.connectionString !== 'string' ||
61
+ config.connectionString.trim() === ''
62
+ ) {
67
63
  throw new Error(
68
- `PostgresStore: ${key} must be provided and cannot be empty. Passing an empty string may cause fallback to local Postgres defaults.`,
64
+ 'PostgresStore: connectionString must be provided and cannot be empty. Passing an empty string may cause fallback to local Postgres defaults.',
69
65
  );
70
66
  }
67
+ } else {
68
+ const required = ['host', 'database', 'user', 'password'];
69
+ for (const key of required) {
70
+ if (!(key in config) || typeof (config as any)[key] !== 'string' || (config as any)[key].trim() === '') {
71
+ throw new Error(
72
+ `PostgresStore: ${key} must be provided and cannot be empty. Passing an empty string may cause fallback to local Postgres defaults.`,
73
+ );
74
+ }
75
+ }
71
76
  }
77
+ super({ name: 'PostgresStore' });
78
+ this.pgp = pgPromise();
79
+ this.schema = config.schemaName;
80
+ this.db = this.pgp(
81
+ `connectionString` in config
82
+ ? { connectionString: config.connectionString }
83
+ : {
84
+ host: config.host,
85
+ port: config.port,
86
+ database: config.database,
87
+ user: config.user,
88
+ password: config.password,
89
+ ssl: config.ssl,
90
+ },
91
+ );
92
+ } catch (e) {
93
+ throw new MastraError(
94
+ {
95
+ id: 'MASTRA_STORAGE_PG_STORE_INITIALIZATION_FAILED',
96
+ domain: ErrorDomain.STORAGE,
97
+ category: ErrorCategory.USER,
98
+ },
99
+ e,
100
+ );
72
101
  }
73
- super({ name: 'PostgresStore' });
74
- this.pgp = pgPromise();
75
- this.schema = config.schemaName;
76
- this.db = this.pgp(
77
- `connectionString` in config
78
- ? { connectionString: config.connectionString }
79
- : {
80
- host: config.host,
81
- port: config.port,
82
- database: config.database,
83
- user: config.user,
84
- password: config.password,
85
- ssl: config.ssl,
86
- },
87
- );
88
102
  }
89
103
 
90
104
  public get supports(): {
91
105
  selectByIncludeResourceScope: boolean;
106
+ resourceWorkingMemory: boolean;
92
107
  } {
93
108
  return {
94
109
  selectByIncludeResourceScope: true,
110
+ resourceWorkingMemory: true,
95
111
  };
96
112
  }
97
113
 
@@ -163,9 +179,19 @@ export class PostgresStore extends MastraStorage {
163
179
  }
164
180
  await this.db.query('COMMIT');
165
181
  } catch (error) {
166
- console.error(`Error inserting into ${tableName}:`, error);
167
182
  await this.db.query('ROLLBACK');
168
- throw error;
183
+ throw new MastraError(
184
+ {
185
+ id: 'MASTRA_STORAGE_PG_STORE_BATCH_INSERT_FAILED',
186
+ domain: ErrorDomain.STORAGE,
187
+ category: ErrorCategory.THIRD_PARTY,
188
+ details: {
189
+ tableName,
190
+ numberOfRecords: records.length,
191
+ },
192
+ },
193
+ error,
194
+ );
169
195
  }
170
196
  }
171
197
 
@@ -250,8 +276,24 @@ export class PostgresStore extends MastraStorage {
250
276
 
251
277
  // Get total count
252
278
  const countQuery = `SELECT COUNT(*) FROM ${this.getTableName(TABLE_TRACES)} ${whereClause}`;
253
- const countResult = await this.db.one(countQuery, queryParams);
254
- const total = parseInt(countResult.count, 10);
279
+ let total = 0;
280
+ try {
281
+ const countResult = await this.db.one(countQuery, queryParams);
282
+ total = parseInt(countResult.count, 10);
283
+ } catch (error) {
284
+ throw new MastraError(
285
+ {
286
+ id: 'MASTRA_STORAGE_PG_STORE_GET_TRACES_PAGINATED_FAILED_TO_RETRIEVE_TOTAL_COUNT',
287
+ domain: ErrorDomain.STORAGE,
288
+ category: ErrorCategory.THIRD_PARTY,
289
+ details: {
290
+ name: args.name ?? '',
291
+ scope: args.scope ?? '',
292
+ },
293
+ },
294
+ error,
295
+ );
296
+ }
255
297
 
256
298
  if (total === 0) {
257
299
  return {
@@ -268,31 +310,46 @@ export class PostgresStore extends MastraStorage {
268
310
  )} ${whereClause} ORDER BY "createdAt" DESC LIMIT $${paramIndex++} OFFSET $${paramIndex++}`;
269
311
  const finalQueryParams = [...queryParams, perPage, currentOffset];
270
312
 
271
- const rows = await this.db.manyOrNone<any>(dataQuery, finalQueryParams);
272
- const traces = rows.map(row => ({
273
- id: row.id,
274
- parentSpanId: row.parentSpanId,
275
- traceId: row.traceId,
276
- name: row.name,
277
- scope: row.scope,
278
- kind: row.kind,
279
- status: row.status,
280
- events: row.events,
281
- links: row.links,
282
- attributes: row.attributes,
283
- startTime: row.startTime,
284
- endTime: row.endTime,
285
- other: row.other,
286
- createdAt: row.createdAt,
287
- }));
313
+ try {
314
+ const rows = await this.db.manyOrNone<any>(dataQuery, finalQueryParams);
315
+ const traces = rows.map(row => ({
316
+ id: row.id,
317
+ parentSpanId: row.parentSpanId,
318
+ traceId: row.traceId,
319
+ name: row.name,
320
+ scope: row.scope,
321
+ kind: row.kind,
322
+ status: row.status,
323
+ events: row.events,
324
+ links: row.links,
325
+ attributes: row.attributes,
326
+ startTime: row.startTime,
327
+ endTime: row.endTime,
328
+ other: row.other,
329
+ createdAt: row.createdAt,
330
+ }));
288
331
 
289
- return {
290
- traces,
291
- total,
292
- page,
293
- perPage,
294
- hasMore: currentOffset + traces.length < total,
295
- };
332
+ return {
333
+ traces,
334
+ total,
335
+ page,
336
+ perPage,
337
+ hasMore: currentOffset + traces.length < total,
338
+ };
339
+ } catch (error) {
340
+ throw new MastraError(
341
+ {
342
+ id: 'MASTRA_STORAGE_PG_STORE_GET_TRACES_PAGINATED_FAILED_TO_RETRIEVE_TRACES',
343
+ domain: ErrorDomain.STORAGE,
344
+ category: ErrorCategory.THIRD_PARTY,
345
+ details: {
346
+ name: args.name ?? '',
347
+ scope: args.scope ?? '',
348
+ },
349
+ },
350
+ error,
351
+ );
352
+ }
296
353
  }
297
354
 
298
355
  private async setupSchema() {
@@ -390,8 +447,17 @@ export class PostgresStore extends MastraStorage {
390
447
 
391
448
  await this.db.none(sql);
392
449
  } catch (error) {
393
- console.error(`Error creating table ${tableName}:`, error);
394
- throw error;
450
+ throw new MastraError(
451
+ {
452
+ id: 'MASTRA_STORAGE_PG_STORE_CREATE_TABLE_FAILED',
453
+ domain: ErrorDomain.STORAGE,
454
+ category: ErrorCategory.THIRD_PARTY,
455
+ details: {
456
+ tableName,
457
+ },
458
+ },
459
+ error,
460
+ );
395
461
  }
396
462
  }
397
463
 
@@ -439,10 +505,17 @@ export class PostgresStore extends MastraStorage {
439
505
  }
440
506
  }
441
507
  } catch (error) {
442
- this.logger?.error?.(
443
- `Error altering table ${tableName}: ${error instanceof Error ? error.message : String(error)}`,
508
+ throw new MastraError(
509
+ {
510
+ id: 'MASTRA_STORAGE_PG_STORE_ALTER_TABLE_FAILED',
511
+ domain: ErrorDomain.STORAGE,
512
+ category: ErrorCategory.THIRD_PARTY,
513
+ details: {
514
+ tableName,
515
+ },
516
+ },
517
+ error,
444
518
  );
445
- throw new Error(`Failed to alter table ${tableName}: ${error}`);
446
519
  }
447
520
  }
448
521
 
@@ -450,8 +523,17 @@ export class PostgresStore extends MastraStorage {
450
523
  try {
451
524
  await this.db.none(`TRUNCATE TABLE ${this.getTableName(tableName)} CASCADE`);
452
525
  } catch (error) {
453
- console.error(`Error clearing table ${tableName}:`, error);
454
- throw error;
526
+ throw new MastraError(
527
+ {
528
+ id: 'MASTRA_STORAGE_PG_STORE_CLEAR_TABLE_FAILED',
529
+ domain: ErrorDomain.STORAGE,
530
+ category: ErrorCategory.THIRD_PARTY,
531
+ details: {
532
+ tableName,
533
+ },
534
+ },
535
+ error,
536
+ );
455
537
  }
456
538
  }
457
539
 
@@ -466,8 +548,17 @@ export class PostgresStore extends MastraStorage {
466
548
  values,
467
549
  );
468
550
  } catch (error) {
469
- console.error(`Error inserting into ${tableName}:`, error);
470
- throw error;
551
+ throw new MastraError(
552
+ {
553
+ id: 'MASTRA_STORAGE_PG_STORE_INSERT_FAILED',
554
+ domain: ErrorDomain.STORAGE,
555
+ category: ErrorCategory.THIRD_PARTY,
556
+ details: {
557
+ tableName,
558
+ },
559
+ },
560
+ error,
561
+ );
471
562
  }
472
563
  }
473
564
 
@@ -497,8 +588,17 @@ export class PostgresStore extends MastraStorage {
497
588
 
498
589
  return result;
499
590
  } catch (error) {
500
- console.error(`Error loading from ${tableName}:`, error);
501
- throw error;
591
+ throw new MastraError(
592
+ {
593
+ id: 'MASTRA_STORAGE_PG_STORE_LOAD_FAILED',
594
+ domain: ErrorDomain.STORAGE,
595
+ category: ErrorCategory.THIRD_PARTY,
596
+ details: {
597
+ tableName,
598
+ },
599
+ },
600
+ error,
601
+ );
502
602
  }
503
603
  }
504
604
 
@@ -528,8 +628,17 @@ export class PostgresStore extends MastraStorage {
528
628
  updatedAt: thread.updatedAt,
529
629
  };
530
630
  } catch (error) {
531
- console.error(`Error getting thread ${threadId}:`, error);
532
- throw error;
631
+ throw new MastraError(
632
+ {
633
+ id: 'MASTRA_STORAGE_PG_STORE_GET_THREAD_BY_ID_FAILED',
634
+ domain: ErrorDomain.STORAGE,
635
+ category: ErrorCategory.THIRD_PARTY,
636
+ details: {
637
+ threadId,
638
+ },
639
+ },
640
+ error,
641
+ );
533
642
  }
534
643
  }
535
644
 
@@ -601,7 +710,20 @@ export class PostgresStore extends MastraStorage {
601
710
  hasMore: currentOffset + threads.length < total,
602
711
  };
603
712
  } catch (error) {
604
- this.logger.error(`Error getting threads for resource ${resourceId}:`, error);
713
+ const mastraError = new MastraError(
714
+ {
715
+ id: 'MASTRA_STORAGE_PG_STORE_GET_THREADS_BY_RESOURCE_ID_PAGINATED_FAILED',
716
+ domain: ErrorDomain.STORAGE,
717
+ category: ErrorCategory.THIRD_PARTY,
718
+ details: {
719
+ resourceId,
720
+ page,
721
+ },
722
+ },
723
+ error,
724
+ );
725
+ this.logger?.error?.(mastraError.toString());
726
+ this.logger?.trackException(mastraError);
605
727
  return { threads: [], total: 0, page, perPage: perPageInput || 100, hasMore: false };
606
728
  }
607
729
  }
@@ -635,8 +757,17 @@ export class PostgresStore extends MastraStorage {
635
757
 
636
758
  return thread;
637
759
  } catch (error) {
638
- console.error('Error saving thread:', error);
639
- throw error;
760
+ throw new MastraError(
761
+ {
762
+ id: 'MASTRA_STORAGE_PG_STORE_SAVE_THREAD_FAILED',
763
+ domain: ErrorDomain.STORAGE,
764
+ category: ErrorCategory.THIRD_PARTY,
765
+ details: {
766
+ threadId: thread.id,
767
+ },
768
+ },
769
+ error,
770
+ );
640
771
  }
641
772
  }
642
773
 
@@ -649,24 +780,33 @@ export class PostgresStore extends MastraStorage {
649
780
  title: string;
650
781
  metadata: Record<string, unknown>;
651
782
  }): Promise<StorageThreadType> {
652
- try {
653
- // First get the existing thread to merge metadata
654
- const existingThread = await this.getThreadById({ threadId: id });
655
- if (!existingThread) {
656
- throw new Error(`Thread ${id} not found`);
657
- }
783
+ // First get the existing thread to merge metadata
784
+ const existingThread = await this.getThreadById({ threadId: id });
785
+ if (!existingThread) {
786
+ throw new MastraError({
787
+ id: 'MASTRA_STORAGE_PG_STORE_UPDATE_THREAD_FAILED',
788
+ domain: ErrorDomain.STORAGE,
789
+ category: ErrorCategory.USER,
790
+ text: `Thread ${id} not found`,
791
+ details: {
792
+ threadId: id,
793
+ title,
794
+ },
795
+ });
796
+ }
658
797
 
659
- // Merge the existing metadata with the new metadata
660
- const mergedMetadata = {
661
- ...existingThread.metadata,
662
- ...metadata,
663
- };
798
+ // Merge the existing metadata with the new metadata
799
+ const mergedMetadata = {
800
+ ...existingThread.metadata,
801
+ ...metadata,
802
+ };
664
803
 
804
+ try {
665
805
  const thread = await this.db.one<StorageThreadType>(
666
806
  `UPDATE ${this.getTableName(TABLE_THREADS)}
667
807
  SET title = $1,
668
- metadata = $2,
669
- "updatedAt" = $3
808
+ metadata = $2,
809
+ "updatedAt" = $3
670
810
  WHERE id = $4
671
811
  RETURNING *`,
672
812
  [title, mergedMetadata, new Date().toISOString(), id],
@@ -679,8 +819,18 @@ export class PostgresStore extends MastraStorage {
679
819
  updatedAt: thread.updatedAt,
680
820
  };
681
821
  } catch (error) {
682
- console.error('Error updating thread:', error);
683
- throw error;
822
+ throw new MastraError(
823
+ {
824
+ id: 'MASTRA_STORAGE_PG_STORE_UPDATE_THREAD_FAILED',
825
+ domain: ErrorDomain.STORAGE,
826
+ category: ErrorCategory.THIRD_PARTY,
827
+ details: {
828
+ threadId: id,
829
+ title,
830
+ },
831
+ },
832
+ error,
833
+ );
684
834
  }
685
835
  }
686
836
 
@@ -694,41 +844,42 @@ export class PostgresStore extends MastraStorage {
694
844
  await t.none(`DELETE FROM ${this.getTableName(TABLE_THREADS)} WHERE id = $1`, [threadId]);
695
845
  });
696
846
  } catch (error) {
697
- console.error('Error deleting thread:', error);
698
- throw error;
847
+ throw new MastraError(
848
+ {
849
+ id: 'MASTRA_STORAGE_PG_STORE_DELETE_THREAD_FAILED',
850
+ domain: ErrorDomain.STORAGE,
851
+ category: ErrorCategory.THIRD_PARTY,
852
+ details: {
853
+ threadId,
854
+ },
855
+ },
856
+ error,
857
+ );
699
858
  }
700
859
  }
701
860
 
702
- /**
703
- * @deprecated use getMessagesPaginated instead
704
- */
705
- public async getMessages(args: StorageGetMessagesArg & { format?: 'v1' }): Promise<MastraMessageV1[]>;
706
- public async getMessages(args: StorageGetMessagesArg & { format: 'v2' }): Promise<MastraMessageV2[]>;
707
- public async getMessages(
708
- args: StorageGetMessagesArg & {
709
- format?: 'v1' | 'v2';
710
- },
711
- ): Promise<MastraMessageV1[] | MastraMessageV2[]> {
712
- const { threadId, format, selectBy } = args;
713
-
714
- const selectStatement = `SELECT id, content, role, type, "createdAt", thread_id AS "threadId"`;
715
- const orderByStatement = `ORDER BY "createdAt" DESC`;
716
-
717
- try {
718
- let rows: any[] = [];
719
- const include = selectBy?.include || [];
720
-
721
- if (include.length) {
722
- const unionQueries: string[] = [];
723
- const params: any[] = [];
724
- let paramIdx = 1;
725
-
726
- for (const inc of include) {
727
- const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
728
- // if threadId is provided, use it, otherwise use threadId from args
729
- const searchId = inc.threadId || threadId;
730
- unionQueries.push(
731
- `
861
+ private async _getIncludedMessages({
862
+ threadId,
863
+ selectBy,
864
+ orderByStatement,
865
+ }: {
866
+ threadId: string;
867
+ selectBy: StorageGetMessagesArg['selectBy'];
868
+ orderByStatement: string;
869
+ }) {
870
+ const include = selectBy?.include;
871
+ if (!include) return null;
872
+
873
+ const unionQueries: string[] = [];
874
+ const params: any[] = [];
875
+ let paramIdx = 1;
876
+
877
+ for (const inc of include) {
878
+ const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
879
+ // if threadId is provided, use it, otherwise use threadId from args
880
+ const searchId = inc.threadId || threadId;
881
+ unionQueries.push(
882
+ `
732
883
  SELECT * FROM (
733
884
  WITH ordered_messages AS (
734
885
  SELECT
@@ -760,37 +911,59 @@ export class PostgresStore extends MastraStorage {
760
911
  )
761
912
  ) AS query_${paramIdx}
762
913
  `, // Keep ASC for final sorting after fetching context
763
- );
764
- params.push(searchId, id, withPreviousMessages, withNextMessages);
765
- paramIdx += 4;
766
- }
767
- const finalQuery = unionQueries.join(' UNION ALL ') + ' ORDER BY "createdAt" ASC';
768
- const includedRows = await this.db.manyOrNone(finalQuery, params);
769
- const seen = new Set<string>();
770
- const dedupedRows = includedRows.filter(row => {
771
- if (seen.has(row.id)) return false;
772
- seen.add(row.id);
773
- return true;
774
- });
775
- rows = dedupedRows;
776
- } else {
777
- const limit = typeof selectBy?.last === `number` ? selectBy.last : 40;
778
- if (limit === 0 && selectBy?.last !== false) {
779
- // if last is explicitly false, we fetch all
780
- // Do nothing, rows will be empty, and we return empty array later.
781
- } else {
782
- let query = `${selectStatement} FROM ${this.getTableName(
783
- TABLE_MESSAGES,
784
- )} WHERE thread_id = $1 ${orderByStatement}`;
785
- const queryParams: any[] = [threadId];
786
- if (limit !== undefined && selectBy?.last !== false) {
787
- query += ` LIMIT $2`;
788
- queryParams.push(limit);
789
- }
790
- rows = await this.db.manyOrNone(query, queryParams);
914
+ );
915
+ params.push(searchId, id, withPreviousMessages, withNextMessages);
916
+ paramIdx += 4;
917
+ }
918
+ const finalQuery = unionQueries.join(' UNION ALL ') + ' ORDER BY "createdAt" ASC';
919
+ const includedRows = await this.db.manyOrNone(finalQuery, params);
920
+ const seen = new Set<string>();
921
+ const dedupedRows = includedRows.filter(row => {
922
+ if (seen.has(row.id)) return false;
923
+ seen.add(row.id);
924
+ return true;
925
+ });
926
+ return dedupedRows;
927
+ }
928
+
929
+ /**
930
+ * @deprecated use getMessagesPaginated instead
931
+ */
932
+ public async getMessages(args: StorageGetMessagesArg & { format?: 'v1' }): Promise<MastraMessageV1[]>;
933
+ public async getMessages(args: StorageGetMessagesArg & { format: 'v2' }): Promise<MastraMessageV2[]>;
934
+ public async getMessages(
935
+ args: StorageGetMessagesArg & {
936
+ format?: 'v1' | 'v2';
937
+ },
938
+ ): Promise<MastraMessageV1[] | MastraMessageV2[]> {
939
+ const { threadId, format, selectBy } = args;
940
+
941
+ const selectStatement = `SELECT id, content, role, type, "createdAt", thread_id AS "threadId"`;
942
+ const orderByStatement = `ORDER BY "createdAt" DESC`;
943
+ const limit = this.resolveMessageLimit({ last: selectBy?.last, defaultLimit: 40 });
944
+
945
+ try {
946
+ let rows: any[] = [];
947
+ const include = selectBy?.include || [];
948
+
949
+ if (include?.length) {
950
+ const includeMessages = await this._getIncludedMessages({ threadId, selectBy, orderByStatement });
951
+ if (includeMessages) {
952
+ rows.push(...includeMessages);
791
953
  }
792
954
  }
793
955
 
956
+ const excludeIds = rows.map(m => m.id);
957
+ const excludeIdsParam = excludeIds.map((_, idx) => `$${idx + 2}`).join(', ');
958
+ let query = `${selectStatement} FROM ${this.getTableName(TABLE_MESSAGES)} WHERE thread_id = $1
959
+ ${excludeIds.length ? `AND id NOT IN (${excludeIdsParam})` : ''}
960
+ ${orderByStatement}
961
+ LIMIT $${excludeIds.length + 2}
962
+ `;
963
+ const queryParams: any[] = [threadId, ...excludeIds, limit];
964
+ const remainingRows = await this.db.manyOrNone(query, queryParams);
965
+ rows.push(...remainingRows);
966
+
794
967
  const fetchedMessages = (rows || []).map(message => {
795
968
  if (typeof message.content === 'string') {
796
969
  try {
@@ -815,7 +988,19 @@ export class PostgresStore extends MastraStorage {
815
988
  )
816
989
  : sortedMessages;
817
990
  } catch (error) {
818
- this.logger.error('Error getting messages:', error);
991
+ const mastraError = new MastraError(
992
+ {
993
+ id: 'MASTRA_STORAGE_PG_STORE_GET_MESSAGES_FAILED',
994
+ domain: ErrorDomain.STORAGE,
995
+ category: ErrorCategory.THIRD_PARTY,
996
+ details: {
997
+ threadId,
998
+ },
999
+ },
1000
+ error,
1001
+ );
1002
+ this.logger?.error?.(mastraError.toString());
1003
+ this.logger?.trackException(mastraError);
819
1004
  return [];
820
1005
  }
821
1006
  }
@@ -833,8 +1018,20 @@ export class PostgresStore extends MastraStorage {
833
1018
  const selectStatement = `SELECT id, content, role, type, "createdAt", thread_id AS "threadId"`;
834
1019
  const orderByStatement = `ORDER BY "createdAt" DESC`;
835
1020
 
1021
+ const messages: MastraMessageV2[] = [];
1022
+
1023
+ if (selectBy?.include?.length) {
1024
+ const includeMessages = await this._getIncludedMessages({ threadId, selectBy, orderByStatement });
1025
+ if (includeMessages) {
1026
+ messages.push(...includeMessages);
1027
+ }
1028
+ }
1029
+
836
1030
  try {
837
- const perPage = perPageInput !== undefined ? perPageInput : 40;
1031
+ const perPage =
1032
+ perPageInput !== undefined
1033
+ ? perPageInput
1034
+ : this.resolveMessageLimit({ last: selectBy?.last, defaultLimit: 40 });
838
1035
  const currentOffset = page * perPage;
839
1036
 
840
1037
  const conditions: string[] = [`thread_id = $1`];
@@ -855,7 +1052,7 @@ export class PostgresStore extends MastraStorage {
855
1052
  const countResult = await this.db.one(countQuery, queryParams);
856
1053
  const total = parseInt(countResult.count, 10);
857
1054
 
858
- if (total === 0) {
1055
+ if (total === 0 && messages.length === 0) {
859
1056
  return {
860
1057
  messages: [],
861
1058
  total: 0,
@@ -865,12 +1062,17 @@ export class PostgresStore extends MastraStorage {
865
1062
  };
866
1063
  }
867
1064
 
1065
+ const excludeIds = messages.map(m => m.id);
1066
+ const excludeIdsParam = excludeIds.map((_, idx) => `$${idx + paramIndex}`).join(', ');
1067
+ paramIndex += excludeIds.length;
1068
+
868
1069
  const dataQuery = `${selectStatement} FROM ${this.getTableName(
869
1070
  TABLE_MESSAGES,
870
- )} ${whereClause} ${orderByStatement} LIMIT $${paramIndex++} OFFSET $${paramIndex++}`;
871
- const rows = await this.db.manyOrNone(dataQuery, [...queryParams, perPage, currentOffset]);
1071
+ )} ${whereClause} ${excludeIds.length ? `AND id NOT IN (${excludeIdsParam})` : ''}${orderByStatement} LIMIT $${paramIndex++} OFFSET $${paramIndex++}`;
1072
+ const rows = await this.db.manyOrNone(dataQuery, [...queryParams, ...excludeIds, perPage, currentOffset]);
1073
+ messages.push(...(rows || []));
872
1074
 
873
- const list = new MessageList().add(rows || [], 'memory');
1075
+ const list = new MessageList().add(messages, 'memory');
874
1076
  const messagesToReturn = format === `v2` ? list.get.all.v2() : list.get.all.v1();
875
1077
 
876
1078
  return {
@@ -881,7 +1083,20 @@ export class PostgresStore extends MastraStorage {
881
1083
  hasMore: currentOffset + rows.length < total,
882
1084
  };
883
1085
  } catch (error) {
884
- this.logger.error('Error getting messages:', error);
1086
+ const mastraError = new MastraError(
1087
+ {
1088
+ id: 'MASTRA_STORAGE_PG_STORE_GET_MESSAGES_PAGINATED_FAILED',
1089
+ domain: ErrorDomain.STORAGE,
1090
+ category: ErrorCategory.THIRD_PARTY,
1091
+ details: {
1092
+ threadId,
1093
+ page,
1094
+ },
1095
+ },
1096
+ error,
1097
+ );
1098
+ this.logger?.error?.(mastraError.toString());
1099
+ this.logger?.trackException(mastraError);
885
1100
  return { messages: [], total: 0, page, perPage: perPageInput || 40, hasMore: false };
886
1101
  }
887
1102
  }
@@ -896,18 +1111,31 @@ export class PostgresStore extends MastraStorage {
896
1111
  | { messages: MastraMessageV2[]; format: 'v2' }): Promise<MastraMessageV2[] | MastraMessageV1[]> {
897
1112
  if (messages.length === 0) return messages;
898
1113
 
899
- try {
900
- const threadId = messages[0]?.threadId;
901
- if (!threadId) {
902
- throw new Error('Thread ID is required');
903
- }
1114
+ const threadId = messages[0]?.threadId;
1115
+ if (!threadId) {
1116
+ throw new MastraError({
1117
+ id: 'MASTRA_STORAGE_PG_STORE_SAVE_MESSAGES_FAILED',
1118
+ domain: ErrorDomain.STORAGE,
1119
+ category: ErrorCategory.THIRD_PARTY,
1120
+ text: `Thread ID is required`,
1121
+ });
1122
+ }
904
1123
 
905
- // Check if thread exists
906
- const thread = await this.getThreadById({ threadId });
907
- if (!thread) {
908
- throw new Error(`Thread ${threadId} not found`);
909
- }
1124
+ // Check if thread exists
1125
+ const thread = await this.getThreadById({ threadId });
1126
+ if (!thread) {
1127
+ throw new MastraError({
1128
+ id: 'MASTRA_STORAGE_PG_STORE_SAVE_MESSAGES_FAILED',
1129
+ domain: ErrorDomain.STORAGE,
1130
+ category: ErrorCategory.THIRD_PARTY,
1131
+ text: `Thread ${threadId} not found`,
1132
+ details: {
1133
+ threadId,
1134
+ },
1135
+ });
1136
+ }
910
1137
 
1138
+ try {
911
1139
  await this.db.tx(async t => {
912
1140
  // Execute message inserts and thread update in parallel for better performance
913
1141
  const messageInserts = messages.map(message => {
@@ -923,7 +1151,13 @@ export class PostgresStore extends MastraStorage {
923
1151
  }
924
1152
  return t.none(
925
1153
  `INSERT INTO ${this.getTableName(TABLE_MESSAGES)} (id, thread_id, content, "createdAt", role, type, "resourceId")
926
- VALUES ($1, $2, $3, $4, $5, $6, $7)`,
1154
+ VALUES ($1, $2, $3, $4, $5, $6, $7)
1155
+ ON CONFLICT (id) DO UPDATE SET
1156
+ thread_id = EXCLUDED.thread_id,
1157
+ content = EXCLUDED.content,
1158
+ role = EXCLUDED.role,
1159
+ type = EXCLUDED.type,
1160
+ "resourceId" = EXCLUDED."resourceId"`,
927
1161
  [
928
1162
  message.id,
929
1163
  message.threadId,
@@ -950,8 +1184,17 @@ export class PostgresStore extends MastraStorage {
950
1184
  if (format === `v2`) return list.get.all.v2();
951
1185
  return list.get.all.v1();
952
1186
  } catch (error) {
953
- console.error('Error saving messages:', error);
954
- throw error;
1187
+ throw new MastraError(
1188
+ {
1189
+ id: 'MASTRA_STORAGE_PG_STORE_SAVE_MESSAGES_FAILED',
1190
+ domain: ErrorDomain.STORAGE,
1191
+ category: ErrorCategory.THIRD_PARTY,
1192
+ details: {
1193
+ threadId,
1194
+ },
1195
+ },
1196
+ error,
1197
+ );
955
1198
  }
956
1199
  }
957
1200
 
@@ -980,8 +1223,18 @@ export class PostgresStore extends MastraStorage {
980
1223
  [workflowName, runId, JSON.stringify(snapshot), now, now],
981
1224
  );
982
1225
  } catch (error) {
983
- console.error('Error persisting workflow snapshot:', error);
984
- throw error;
1226
+ throw new MastraError(
1227
+ {
1228
+ id: 'MASTRA_STORAGE_PG_STORE_PERSIST_WORKFLOW_SNAPSHOT_FAILED',
1229
+ domain: ErrorDomain.STORAGE,
1230
+ category: ErrorCategory.THIRD_PARTY,
1231
+ details: {
1232
+ workflowName,
1233
+ runId,
1234
+ },
1235
+ },
1236
+ error,
1237
+ );
985
1238
  }
986
1239
  }
987
1240
 
@@ -1007,8 +1260,18 @@ export class PostgresStore extends MastraStorage {
1007
1260
 
1008
1261
  return (result as any).snapshot;
1009
1262
  } catch (error) {
1010
- console.error('Error loading workflow snapshot:', error);
1011
- throw error;
1263
+ throw new MastraError(
1264
+ {
1265
+ id: 'MASTRA_STORAGE_PG_STORE_LOAD_WORKFLOW_SNAPSHOT_FAILED',
1266
+ domain: ErrorDomain.STORAGE,
1267
+ category: ErrorCategory.THIRD_PARTY,
1268
+ details: {
1269
+ workflowName,
1270
+ runId,
1271
+ },
1272
+ },
1273
+ error,
1274
+ );
1012
1275
  }
1013
1276
  }
1014
1277
 
@@ -1122,8 +1385,17 @@ export class PostgresStore extends MastraStorage {
1122
1385
  // Use runs.length as total when not paginating
1123
1386
  return { runs, total: total || runs.length };
1124
1387
  } catch (error) {
1125
- console.error('Error getting workflow runs:', error);
1126
- throw error;
1388
+ throw new MastraError(
1389
+ {
1390
+ id: 'MASTRA_STORAGE_PG_STORE_GET_WORKFLOW_RUNS_FAILED',
1391
+ domain: ErrorDomain.STORAGE,
1392
+ category: ErrorCategory.THIRD_PARTY,
1393
+ details: {
1394
+ workflowName: workflowName || 'all',
1395
+ },
1396
+ },
1397
+ error,
1398
+ );
1127
1399
  }
1128
1400
  }
1129
1401
 
@@ -1169,8 +1441,18 @@ export class PostgresStore extends MastraStorage {
1169
1441
 
1170
1442
  return this.parseWorkflowRun(result);
1171
1443
  } catch (error) {
1172
- console.error('Error getting workflow run by ID:', error);
1173
- throw error;
1444
+ throw new MastraError(
1445
+ {
1446
+ id: 'MASTRA_STORAGE_PG_STORE_GET_WORKFLOW_RUN_BY_ID_FAILED',
1447
+ domain: ErrorDomain.STORAGE,
1448
+ category: ErrorCategory.THIRD_PARTY,
1449
+ details: {
1450
+ runId,
1451
+ workflowName: workflowName || '',
1452
+ },
1453
+ },
1454
+ error,
1455
+ );
1174
1456
  }
1175
1457
  }
1176
1458
 
@@ -1216,32 +1498,52 @@ export class PostgresStore extends MastraStorage {
1216
1498
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
1217
1499
 
1218
1500
  const countQuery = `SELECT COUNT(*) FROM ${this.getTableName(TABLE_EVALS)} ${whereClause}`;
1219
- const countResult = await this.db.one(countQuery, queryParams);
1220
- const total = parseInt(countResult.count, 10);
1221
- const currentOffset = page * perPage;
1501
+ try {
1502
+ const countResult = await this.db.one(countQuery, queryParams);
1503
+ const total = parseInt(countResult.count, 10);
1504
+ const currentOffset = page * perPage;
1505
+
1506
+ if (total === 0) {
1507
+ return {
1508
+ evals: [],
1509
+ total: 0,
1510
+ page,
1511
+ perPage,
1512
+ hasMore: false,
1513
+ };
1514
+ }
1515
+
1516
+ const dataQuery = `SELECT * FROM ${this.getTableName(
1517
+ TABLE_EVALS,
1518
+ )} ${whereClause} ORDER BY created_at DESC LIMIT $${paramIndex++} OFFSET $${paramIndex++}`;
1519
+ const rows = await this.db.manyOrNone(dataQuery, [...queryParams, perPage, currentOffset]);
1222
1520
 
1223
- if (total === 0) {
1224
1521
  return {
1225
- evals: [],
1226
- total: 0,
1522
+ evals: rows?.map(row => this.transformEvalRow(row)) ?? [],
1523
+ total,
1227
1524
  page,
1228
1525
  perPage,
1229
- hasMore: false,
1526
+ hasMore: currentOffset + (rows?.length ?? 0) < total,
1230
1527
  };
1528
+ } catch (error) {
1529
+ const mastraError = new MastraError(
1530
+ {
1531
+ id: 'MASTRA_STORAGE_PG_STORE_GET_EVALS_FAILED',
1532
+ domain: ErrorDomain.STORAGE,
1533
+ category: ErrorCategory.THIRD_PARTY,
1534
+ details: {
1535
+ agentName: agentName || 'all',
1536
+ type: type || 'all',
1537
+ page,
1538
+ perPage,
1539
+ },
1540
+ },
1541
+ error,
1542
+ );
1543
+ this.logger?.error?.(mastraError.toString());
1544
+ this.logger?.trackException(mastraError);
1545
+ throw mastraError;
1231
1546
  }
1232
-
1233
- const dataQuery = `SELECT * FROM ${this.getTableName(
1234
- TABLE_EVALS,
1235
- )} ${whereClause} ORDER BY created_at DESC LIMIT $${paramIndex++} OFFSET $${paramIndex++}`;
1236
- const rows = await this.db.manyOrNone(dataQuery, [...queryParams, perPage, currentOffset]);
1237
-
1238
- return {
1239
- evals: rows?.map(row => this.transformEvalRow(row)) ?? [],
1240
- total,
1241
- page,
1242
- perPage,
1243
- hasMore: currentOffset + (rows?.length ?? 0) < total,
1244
- };
1245
1547
  }
1246
1548
 
1247
1549
  async updateMessages({
@@ -1373,4 +1675,101 @@ export class PostgresStore extends MastraStorage {
1373
1675
  return message;
1374
1676
  });
1375
1677
  }
1678
+
1679
+ async getResourceById({ resourceId }: { resourceId: string }): Promise<StorageResourceType | null> {
1680
+ const tableName = this.getTableName(TABLE_RESOURCES);
1681
+ const result = await this.db.oneOrNone<StorageResourceType>(`SELECT * FROM ${tableName} WHERE id = $1`, [
1682
+ resourceId,
1683
+ ]);
1684
+
1685
+ if (!result) {
1686
+ return null;
1687
+ }
1688
+
1689
+ return {
1690
+ ...result,
1691
+ // Ensure workingMemory is always returned as a string, regardless of automatic parsing
1692
+ workingMemory:
1693
+ typeof result.workingMemory === 'object' ? JSON.stringify(result.workingMemory) : result.workingMemory,
1694
+ metadata: typeof result.metadata === 'string' ? JSON.parse(result.metadata) : result.metadata,
1695
+ };
1696
+ }
1697
+
1698
+ async saveResource({ resource }: { resource: StorageResourceType }): Promise<StorageResourceType> {
1699
+ const tableName = this.getTableName(TABLE_RESOURCES);
1700
+ await this.db.none(
1701
+ `INSERT INTO ${tableName} (id, "workingMemory", metadata, "createdAt", "updatedAt")
1702
+ VALUES ($1, $2, $3, $4, $5)`,
1703
+ [
1704
+ resource.id,
1705
+ resource.workingMemory,
1706
+ JSON.stringify(resource.metadata),
1707
+ resource.createdAt.toISOString(),
1708
+ resource.updatedAt.toISOString(),
1709
+ ],
1710
+ );
1711
+
1712
+ return resource;
1713
+ }
1714
+
1715
+ async updateResource({
1716
+ resourceId,
1717
+ workingMemory,
1718
+ metadata,
1719
+ }: {
1720
+ resourceId: string;
1721
+ workingMemory?: string;
1722
+ metadata?: Record<string, unknown>;
1723
+ }): Promise<StorageResourceType> {
1724
+ const existingResource = await this.getResourceById({ resourceId });
1725
+
1726
+ if (!existingResource) {
1727
+ // Create new resource if it doesn't exist
1728
+ const newResource: StorageResourceType = {
1729
+ id: resourceId,
1730
+ workingMemory,
1731
+ metadata: metadata || {},
1732
+ createdAt: new Date(),
1733
+ updatedAt: new Date(),
1734
+ };
1735
+ return this.saveResource({ resource: newResource });
1736
+ }
1737
+
1738
+ const updatedResource = {
1739
+ ...existingResource,
1740
+ workingMemory: workingMemory !== undefined ? workingMemory : existingResource.workingMemory,
1741
+ metadata: {
1742
+ ...existingResource.metadata,
1743
+ ...metadata,
1744
+ },
1745
+ updatedAt: new Date(),
1746
+ };
1747
+
1748
+ const tableName = this.getTableName(TABLE_RESOURCES);
1749
+ const updates: string[] = [];
1750
+ const values: any[] = [];
1751
+ let paramIndex = 1;
1752
+
1753
+ if (workingMemory !== undefined) {
1754
+ updates.push(`"workingMemory" = $${paramIndex}`);
1755
+ values.push(workingMemory);
1756
+ paramIndex++;
1757
+ }
1758
+
1759
+ if (metadata) {
1760
+ updates.push(`metadata = $${paramIndex}`);
1761
+ values.push(JSON.stringify(updatedResource.metadata));
1762
+ paramIndex++;
1763
+ }
1764
+
1765
+ updates.push(`"updatedAt" = $${paramIndex}`);
1766
+ values.push(updatedResource.updatedAt.toISOString());
1767
+ paramIndex++;
1768
+
1769
+ values.push(resourceId);
1770
+
1771
+ await this.db.none(`UPDATE ${tableName} SET ${updates.join(', ')} WHERE id = $${paramIndex}`, values);
1772
+
1773
+ return updatedResource;
1774
+ }
1376
1775
  }