@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.
- package/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +28 -0
- package/dist/_tsup-dts-rollup.d.cts +40 -6
- package/dist/_tsup-dts-rollup.d.ts +40 -6
- package/dist/index.cjs +808 -255
- package/dist/index.js +785 -232
- package/package.json +4 -4
- package/src/storage/index.test.ts +428 -35
- package/src/storage/index.ts +596 -197
- package/src/vector/filter.test.ts +12 -12
- package/src/vector/filter.ts +36 -7
- package/src/vector/index.test.ts +2 -2
- package/src/vector/index.ts +283 -90
- package/src/vector/sql-builder.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/pg",
|
|
3
|
-
"version": "0.11.1-alpha.
|
|
3
|
+
"version": "0.11.1-alpha.2",
|
|
4
4
|
"description": "Postgres provider for Mastra - includes both vector and db storage capabilities",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -29,13 +29,13 @@
|
|
|
29
29
|
"@microsoft/api-extractor": "^7.52.8",
|
|
30
30
|
"@types/node": "^20.19.0",
|
|
31
31
|
"@types/pg": "^8.15.4",
|
|
32
|
-
"eslint": "^9.
|
|
32
|
+
"eslint": "^9.29.0",
|
|
33
33
|
"tsup": "^8.5.0",
|
|
34
34
|
"typescript": "^5.8.3",
|
|
35
35
|
"vitest": "^3.2.3",
|
|
36
36
|
"@internal/lint": "0.0.13",
|
|
37
|
-
"@
|
|
38
|
-
"@
|
|
37
|
+
"@mastra/core": "0.10.7-alpha.2",
|
|
38
|
+
"@internal/storage-test-utils": "0.0.9"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"@mastra/core": ">=0.10.4-0 <0.11.0"
|
|
@@ -4,11 +4,12 @@ import {
|
|
|
4
4
|
createSampleTraceForDB,
|
|
5
5
|
createSampleThread,
|
|
6
6
|
createSampleMessageV1,
|
|
7
|
+
createSampleMessageV2,
|
|
7
8
|
createSampleWorkflowSnapshot,
|
|
8
9
|
resetRole,
|
|
9
10
|
checkWorkflowSnapshot,
|
|
10
11
|
} from '@internal/storage-test-utils';
|
|
11
|
-
import type {
|
|
12
|
+
import type { MastraMessageV2 } from '@mastra/core/agent';
|
|
12
13
|
import type { MastraMessageV1, StorageThreadType } from '@mastra/core/memory';
|
|
13
14
|
import type { StorageColumn, TABLE_NAMES } from '@mastra/core/storage';
|
|
14
15
|
import {
|
|
@@ -37,37 +38,6 @@ const connectionString = `postgresql://${TEST_CONFIG.user}:${TEST_CONFIG.passwor
|
|
|
37
38
|
|
|
38
39
|
vi.setConfig({ testTimeout: 60_000, hookTimeout: 60_000 });
|
|
39
40
|
|
|
40
|
-
const createSampleMessageV2 = ({
|
|
41
|
-
threadId,
|
|
42
|
-
resourceId,
|
|
43
|
-
role = 'user',
|
|
44
|
-
content,
|
|
45
|
-
createdAt,
|
|
46
|
-
thread,
|
|
47
|
-
}: {
|
|
48
|
-
threadId: string;
|
|
49
|
-
resourceId?: string;
|
|
50
|
-
role?: 'user' | 'assistant';
|
|
51
|
-
content?: Partial<MastraMessageContentV2>;
|
|
52
|
-
createdAt?: Date;
|
|
53
|
-
thread?: StorageThreadType;
|
|
54
|
-
}): MastraMessageV2 => {
|
|
55
|
-
return {
|
|
56
|
-
id: randomUUID(),
|
|
57
|
-
threadId,
|
|
58
|
-
resourceId: resourceId || thread?.resourceId || 'test-resource',
|
|
59
|
-
role,
|
|
60
|
-
createdAt: createdAt || new Date(),
|
|
61
|
-
content: {
|
|
62
|
-
format: 2,
|
|
63
|
-
parts: content?.parts || [{ type: 'text', text: content?.content ?? '' }],
|
|
64
|
-
content: content?.content || `Sample content ${randomUUID()}`,
|
|
65
|
-
...content,
|
|
66
|
-
},
|
|
67
|
-
type: 'v2',
|
|
68
|
-
};
|
|
69
|
-
};
|
|
70
|
-
|
|
71
41
|
describe('PostgresStore', () => {
|
|
72
42
|
let store: PostgresStore;
|
|
73
43
|
|
|
@@ -426,6 +396,75 @@ describe('PostgresStore', () => {
|
|
|
426
396
|
expect(crossThreadMessages3.filter(m => m.threadId === `thread-one`)).toHaveLength(3);
|
|
427
397
|
expect(crossThreadMessages3.filter(m => m.threadId === `thread-two`)).toHaveLength(0);
|
|
428
398
|
});
|
|
399
|
+
|
|
400
|
+
it('should return messages using both last and include (cross-thread, deduped)', async () => {
|
|
401
|
+
const thread = createSampleThread({ id: 'thread-one' });
|
|
402
|
+
await store.saveThread({ thread });
|
|
403
|
+
|
|
404
|
+
const thread2 = createSampleThread({ id: 'thread-two' });
|
|
405
|
+
await store.saveThread({ thread: thread2 });
|
|
406
|
+
|
|
407
|
+
const now = new Date();
|
|
408
|
+
|
|
409
|
+
// Setup: create messages in two threads
|
|
410
|
+
const messages = [
|
|
411
|
+
createSampleMessageV2({
|
|
412
|
+
threadId: 'thread-one',
|
|
413
|
+
content: { content: 'A' },
|
|
414
|
+
createdAt: new Date(now.getTime()),
|
|
415
|
+
}),
|
|
416
|
+
createSampleMessageV2({
|
|
417
|
+
threadId: 'thread-one',
|
|
418
|
+
content: { content: 'B' },
|
|
419
|
+
createdAt: new Date(now.getTime() + 1000),
|
|
420
|
+
}),
|
|
421
|
+
createSampleMessageV2({
|
|
422
|
+
threadId: 'thread-one',
|
|
423
|
+
content: { content: 'C' },
|
|
424
|
+
createdAt: new Date(now.getTime() + 2000),
|
|
425
|
+
}),
|
|
426
|
+
createSampleMessageV2({
|
|
427
|
+
threadId: 'thread-two',
|
|
428
|
+
content: { content: 'D' },
|
|
429
|
+
createdAt: new Date(now.getTime() + 3000),
|
|
430
|
+
}),
|
|
431
|
+
createSampleMessageV2({
|
|
432
|
+
threadId: 'thread-two',
|
|
433
|
+
content: { content: 'E' },
|
|
434
|
+
createdAt: new Date(now.getTime() + 4000),
|
|
435
|
+
}),
|
|
436
|
+
createSampleMessageV2({
|
|
437
|
+
threadId: 'thread-two',
|
|
438
|
+
content: { content: 'F' },
|
|
439
|
+
createdAt: new Date(now.getTime() + 5000),
|
|
440
|
+
}),
|
|
441
|
+
];
|
|
442
|
+
await store.saveMessages({ messages, format: 'v2' });
|
|
443
|
+
|
|
444
|
+
// Use last: 2 and include a message from another thread with context
|
|
445
|
+
const result = await store.getMessages({
|
|
446
|
+
threadId: 'thread-one',
|
|
447
|
+
format: 'v2',
|
|
448
|
+
selectBy: {
|
|
449
|
+
last: 2,
|
|
450
|
+
include: [
|
|
451
|
+
{
|
|
452
|
+
id: messages[4].id, // 'E' from thread-bar
|
|
453
|
+
threadId: 'thread-two',
|
|
454
|
+
withPreviousMessages: 1,
|
|
455
|
+
withNextMessages: 1,
|
|
456
|
+
},
|
|
457
|
+
],
|
|
458
|
+
},
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// Should include last 2 from thread-one and 3 from thread-two (D, E, F)
|
|
462
|
+
expect(result.map(m => m.content.content).sort()).toEqual(['B', 'C', 'D', 'E', 'F']);
|
|
463
|
+
// Should include 2 from thread-one
|
|
464
|
+
expect(result.filter(m => m.threadId === 'thread-one').map(m => m.content.content)).toEqual(['B', 'C']);
|
|
465
|
+
// Should include 3 from thread-two
|
|
466
|
+
expect(result.filter(m => m.threadId === 'thread-two').map(m => m.content.content)).toEqual(['D', 'E', 'F']);
|
|
467
|
+
});
|
|
429
468
|
});
|
|
430
469
|
|
|
431
470
|
describe('updateMessages', () => {
|
|
@@ -552,6 +591,81 @@ describe('PostgresStore', () => {
|
|
|
552
591
|
expect(thread2Messages).toHaveLength(1);
|
|
553
592
|
expect(thread2Messages[0].id).toBe(message.id);
|
|
554
593
|
});
|
|
594
|
+
it('should upsert messages: duplicate id+threadId results in update, not duplicate row', async () => {
|
|
595
|
+
const thread = await createSampleThread();
|
|
596
|
+
await store.saveThread({ thread });
|
|
597
|
+
const baseMessage = createSampleMessageV2({
|
|
598
|
+
threadId: thread.id,
|
|
599
|
+
createdAt: new Date(),
|
|
600
|
+
content: { content: 'Original' },
|
|
601
|
+
resourceId: thread.resourceId,
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
// Insert the message for the first time
|
|
605
|
+
await store.saveMessages({ messages: [baseMessage], format: 'v2' });
|
|
606
|
+
|
|
607
|
+
// Insert again with the same id and threadId but different content
|
|
608
|
+
const updatedMessage = {
|
|
609
|
+
...createSampleMessageV2({
|
|
610
|
+
threadId: thread.id,
|
|
611
|
+
createdAt: new Date(),
|
|
612
|
+
content: { content: 'Updated' },
|
|
613
|
+
resourceId: thread.resourceId,
|
|
614
|
+
}),
|
|
615
|
+
id: baseMessage.id,
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
await store.saveMessages({ messages: [updatedMessage], format: 'v2' });
|
|
619
|
+
|
|
620
|
+
// Retrieve messages for the thread
|
|
621
|
+
const retrievedMessages = await store.getMessages({ threadId: thread.id, format: 'v2' });
|
|
622
|
+
|
|
623
|
+
// Only one message should exist for that id+threadId
|
|
624
|
+
expect(retrievedMessages.filter(m => m.id === baseMessage.id)).toHaveLength(1);
|
|
625
|
+
|
|
626
|
+
// The content should be the updated one
|
|
627
|
+
expect(retrievedMessages.find(m => m.id === baseMessage.id)?.content.content).toBe('Updated');
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
it('should upsert messages: duplicate id and different threadid', async () => {
|
|
631
|
+
const thread1 = await createSampleThread();
|
|
632
|
+
const thread2 = await createSampleThread();
|
|
633
|
+
await store.saveThread({ thread: thread1 });
|
|
634
|
+
await store.saveThread({ thread: thread2 });
|
|
635
|
+
|
|
636
|
+
const message = createSampleMessageV2({
|
|
637
|
+
threadId: thread1.id,
|
|
638
|
+
createdAt: new Date(),
|
|
639
|
+
content: { content: 'Thread1 Content' },
|
|
640
|
+
resourceId: thread1.resourceId,
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
// Insert message into thread1
|
|
644
|
+
await store.saveMessages({ messages: [message], format: 'v2' });
|
|
645
|
+
|
|
646
|
+
// Attempt to insert a message with the same id but different threadId
|
|
647
|
+
const conflictingMessage = {
|
|
648
|
+
...createSampleMessageV2({
|
|
649
|
+
threadId: thread2.id, // different thread
|
|
650
|
+
content: { content: 'Thread2 Content' },
|
|
651
|
+
resourceId: thread2.resourceId,
|
|
652
|
+
}),
|
|
653
|
+
id: message.id,
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
// Save should move the message to the new thread
|
|
657
|
+
await store.saveMessages({ messages: [conflictingMessage], format: 'v2' });
|
|
658
|
+
|
|
659
|
+
// Retrieve messages for both threads
|
|
660
|
+
const thread1Messages = await store.getMessages({ threadId: thread1.id, format: 'v2' });
|
|
661
|
+
const thread2Messages = await store.getMessages({ threadId: thread2.id, format: 'v2' });
|
|
662
|
+
|
|
663
|
+
// Thread 1 should NOT have the message with that id
|
|
664
|
+
expect(thread1Messages.find(m => m.id === message.id)).toBeUndefined();
|
|
665
|
+
|
|
666
|
+
// Thread 2 should have the message with that id
|
|
667
|
+
expect(thread2Messages.find(m => m.id === message.id)?.content.content).toBe('Thread2 Content');
|
|
668
|
+
});
|
|
555
669
|
});
|
|
556
670
|
|
|
557
671
|
describe('Edge Cases and Error Handling', () => {
|
|
@@ -1576,7 +1690,6 @@ describe('PostgresStore', () => {
|
|
|
1576
1690
|
selectBy: { pagination: { page: 0, perPage: 5 } },
|
|
1577
1691
|
format: 'v2',
|
|
1578
1692
|
});
|
|
1579
|
-
console.log(page1);
|
|
1580
1693
|
expect(page1.messages).toHaveLength(5);
|
|
1581
1694
|
expect(page1.total).toBe(15);
|
|
1582
1695
|
expect(page1.page).toBe(0);
|
|
@@ -1647,6 +1760,286 @@ describe('PostgresStore', () => {
|
|
|
1647
1760
|
);
|
|
1648
1761
|
}
|
|
1649
1762
|
});
|
|
1763
|
+
|
|
1764
|
+
it('should save and retrieve messages', async () => {
|
|
1765
|
+
const thread = createSampleThread();
|
|
1766
|
+
await store.saveThread({ thread });
|
|
1767
|
+
|
|
1768
|
+
const messages = [
|
|
1769
|
+
createSampleMessageV1({ threadId: thread.id }),
|
|
1770
|
+
createSampleMessageV1({ threadId: thread.id }),
|
|
1771
|
+
];
|
|
1772
|
+
|
|
1773
|
+
// Save messages
|
|
1774
|
+
const savedMessages = await store.saveMessages({ messages });
|
|
1775
|
+
expect(savedMessages).toEqual(messages);
|
|
1776
|
+
|
|
1777
|
+
// Retrieve messages
|
|
1778
|
+
const retrievedMessages = await store.getMessagesPaginated({ threadId: thread.id, format: 'v1' });
|
|
1779
|
+
expect(retrievedMessages.messages).toHaveLength(2);
|
|
1780
|
+
const checkMessages = messages.map(m => {
|
|
1781
|
+
const { resourceId, ...rest } = m;
|
|
1782
|
+
return rest;
|
|
1783
|
+
});
|
|
1784
|
+
expect(retrievedMessages.messages).toEqual(expect.arrayContaining(checkMessages));
|
|
1785
|
+
});
|
|
1786
|
+
|
|
1787
|
+
it('should maintain message order', async () => {
|
|
1788
|
+
const thread = createSampleThread();
|
|
1789
|
+
await store.saveThread({ thread });
|
|
1790
|
+
|
|
1791
|
+
const messageContent = ['First', 'Second', 'Third'];
|
|
1792
|
+
|
|
1793
|
+
const messages = messageContent.map(content =>
|
|
1794
|
+
createSampleMessageV2({
|
|
1795
|
+
threadId: thread.id,
|
|
1796
|
+
content: { content, parts: [{ type: 'text', text: content }] },
|
|
1797
|
+
}),
|
|
1798
|
+
);
|
|
1799
|
+
|
|
1800
|
+
await store.saveMessages({ messages, format: 'v2' });
|
|
1801
|
+
|
|
1802
|
+
const retrievedMessages = await store.getMessagesPaginated({ threadId: thread.id, format: 'v2' });
|
|
1803
|
+
expect(retrievedMessages.messages).toHaveLength(3);
|
|
1804
|
+
|
|
1805
|
+
// Verify order is maintained
|
|
1806
|
+
retrievedMessages.messages.forEach((msg, idx) => {
|
|
1807
|
+
expect((msg.content.parts[0] as any).text).toEqual(messageContent[idx]);
|
|
1808
|
+
});
|
|
1809
|
+
});
|
|
1810
|
+
|
|
1811
|
+
it('should rollback on error during message save', async () => {
|
|
1812
|
+
const thread = createSampleThread();
|
|
1813
|
+
await store.saveThread({ thread });
|
|
1814
|
+
|
|
1815
|
+
const messages = [
|
|
1816
|
+
createSampleMessageV1({ threadId: thread.id }),
|
|
1817
|
+
{ ...createSampleMessageV1({ threadId: thread.id }), id: null } as any, // This will cause an error
|
|
1818
|
+
];
|
|
1819
|
+
|
|
1820
|
+
await expect(store.saveMessages({ messages })).rejects.toThrow();
|
|
1821
|
+
|
|
1822
|
+
// Verify no messages were saved
|
|
1823
|
+
const savedMessages = await store.getMessagesPaginated({ threadId: thread.id, format: 'v2' });
|
|
1824
|
+
expect(savedMessages.messages).toHaveLength(0);
|
|
1825
|
+
});
|
|
1826
|
+
|
|
1827
|
+
it('should retrieve messages w/ next/prev messages by message id + resource id', async () => {
|
|
1828
|
+
const thread = createSampleThread({ id: 'thread-one' });
|
|
1829
|
+
await store.saveThread({ thread });
|
|
1830
|
+
|
|
1831
|
+
const thread2 = createSampleThread({ id: 'thread-two' });
|
|
1832
|
+
await store.saveThread({ thread: thread2 });
|
|
1833
|
+
|
|
1834
|
+
const thread3 = createSampleThread({ id: 'thread-three' });
|
|
1835
|
+
await store.saveThread({ thread: thread3 });
|
|
1836
|
+
|
|
1837
|
+
const messages: MastraMessageV2[] = [
|
|
1838
|
+
createSampleMessageV2({
|
|
1839
|
+
threadId: 'thread-one',
|
|
1840
|
+
content: { content: 'First' },
|
|
1841
|
+
resourceId: 'cross-thread-resource',
|
|
1842
|
+
}),
|
|
1843
|
+
createSampleMessageV2({
|
|
1844
|
+
threadId: 'thread-one',
|
|
1845
|
+
content: { content: 'Second' },
|
|
1846
|
+
resourceId: 'cross-thread-resource',
|
|
1847
|
+
}),
|
|
1848
|
+
createSampleMessageV2({
|
|
1849
|
+
threadId: 'thread-one',
|
|
1850
|
+
content: { content: 'Third' },
|
|
1851
|
+
resourceId: 'cross-thread-resource',
|
|
1852
|
+
}),
|
|
1853
|
+
|
|
1854
|
+
createSampleMessageV2({
|
|
1855
|
+
threadId: 'thread-two',
|
|
1856
|
+
content: { content: 'Fourth' },
|
|
1857
|
+
resourceId: 'cross-thread-resource',
|
|
1858
|
+
}),
|
|
1859
|
+
createSampleMessageV2({
|
|
1860
|
+
threadId: 'thread-two',
|
|
1861
|
+
content: { content: 'Fifth' },
|
|
1862
|
+
resourceId: 'cross-thread-resource',
|
|
1863
|
+
}),
|
|
1864
|
+
createSampleMessageV2({
|
|
1865
|
+
threadId: 'thread-two',
|
|
1866
|
+
content: { content: 'Sixth' },
|
|
1867
|
+
resourceId: 'cross-thread-resource',
|
|
1868
|
+
}),
|
|
1869
|
+
|
|
1870
|
+
createSampleMessageV2({
|
|
1871
|
+
threadId: 'thread-three',
|
|
1872
|
+
content: { content: 'Seventh' },
|
|
1873
|
+
resourceId: 'other-resource',
|
|
1874
|
+
}),
|
|
1875
|
+
createSampleMessageV2({
|
|
1876
|
+
threadId: 'thread-three',
|
|
1877
|
+
content: { content: 'Eighth' },
|
|
1878
|
+
resourceId: 'other-resource',
|
|
1879
|
+
}),
|
|
1880
|
+
];
|
|
1881
|
+
|
|
1882
|
+
await store.saveMessages({ messages: messages, format: 'v2' });
|
|
1883
|
+
|
|
1884
|
+
const retrievedMessages = await store.getMessagesPaginated({ threadId: 'thread-one', format: 'v2' });
|
|
1885
|
+
expect(retrievedMessages.messages).toHaveLength(3);
|
|
1886
|
+
expect(retrievedMessages.messages.map((m: any) => m.content.parts[0].text)).toEqual([
|
|
1887
|
+
'First',
|
|
1888
|
+
'Second',
|
|
1889
|
+
'Third',
|
|
1890
|
+
]);
|
|
1891
|
+
|
|
1892
|
+
const retrievedMessages2 = await store.getMessagesPaginated({ threadId: 'thread-two', format: 'v2' });
|
|
1893
|
+
expect(retrievedMessages2.messages).toHaveLength(3);
|
|
1894
|
+
expect(retrievedMessages2.messages.map((m: any) => m.content.parts[0].text)).toEqual([
|
|
1895
|
+
'Fourth',
|
|
1896
|
+
'Fifth',
|
|
1897
|
+
'Sixth',
|
|
1898
|
+
]);
|
|
1899
|
+
|
|
1900
|
+
const retrievedMessages3 = await store.getMessagesPaginated({ threadId: 'thread-three', format: 'v2' });
|
|
1901
|
+
expect(retrievedMessages3.messages).toHaveLength(2);
|
|
1902
|
+
expect(retrievedMessages3.messages.map((m: any) => m.content.parts[0].text)).toEqual(['Seventh', 'Eighth']);
|
|
1903
|
+
|
|
1904
|
+
const { messages: crossThreadMessages } = await store.getMessagesPaginated({
|
|
1905
|
+
threadId: 'thread-doesnt-exist',
|
|
1906
|
+
format: 'v2',
|
|
1907
|
+
selectBy: {
|
|
1908
|
+
last: 0,
|
|
1909
|
+
include: [
|
|
1910
|
+
{
|
|
1911
|
+
id: messages[1].id,
|
|
1912
|
+
threadId: 'thread-one',
|
|
1913
|
+
withNextMessages: 2,
|
|
1914
|
+
withPreviousMessages: 2,
|
|
1915
|
+
},
|
|
1916
|
+
{
|
|
1917
|
+
id: messages[4].id,
|
|
1918
|
+
threadId: 'thread-two',
|
|
1919
|
+
withPreviousMessages: 2,
|
|
1920
|
+
withNextMessages: 2,
|
|
1921
|
+
},
|
|
1922
|
+
],
|
|
1923
|
+
},
|
|
1924
|
+
});
|
|
1925
|
+
|
|
1926
|
+
expect(crossThreadMessages).toHaveLength(6);
|
|
1927
|
+
expect(crossThreadMessages.filter(m => m.threadId === `thread-one`)).toHaveLength(3);
|
|
1928
|
+
expect(crossThreadMessages.filter(m => m.threadId === `thread-two`)).toHaveLength(3);
|
|
1929
|
+
|
|
1930
|
+
const crossThreadMessages2 = await store.getMessagesPaginated({
|
|
1931
|
+
threadId: 'thread-one',
|
|
1932
|
+
format: 'v2',
|
|
1933
|
+
selectBy: {
|
|
1934
|
+
last: 0,
|
|
1935
|
+
include: [
|
|
1936
|
+
{
|
|
1937
|
+
id: messages[4].id,
|
|
1938
|
+
threadId: 'thread-two',
|
|
1939
|
+
withPreviousMessages: 1,
|
|
1940
|
+
withNextMessages: 1,
|
|
1941
|
+
},
|
|
1942
|
+
],
|
|
1943
|
+
},
|
|
1944
|
+
});
|
|
1945
|
+
|
|
1946
|
+
expect(crossThreadMessages2.messages).toHaveLength(3);
|
|
1947
|
+
expect(crossThreadMessages2.messages.filter(m => m.threadId === `thread-one`)).toHaveLength(0);
|
|
1948
|
+
expect(crossThreadMessages2.messages.filter(m => m.threadId === `thread-two`)).toHaveLength(3);
|
|
1949
|
+
|
|
1950
|
+
const crossThreadMessages3 = await store.getMessagesPaginated({
|
|
1951
|
+
threadId: 'thread-two',
|
|
1952
|
+
format: 'v2',
|
|
1953
|
+
selectBy: {
|
|
1954
|
+
last: 0,
|
|
1955
|
+
include: [
|
|
1956
|
+
{
|
|
1957
|
+
id: messages[1].id,
|
|
1958
|
+
threadId: 'thread-one',
|
|
1959
|
+
withNextMessages: 1,
|
|
1960
|
+
withPreviousMessages: 1,
|
|
1961
|
+
},
|
|
1962
|
+
],
|
|
1963
|
+
},
|
|
1964
|
+
});
|
|
1965
|
+
|
|
1966
|
+
expect(crossThreadMessages3.messages).toHaveLength(3);
|
|
1967
|
+
expect(crossThreadMessages3.messages.filter(m => m.threadId === `thread-one`)).toHaveLength(3);
|
|
1968
|
+
expect(crossThreadMessages3.messages.filter(m => m.threadId === `thread-two`)).toHaveLength(0);
|
|
1969
|
+
});
|
|
1970
|
+
|
|
1971
|
+
it('should return messages using both last and include (cross-thread, deduped)', async () => {
|
|
1972
|
+
const thread = createSampleThread({ id: 'thread-one' });
|
|
1973
|
+
await store.saveThread({ thread });
|
|
1974
|
+
|
|
1975
|
+
const thread2 = createSampleThread({ id: 'thread-two' });
|
|
1976
|
+
await store.saveThread({ thread: thread2 });
|
|
1977
|
+
|
|
1978
|
+
const now = new Date();
|
|
1979
|
+
|
|
1980
|
+
// Setup: create messages in two threads
|
|
1981
|
+
const messages = [
|
|
1982
|
+
createSampleMessageV2({
|
|
1983
|
+
threadId: 'thread-one',
|
|
1984
|
+
content: { content: 'A' },
|
|
1985
|
+
createdAt: new Date(now.getTime()),
|
|
1986
|
+
}),
|
|
1987
|
+
createSampleMessageV2({
|
|
1988
|
+
threadId: 'thread-one',
|
|
1989
|
+
content: { content: 'B' },
|
|
1990
|
+
createdAt: new Date(now.getTime() + 1000),
|
|
1991
|
+
}),
|
|
1992
|
+
createSampleMessageV2({
|
|
1993
|
+
threadId: 'thread-one',
|
|
1994
|
+
content: { content: 'C' },
|
|
1995
|
+
createdAt: new Date(now.getTime() + 2000),
|
|
1996
|
+
}),
|
|
1997
|
+
createSampleMessageV2({
|
|
1998
|
+
threadId: 'thread-two',
|
|
1999
|
+
content: { content: 'D' },
|
|
2000
|
+
createdAt: new Date(now.getTime() + 3000),
|
|
2001
|
+
}),
|
|
2002
|
+
createSampleMessageV2({
|
|
2003
|
+
threadId: 'thread-two',
|
|
2004
|
+
content: { content: 'E' },
|
|
2005
|
+
createdAt: new Date(now.getTime() + 4000),
|
|
2006
|
+
}),
|
|
2007
|
+
createSampleMessageV2({
|
|
2008
|
+
threadId: 'thread-two',
|
|
2009
|
+
content: { content: 'F' },
|
|
2010
|
+
createdAt: new Date(now.getTime() + 5000),
|
|
2011
|
+
}),
|
|
2012
|
+
];
|
|
2013
|
+
await store.saveMessages({ messages, format: 'v2' });
|
|
2014
|
+
|
|
2015
|
+
// Use last: 2 and include a message from another thread with context
|
|
2016
|
+
const { messages: result } = await store.getMessagesPaginated({
|
|
2017
|
+
threadId: 'thread-one',
|
|
2018
|
+
format: 'v2',
|
|
2019
|
+
selectBy: {
|
|
2020
|
+
last: 2,
|
|
2021
|
+
include: [
|
|
2022
|
+
{
|
|
2023
|
+
id: messages[4].id, // 'E' from thread-bar
|
|
2024
|
+
threadId: 'thread-two',
|
|
2025
|
+
withPreviousMessages: 1,
|
|
2026
|
+
withNextMessages: 1,
|
|
2027
|
+
},
|
|
2028
|
+
],
|
|
2029
|
+
},
|
|
2030
|
+
});
|
|
2031
|
+
|
|
2032
|
+
// Should include last 2 from thread-one and 3 from thread-two (D, E, F)
|
|
2033
|
+
expect(result.map(m => m.content.content).sort()).toEqual(['B', 'C', 'D', 'E', 'F']);
|
|
2034
|
+
// Should include 2 from thread-one
|
|
2035
|
+
expect(result.filter(m => m.threadId === 'thread-one').map((m: any) => m.content.content)).toEqual(['B', 'C']);
|
|
2036
|
+
// Should include 3 from thread-two
|
|
2037
|
+
expect(result.filter(m => m.threadId === 'thread-two').map((m: any) => m.content.content)).toEqual([
|
|
2038
|
+
'D',
|
|
2039
|
+
'E',
|
|
2040
|
+
'F',
|
|
2041
|
+
]);
|
|
2042
|
+
});
|
|
1650
2043
|
});
|
|
1651
2044
|
|
|
1652
2045
|
describe('getThreadsByResourceId with pagination', () => {
|
|
@@ -1730,7 +2123,7 @@ describe('PostgresStore', () => {
|
|
|
1730
2123
|
record: { id: '1', name: 'Alice' },
|
|
1731
2124
|
});
|
|
1732
2125
|
|
|
1733
|
-
const row = await store.load({
|
|
2126
|
+
const row: any = await store.load({
|
|
1734
2127
|
tableName: camelCaseTable as TABLE_NAMES,
|
|
1735
2128
|
keys: { id: '1' },
|
|
1736
2129
|
});
|
|
@@ -1750,7 +2143,7 @@ describe('PostgresStore', () => {
|
|
|
1750
2143
|
record: { id: '2', name: 'Bob' },
|
|
1751
2144
|
});
|
|
1752
2145
|
|
|
1753
|
-
const row = await store.load({
|
|
2146
|
+
const row: any = await store.load({
|
|
1754
2147
|
tableName: snakeCaseTable as TABLE_NAMES,
|
|
1755
2148
|
keys: { id: '2' },
|
|
1756
2149
|
});
|