@mastra/pg 0.10.2-alpha.1 → 0.10.2-alpha.3

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,23 +1,23 @@
1
1
 
2
- > @mastra/pg@0.10.2-alpha.1 build /home/runner/work/mastra/mastra/stores/pg
2
+ > @mastra/pg@0.10.2-alpha.3 build /home/runner/work/mastra/mastra/stores/pg
3
3
  > tsup src/index.ts --format esm,cjs --experimental-dts --clean --treeshake=smallest --splitting
4
4
 
5
5
  CLI Building entry: src/index.ts
6
6
  CLI Using tsconfig: tsconfig.json
7
7
  CLI tsup v8.5.0
8
8
  TSC Build start
9
- TSC ⚡️ Build success in 10884ms
9
+ TSC ⚡️ Build success in 10698ms
10
10
  DTS Build start
11
11
  CLI Target: es2022
12
12
  Analysis will use the bundled TypeScript version 5.8.3
13
13
  Writing package typings: /home/runner/work/mastra/mastra/stores/pg/dist/_tsup-dts-rollup.d.ts
14
14
  Analysis will use the bundled TypeScript version 5.8.3
15
15
  Writing package typings: /home/runner/work/mastra/mastra/stores/pg/dist/_tsup-dts-rollup.d.cts
16
- DTS ⚡️ Build success in 12247ms
16
+ DTS ⚡️ Build success in 10174ms
17
17
  CLI Cleaning output folder
18
18
  ESM Build start
19
19
  CJS Build start
20
- ESM dist/index.js 66.09 KB
21
- ESM ⚡️ Build success in 1701ms
22
- CJS dist/index.cjs 66.65 KB
23
- CJS ⚡️ Build success in 1701ms
20
+ ESM dist/index.js 67.23 KB
21
+ ESM ⚡️ Build success in 1908ms
22
+ CJS dist/index.cjs 67.79 KB
23
+ CJS ⚡️ Build success in 1909ms
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @mastra/pg
2
2
 
3
+ ## 0.10.2-alpha.3
4
+
5
+ ### Patch Changes
6
+
7
+ - e0f9201: change how dedupe works for libsql and pg
8
+ - Updated dependencies [925ab94]
9
+ - @mastra/core@0.10.4-alpha.3
10
+
11
+ ## 0.10.2-alpha.2
12
+
13
+ ### Patch Changes
14
+
15
+ - 48eddb9: update filter logic in Memory class to support semantic recall search scope
16
+ - Updated dependencies [48eddb9]
17
+ - @mastra/core@0.10.4-alpha.2
18
+
3
19
  ## 0.10.2-alpha.1
4
20
 
5
21
  ### Patch Changes
@@ -272,6 +272,9 @@ declare class PostgresStore extends MastraStorage {
272
272
  private setupSchemaPromise;
273
273
  private schemaSetupComplete;
274
274
  constructor(config: PostgresConfig);
275
+ get supports(): {
276
+ selectByIncludeResourceScope: boolean;
277
+ };
275
278
  private getTableName;
276
279
  /** @deprecated use getEvals instead */
277
280
  getEvalsByAgentName(agentName: string, type?: 'test' | 'live'): Promise<EvalRow[]>;
@@ -272,6 +272,9 @@ declare class PostgresStore extends MastraStorage {
272
272
  private setupSchemaPromise;
273
273
  private schemaSetupComplete;
274
274
  constructor(config: PostgresConfig);
275
+ get supports(): {
276
+ selectByIncludeResourceScope: boolean;
277
+ };
275
278
  private getTableName;
276
279
  /** @deprecated use getEvals instead */
277
280
  getEvalsByAgentName(agentName: string, type?: 'test' | 'live'): Promise<EvalRow[]>;
package/dist/index.cjs CHANGED
@@ -944,6 +944,11 @@ var PostgresStore = class extends storage.MastraStorage {
944
944
  }
945
945
  );
946
946
  }
947
+ get supports() {
948
+ return {
949
+ selectByIncludeResourceScope: true
950
+ };
951
+ }
947
952
  getTableName(indexName) {
948
953
  const parsedIndexName = utils.parseSqlIdentifier(indexName, "table name");
949
954
  const parsedSchemaName = this.schema ? utils.parseSqlIdentifier(this.schema, "schema name") : void 0;
@@ -1434,47 +1439,59 @@ var PostgresStore = class extends storage.MastraStorage {
1434
1439
  let rows = [];
1435
1440
  const include = selectBy?.include || [];
1436
1441
  if (include.length) {
1437
- rows = await this.db.manyOrNone(
1438
- `
1439
- WITH ordered_messages AS (
1440
- SELECT
1441
- *,
1442
- ROW_NUMBER() OVER (${orderByStatement}) as row_num
1443
- FROM ${this.getTableName(storage.TABLE_MESSAGES)}
1444
- WHERE thread_id = $1
1445
- )
1446
- SELECT
1447
- m.id,
1448
- m.content,
1449
- m.role,
1450
- m.type,
1451
- m."createdAt",
1452
- m.thread_id AS "threadId"
1453
- FROM ordered_messages m
1454
- WHERE m.id = ANY($2)
1455
- OR EXISTS (
1456
- SELECT 1 FROM ordered_messages target
1457
- WHERE target.id = ANY($2)
1458
- AND (
1459
- -- Get previous messages based on the max withPreviousMessages
1460
- (m.row_num <= target.row_num + $3 AND m.row_num > target.row_num)
1461
- OR
1462
- -- Get next messages based on the max withNextMessages
1463
- (m.row_num >= target.row_num - $4 AND m.row_num < target.row_num)
1442
+ const unionQueries = [];
1443
+ const params = [];
1444
+ let paramIdx = 1;
1445
+ for (const inc of include) {
1446
+ const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
1447
+ const searchId = inc.threadId || threadId;
1448
+ unionQueries.push(
1449
+ `
1450
+ SELECT * FROM (
1451
+ WITH ordered_messages AS (
1452
+ SELECT
1453
+ *,
1454
+ ROW_NUMBER() OVER (${orderByStatement}) as row_num
1455
+ FROM ${this.getTableName(storage.TABLE_MESSAGES)}
1456
+ WHERE thread_id = $${paramIdx}
1464
1457
  )
1465
- )
1466
- ORDER BY m."createdAt" ASC
1467
- `,
1468
- // Keep ASC for final sorting after fetching context
1469
- [
1470
- threadId,
1471
- include.map((i) => i.id),
1472
- Math.max(0, ...include.map((i) => i.withPreviousMessages || 0)),
1473
- // Ensure non-negative
1474
- Math.max(0, ...include.map((i) => i.withNextMessages || 0))
1475
- // Ensure non-negative
1476
- ]
1477
- );
1458
+ SELECT
1459
+ m.id,
1460
+ m.content,
1461
+ m.role,
1462
+ m.type,
1463
+ m."createdAt",
1464
+ m.thread_id AS "threadId",
1465
+ m."resourceId"
1466
+ FROM ordered_messages m
1467
+ WHERE m.id = $${paramIdx + 1}
1468
+ OR EXISTS (
1469
+ SELECT 1 FROM ordered_messages target
1470
+ WHERE target.id = $${paramIdx + 1}
1471
+ AND (
1472
+ -- Get previous messages based on the max withPreviousMessages
1473
+ (m.row_num <= target.row_num + $${paramIdx + 2} AND m.row_num > target.row_num)
1474
+ OR
1475
+ -- Get next messages based on the max withNextMessages
1476
+ (m.row_num >= target.row_num - $${paramIdx + 3} AND m.row_num < target.row_num)
1477
+ )
1478
+ )
1479
+ )
1480
+ `
1481
+ // Keep ASC for final sorting after fetching context
1482
+ );
1483
+ params.push(searchId, id, withPreviousMessages, withNextMessages);
1484
+ paramIdx += 4;
1485
+ }
1486
+ const finalQuery = unionQueries.join(" UNION ALL ") + ' ORDER BY "createdAt" ASC';
1487
+ const includedRows = await this.db.manyOrNone(finalQuery, params);
1488
+ const seen = /* @__PURE__ */ new Set();
1489
+ const dedupedRows = includedRows.filter((row) => {
1490
+ if (seen.has(row.id)) return false;
1491
+ seen.add(row.id);
1492
+ return true;
1493
+ });
1494
+ rows = dedupedRows;
1478
1495
  } else {
1479
1496
  const limit = typeof selectBy?.last === `number` ? selectBy.last : 40;
1480
1497
  if (limit === 0 && selectBy?.last !== false) ; else {
@@ -1578,16 +1595,27 @@ var PostgresStore = class extends storage.MastraStorage {
1578
1595
  }
1579
1596
  await this.db.tx(async (t) => {
1580
1597
  for (const message of messages) {
1598
+ if (!message.threadId) {
1599
+ throw new Error(
1600
+ `Expected to find a threadId for message, but couldn't find one. An unexpected error has occurred.`
1601
+ );
1602
+ }
1603
+ if (!message.resourceId) {
1604
+ throw new Error(
1605
+ `Expected to find a resourceId for message, but couldn't find one. An unexpected error has occurred.`
1606
+ );
1607
+ }
1581
1608
  await t.none(
1582
- `INSERT INTO ${this.getTableName(storage.TABLE_MESSAGES)} (id, thread_id, content, "createdAt", role, type)
1583
- VALUES ($1, $2, $3, $4, $5, $6)`,
1609
+ `INSERT INTO ${this.getTableName(storage.TABLE_MESSAGES)} (id, thread_id, content, "createdAt", role, type, "resourceId")
1610
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`,
1584
1611
  [
1585
1612
  message.id,
1586
- threadId,
1613
+ message.threadId,
1587
1614
  typeof message.content === "string" ? message.content : JSON.stringify(message.content),
1588
1615
  message.createdAt || (/* @__PURE__ */ new Date()).toISOString(),
1589
1616
  message.role,
1590
- message.type || "v2"
1617
+ message.type || "v2",
1618
+ message.resourceId
1591
1619
  ]
1592
1620
  );
1593
1621
  }
package/dist/index.js CHANGED
@@ -936,6 +936,11 @@ var PostgresStore = class extends MastraStorage {
936
936
  }
937
937
  );
938
938
  }
939
+ get supports() {
940
+ return {
941
+ selectByIncludeResourceScope: true
942
+ };
943
+ }
939
944
  getTableName(indexName) {
940
945
  const parsedIndexName = parseSqlIdentifier(indexName, "table name");
941
946
  const parsedSchemaName = this.schema ? parseSqlIdentifier(this.schema, "schema name") : void 0;
@@ -1426,47 +1431,59 @@ var PostgresStore = class extends MastraStorage {
1426
1431
  let rows = [];
1427
1432
  const include = selectBy?.include || [];
1428
1433
  if (include.length) {
1429
- rows = await this.db.manyOrNone(
1430
- `
1431
- WITH ordered_messages AS (
1432
- SELECT
1433
- *,
1434
- ROW_NUMBER() OVER (${orderByStatement}) as row_num
1435
- FROM ${this.getTableName(TABLE_MESSAGES)}
1436
- WHERE thread_id = $1
1437
- )
1438
- SELECT
1439
- m.id,
1440
- m.content,
1441
- m.role,
1442
- m.type,
1443
- m."createdAt",
1444
- m.thread_id AS "threadId"
1445
- FROM ordered_messages m
1446
- WHERE m.id = ANY($2)
1447
- OR EXISTS (
1448
- SELECT 1 FROM ordered_messages target
1449
- WHERE target.id = ANY($2)
1450
- AND (
1451
- -- Get previous messages based on the max withPreviousMessages
1452
- (m.row_num <= target.row_num + $3 AND m.row_num > target.row_num)
1453
- OR
1454
- -- Get next messages based on the max withNextMessages
1455
- (m.row_num >= target.row_num - $4 AND m.row_num < target.row_num)
1434
+ const unionQueries = [];
1435
+ const params = [];
1436
+ let paramIdx = 1;
1437
+ for (const inc of include) {
1438
+ const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
1439
+ const searchId = inc.threadId || threadId;
1440
+ unionQueries.push(
1441
+ `
1442
+ SELECT * FROM (
1443
+ WITH ordered_messages AS (
1444
+ SELECT
1445
+ *,
1446
+ ROW_NUMBER() OVER (${orderByStatement}) as row_num
1447
+ FROM ${this.getTableName(TABLE_MESSAGES)}
1448
+ WHERE thread_id = $${paramIdx}
1456
1449
  )
1457
- )
1458
- ORDER BY m."createdAt" ASC
1459
- `,
1460
- // Keep ASC for final sorting after fetching context
1461
- [
1462
- threadId,
1463
- include.map((i) => i.id),
1464
- Math.max(0, ...include.map((i) => i.withPreviousMessages || 0)),
1465
- // Ensure non-negative
1466
- Math.max(0, ...include.map((i) => i.withNextMessages || 0))
1467
- // Ensure non-negative
1468
- ]
1469
- );
1450
+ SELECT
1451
+ m.id,
1452
+ m.content,
1453
+ m.role,
1454
+ m.type,
1455
+ m."createdAt",
1456
+ m.thread_id AS "threadId",
1457
+ m."resourceId"
1458
+ FROM ordered_messages m
1459
+ WHERE m.id = $${paramIdx + 1}
1460
+ OR EXISTS (
1461
+ SELECT 1 FROM ordered_messages target
1462
+ WHERE target.id = $${paramIdx + 1}
1463
+ AND (
1464
+ -- Get previous messages based on the max withPreviousMessages
1465
+ (m.row_num <= target.row_num + $${paramIdx + 2} AND m.row_num > target.row_num)
1466
+ OR
1467
+ -- Get next messages based on the max withNextMessages
1468
+ (m.row_num >= target.row_num - $${paramIdx + 3} AND m.row_num < target.row_num)
1469
+ )
1470
+ )
1471
+ )
1472
+ `
1473
+ // Keep ASC for final sorting after fetching context
1474
+ );
1475
+ params.push(searchId, id, withPreviousMessages, withNextMessages);
1476
+ paramIdx += 4;
1477
+ }
1478
+ const finalQuery = unionQueries.join(" UNION ALL ") + ' ORDER BY "createdAt" ASC';
1479
+ const includedRows = await this.db.manyOrNone(finalQuery, params);
1480
+ const seen = /* @__PURE__ */ new Set();
1481
+ const dedupedRows = includedRows.filter((row) => {
1482
+ if (seen.has(row.id)) return false;
1483
+ seen.add(row.id);
1484
+ return true;
1485
+ });
1486
+ rows = dedupedRows;
1470
1487
  } else {
1471
1488
  const limit = typeof selectBy?.last === `number` ? selectBy.last : 40;
1472
1489
  if (limit === 0 && selectBy?.last !== false) ; else {
@@ -1570,16 +1587,27 @@ var PostgresStore = class extends MastraStorage {
1570
1587
  }
1571
1588
  await this.db.tx(async (t) => {
1572
1589
  for (const message of messages) {
1590
+ if (!message.threadId) {
1591
+ throw new Error(
1592
+ `Expected to find a threadId for message, but couldn't find one. An unexpected error has occurred.`
1593
+ );
1594
+ }
1595
+ if (!message.resourceId) {
1596
+ throw new Error(
1597
+ `Expected to find a resourceId for message, but couldn't find one. An unexpected error has occurred.`
1598
+ );
1599
+ }
1573
1600
  await t.none(
1574
- `INSERT INTO ${this.getTableName(TABLE_MESSAGES)} (id, thread_id, content, "createdAt", role, type)
1575
- VALUES ($1, $2, $3, $4, $5, $6)`,
1601
+ `INSERT INTO ${this.getTableName(TABLE_MESSAGES)} (id, thread_id, content, "createdAt", role, type, "resourceId")
1602
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`,
1576
1603
  [
1577
1604
  message.id,
1578
- threadId,
1605
+ message.threadId,
1579
1606
  typeof message.content === "string" ? message.content : JSON.stringify(message.content),
1580
1607
  message.createdAt || (/* @__PURE__ */ new Date()).toISOString(),
1581
1608
  message.role,
1582
- message.type || "v2"
1609
+ message.type || "v2",
1610
+ message.resourceId
1583
1611
  ]
1584
1612
  );
1585
1613
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/pg",
3
- "version": "0.10.2-alpha.1",
3
+ "version": "0.10.2-alpha.3",
4
4
  "description": "Postgres provider for Mastra - includes both vector and db storage capabilities",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -35,7 +35,7 @@
35
35
  "vitest": "^3.2.2",
36
36
  "@internal/lint": "0.0.10",
37
37
  "@internal/storage-test-utils": "0.0.6",
38
- "@mastra/core": "0.10.4-alpha.1"
38
+ "@mastra/core": "0.10.4-alpha.3"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "@mastra/core": "^0.10.2-alpha.0"
@@ -7,8 +7,9 @@ import {
7
7
  createSampleMessageV2,
8
8
  createSampleWorkflowSnapshot,
9
9
  resetRole,
10
+ checkWorkflowSnapshot,
10
11
  } from '@internal/storage-test-utils';
11
- import type { MastraMessageV1, StorageThreadType } from '@mastra/core/memory';
12
+ import type { MastraMessageV1, MastraMessageV2, StorageThreadType } from '@mastra/core/memory';
12
13
  import type { StorageColumn, TABLE_NAMES } from '@mastra/core/storage';
13
14
  import {
14
15
  TABLE_WORKFLOW_SNAPSHOT,
@@ -36,13 +37,6 @@ const connectionString = `postgresql://${TEST_CONFIG.user}:${TEST_CONFIG.passwor
36
37
 
37
38
  vi.setConfig({ testTimeout: 60_000, hookTimeout: 60_000 });
38
39
 
39
- const checkWorkflowSnapshot = (snapshot: WorkflowRunState | string, stepId: string, status: string) => {
40
- if (typeof snapshot === 'string') {
41
- throw new Error('Expected WorkflowRunState, got string');
42
- }
43
- expect(snapshot.context?.[stepId]?.status).toBe(status);
44
- };
45
-
46
40
  describe('PostgresStore', () => {
47
41
  let store: PostgresStore;
48
42
 
@@ -241,6 +235,110 @@ describe('PostgresStore', () => {
241
235
  const savedMessages = await store.getMessages({ threadId: thread.id });
242
236
  expect(savedMessages).toHaveLength(0);
243
237
  });
238
+
239
+ it('should retrieve messages w/ next/prev messages by message id + resource id', async () => {
240
+ const thread = createSampleThread({ id: 'thread-one' });
241
+ await store.saveThread({ thread });
242
+
243
+ const thread2 = createSampleThread({ id: 'thread-two' });
244
+ await store.saveThread({ thread: thread2 });
245
+
246
+ const thread3 = createSampleThread({ id: 'thread-three' });
247
+ await store.saveThread({ thread: thread3 });
248
+
249
+ const messages: MastraMessageV2[] = [
250
+ createSampleMessageV2({ threadId: 'thread-one', content: 'First', resourceId: 'cross-thread-resource' }),
251
+ createSampleMessageV2({ threadId: 'thread-one', content: 'Second', resourceId: 'cross-thread-resource' }),
252
+ createSampleMessageV2({ threadId: 'thread-one', content: 'Third', resourceId: 'cross-thread-resource' }),
253
+
254
+ createSampleMessageV2({ threadId: 'thread-two', content: 'Fourth', resourceId: 'cross-thread-resource' }),
255
+ createSampleMessageV2({ threadId: 'thread-two', content: 'Fifth', resourceId: 'cross-thread-resource' }),
256
+ createSampleMessageV2({ threadId: 'thread-two', content: 'Sixth', resourceId: 'cross-thread-resource' }),
257
+
258
+ createSampleMessageV2({ threadId: 'thread-three', content: 'Seventh', resourceId: 'other-resource' }),
259
+ createSampleMessageV2({ threadId: 'thread-three', content: 'Eighth', resourceId: 'other-resource' }),
260
+ ];
261
+
262
+ await store.saveMessages({ messages: messages, format: 'v2' });
263
+
264
+ const retrievedMessages = await store.getMessages({ threadId: 'thread-one', format: 'v2' });
265
+ expect(retrievedMessages).toHaveLength(3);
266
+ expect(retrievedMessages.map((m: any) => m.content.parts[0].text)).toEqual(['First', 'Second', 'Third']);
267
+
268
+ const retrievedMessages2 = await store.getMessages({ threadId: 'thread-two', format: 'v2' });
269
+ expect(retrievedMessages2).toHaveLength(3);
270
+ expect(retrievedMessages2.map((m: any) => m.content.parts[0].text)).toEqual(['Fourth', 'Fifth', 'Sixth']);
271
+
272
+ const retrievedMessages3 = await store.getMessages({ threadId: 'thread-three', format: 'v2' });
273
+ expect(retrievedMessages3).toHaveLength(2);
274
+ expect(retrievedMessages3.map((m: any) => m.content.parts[0].text)).toEqual(['Seventh', 'Eighth']);
275
+
276
+ const crossThreadMessages: MastraMessageV2[] = await store.getMessages({
277
+ threadId: 'thread-doesnt-exist',
278
+ format: 'v2',
279
+ selectBy: {
280
+ last: 0,
281
+ include: [
282
+ {
283
+ id: messages[1].id,
284
+ threadId: 'thread-one',
285
+ withNextMessages: 2,
286
+ withPreviousMessages: 2,
287
+ },
288
+ {
289
+ id: messages[4].id,
290
+ threadId: 'thread-two',
291
+ withPreviousMessages: 2,
292
+ withNextMessages: 2,
293
+ },
294
+ ],
295
+ },
296
+ });
297
+
298
+ expect(crossThreadMessages).toHaveLength(6);
299
+ expect(crossThreadMessages.filter(m => m.threadId === `thread-one`)).toHaveLength(3);
300
+ expect(crossThreadMessages.filter(m => m.threadId === `thread-two`)).toHaveLength(3);
301
+
302
+ const crossThreadMessages2: MastraMessageV2[] = await store.getMessages({
303
+ threadId: 'thread-one',
304
+ format: 'v2',
305
+ selectBy: {
306
+ last: 0,
307
+ include: [
308
+ {
309
+ id: messages[4].id,
310
+ threadId: 'thread-two',
311
+ withPreviousMessages: 1,
312
+ withNextMessages: 1,
313
+ },
314
+ ],
315
+ },
316
+ });
317
+
318
+ expect(crossThreadMessages2).toHaveLength(3);
319
+ expect(crossThreadMessages2.filter(m => m.threadId === `thread-one`)).toHaveLength(0);
320
+ expect(crossThreadMessages2.filter(m => m.threadId === `thread-two`)).toHaveLength(3);
321
+
322
+ const crossThreadMessages3: MastraMessageV2[] = await store.getMessages({
323
+ threadId: 'thread-two',
324
+ format: 'v2',
325
+ selectBy: {
326
+ last: 0,
327
+ include: [
328
+ {
329
+ id: messages[1].id,
330
+ threadId: 'thread-one',
331
+ withNextMessages: 1,
332
+ withPreviousMessages: 1,
333
+ },
334
+ ],
335
+ },
336
+ });
337
+
338
+ expect(crossThreadMessages3).toHaveLength(3);
339
+ expect(crossThreadMessages3.filter(m => m.threadId === `thread-one`)).toHaveLength(3);
340
+ expect(crossThreadMessages3.filter(m => m.threadId === `thread-two`)).toHaveLength(0);
341
+ });
244
342
  });
245
343
 
246
344
  describe('Edge Cases and Error Handling', () => {
@@ -1279,6 +1377,7 @@ describe('PostgresStore', () => {
1279
1377
  });
1280
1378
 
1281
1379
  it('should filter by date with pagination for getMessages', async () => {
1380
+ resetRole();
1282
1381
  const threadData = createSampleThread();
1283
1382
  const thread = await store.saveThread({ thread: threadData as StorageThreadType });
1284
1383
  const now = new Date();
@@ -87,6 +87,14 @@ export class PostgresStore extends MastraStorage {
87
87
  );
88
88
  }
89
89
 
90
+ public get supports(): {
91
+ selectByIncludeResourceScope: boolean;
92
+ } {
93
+ return {
94
+ selectByIncludeResourceScope: true,
95
+ };
96
+ }
97
+
90
98
  private getTableName(indexName: string) {
91
99
  const parsedIndexName = parseSqlIdentifier(indexName, 'table name');
92
100
  const parsedSchemaName = this.schema ? parseSqlIdentifier(this.schema, 'schema name') : undefined;
@@ -706,44 +714,60 @@ export class PostgresStore extends MastraStorage {
706
714
  const include = selectBy?.include || [];
707
715
 
708
716
  if (include.length) {
709
- rows = await this.db.manyOrNone(
710
- `
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
717
- )
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)
717
+ const unionQueries: string[] = [];
718
+ const params: any[] = [];
719
+ let paramIdx = 1;
720
+
721
+ for (const inc of include) {
722
+ const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
723
+ // if threadId is provided, use it, otherwise use threadId from args
724
+ const searchId = inc.threadId || threadId;
725
+ unionQueries.push(
726
+ `
727
+ SELECT * FROM (
728
+ WITH ordered_messages AS (
729
+ SELECT
730
+ *,
731
+ ROW_NUMBER() OVER (${orderByStatement}) as row_num
732
+ FROM ${this.getTableName(TABLE_MESSAGES)}
733
+ WHERE thread_id = $${paramIdx}
736
734
  )
737
- )
738
- ORDER BY m."createdAt" ASC
735
+ SELECT
736
+ m.id,
737
+ m.content,
738
+ m.role,
739
+ m.type,
740
+ m."createdAt",
741
+ m.thread_id AS "threadId",
742
+ m."resourceId"
743
+ FROM ordered_messages m
744
+ WHERE m.id = $${paramIdx + 1}
745
+ OR EXISTS (
746
+ SELECT 1 FROM ordered_messages target
747
+ WHERE target.id = $${paramIdx + 1}
748
+ AND (
749
+ -- Get previous messages based on the max withPreviousMessages
750
+ (m.row_num <= target.row_num + $${paramIdx + 2} AND m.row_num > target.row_num)
751
+ OR
752
+ -- Get next messages based on the max withNextMessages
753
+ (m.row_num >= target.row_num - $${paramIdx + 3} AND m.row_num < target.row_num)
754
+ )
755
+ )
756
+ )
739
757
  `, // Keep ASC for final sorting after fetching context
740
- [
741
- threadId,
742
- include.map(i => i.id),
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
745
- ],
746
- );
758
+ );
759
+ params.push(searchId, id, withPreviousMessages, withNextMessages);
760
+ paramIdx += 4;
761
+ }
762
+ const finalQuery = unionQueries.join(' UNION ALL ') + ' ORDER BY "createdAt" ASC';
763
+ const includedRows = await this.db.manyOrNone(finalQuery, params);
764
+ const seen = new Set<string>();
765
+ const dedupedRows = includedRows.filter(row => {
766
+ if (seen.has(row.id)) return false;
767
+ seen.add(row.id);
768
+ return true;
769
+ });
770
+ rows = dedupedRows;
747
771
  } else {
748
772
  const limit = typeof selectBy?.last === `number` ? selectBy.last : 40;
749
773
  if (limit === 0 && selectBy?.last !== false) {
@@ -881,16 +905,27 @@ export class PostgresStore extends MastraStorage {
881
905
 
882
906
  await this.db.tx(async t => {
883
907
  for (const message of messages) {
908
+ if (!message.threadId) {
909
+ throw new Error(
910
+ `Expected to find a threadId for message, but couldn't find one. An unexpected error has occurred.`,
911
+ );
912
+ }
913
+ if (!message.resourceId) {
914
+ throw new Error(
915
+ `Expected to find a resourceId for message, but couldn't find one. An unexpected error has occurred.`,
916
+ );
917
+ }
884
918
  await t.none(
885
- `INSERT INTO ${this.getTableName(TABLE_MESSAGES)} (id, thread_id, content, "createdAt", role, type)
886
- VALUES ($1, $2, $3, $4, $5, $6)`,
919
+ `INSERT INTO ${this.getTableName(TABLE_MESSAGES)} (id, thread_id, content, "createdAt", role, type, "resourceId")
920
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`,
887
921
  [
888
922
  message.id,
889
- threadId,
923
+ message.threadId,
890
924
  typeof message.content === 'string' ? message.content : JSON.stringify(message.content),
891
925
  message.createdAt || new Date().toISOString(),
892
926
  message.role,
893
927
  message.type || 'v2',
928
+ message.resourceId,
894
929
  ],
895
930
  );
896
931
  }