@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.
- package/.turbo/turbo-build.log +8 -8
- package/CHANGELOG.md +16 -0
- package/dist/_tsup-dts-rollup.d.cts +68 -5
- package/dist/_tsup-dts-rollup.d.ts +68 -5
- package/dist/index.cjs +307 -144
- package/dist/index.js +307 -144
- package/package.json +11 -10
- package/src/storage/index.test.ts +472 -52
- package/src/storage/index.ts +459 -179
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { randomUUID } from 'crypto';
|
|
2
|
-
import
|
|
2
|
+
import { createSampleEval, createSampleTraceForDB } from '@internal/storage-test-utils';
|
|
3
3
|
import type { MastraMessageV1 } from '@mastra/core/memory';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
TABLE_WORKFLOW_SNAPSHOT,
|
|
6
|
+
TABLE_MESSAGES,
|
|
7
|
+
TABLE_THREADS,
|
|
8
|
+
TABLE_EVALS,
|
|
9
|
+
TABLE_TRACES,
|
|
10
|
+
} from '@mastra/core/storage';
|
|
5
11
|
import type { WorkflowRunState } from '@mastra/core/workflows';
|
|
6
12
|
import pgPromise from 'pg-promise';
|
|
7
13
|
import { describe, it, expect, beforeAll, beforeEach, afterAll, afterEach, vi } from 'vitest';
|
|
@@ -73,24 +79,6 @@ const createSampleWorkflowSnapshot = (status: WorkflowRunState['context'][string
|
|
|
73
79
|
return { snapshot, runId, stepId };
|
|
74
80
|
};
|
|
75
81
|
|
|
76
|
-
const createSampleEval = (agentName: string, isTest = false) => {
|
|
77
|
-
const testInfo = isTest ? { testPath: 'test/path.ts', testName: 'Test Name' } : undefined;
|
|
78
|
-
|
|
79
|
-
return {
|
|
80
|
-
id: randomUUID(),
|
|
81
|
-
agentName,
|
|
82
|
-
input: 'Sample input',
|
|
83
|
-
output: 'Sample output',
|
|
84
|
-
result: { score: 0.8 } as MetricResult,
|
|
85
|
-
metricName: 'sample-metric',
|
|
86
|
-
instructions: 'Sample instructions',
|
|
87
|
-
testInfo,
|
|
88
|
-
globalRunId: `global-${randomUUID()}`,
|
|
89
|
-
runId: `run-${randomUUID()}`,
|
|
90
|
-
createdAt: new Date().toISOString(),
|
|
91
|
-
};
|
|
92
|
-
};
|
|
93
|
-
|
|
94
82
|
const checkWorkflowSnapshot = (snapshot: WorkflowRunState | string, stepId: string, status: string) => {
|
|
95
83
|
if (typeof snapshot === 'string') {
|
|
96
84
|
throw new Error('Expected WorkflowRunState, got string');
|
|
@@ -114,6 +102,7 @@ describe('PostgresStore', () => {
|
|
|
114
102
|
await store.clearTable({ tableName: TABLE_MESSAGES });
|
|
115
103
|
await store.clearTable({ tableName: TABLE_THREADS });
|
|
116
104
|
await store.clearTable({ tableName: TABLE_EVALS });
|
|
105
|
+
await store.clearTable({ tableName: TABLE_TRACES });
|
|
117
106
|
} catch (error) {
|
|
118
107
|
// Ignore errors during table clearing
|
|
119
108
|
console.warn('Error clearing tables:', error);
|
|
@@ -247,7 +236,7 @@ describe('PostgresStore', () => {
|
|
|
247
236
|
expect(savedMessages).toEqual(messages);
|
|
248
237
|
|
|
249
238
|
// Retrieve messages
|
|
250
|
-
const retrievedMessages = await store.getMessages({ threadId: thread.id });
|
|
239
|
+
const retrievedMessages = await store.getMessages({ threadId: thread.id, format: 'v1' });
|
|
251
240
|
expect(retrievedMessages).toHaveLength(2);
|
|
252
241
|
const checkMessages = messages.map(m => {
|
|
253
242
|
const { resourceId, ...rest } = m;
|
|
@@ -276,7 +265,7 @@ describe('PostgresStore', () => {
|
|
|
276
265
|
|
|
277
266
|
await store.saveMessages({ messages });
|
|
278
267
|
|
|
279
|
-
const retrievedMessages = await store.getMessages
|
|
268
|
+
const retrievedMessages = await store.getMessages({ threadId: thread.id, format: 'v1' });
|
|
280
269
|
expect(retrievedMessages).toHaveLength(3);
|
|
281
270
|
|
|
282
271
|
// Verify order is maintained
|
|
@@ -301,6 +290,43 @@ describe('PostgresStore', () => {
|
|
|
301
290
|
const savedMessages = await store.getMessages({ threadId: thread.id });
|
|
302
291
|
expect(savedMessages).toHaveLength(0);
|
|
303
292
|
});
|
|
293
|
+
|
|
294
|
+
it('should filter by date with pagination for getMessages', async () => {
|
|
295
|
+
const thread = createSampleThread();
|
|
296
|
+
await store.saveThread({ thread });
|
|
297
|
+
const now = new Date();
|
|
298
|
+
const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
|
299
|
+
const dayBeforeYesterday = new Date(now.getTime() - 48 * 60 * 60 * 1000);
|
|
300
|
+
|
|
301
|
+
const createMsgAtDate = (date: Date) => {
|
|
302
|
+
return store.saveMessages({ messages: [{ ...createSampleMessage(thread.id), createdAt: date }] });
|
|
303
|
+
};
|
|
304
|
+
await Promise.all([
|
|
305
|
+
createMsgAtDate(dayBeforeYesterday),
|
|
306
|
+
createMsgAtDate(dayBeforeYesterday),
|
|
307
|
+
createMsgAtDate(yesterday),
|
|
308
|
+
createMsgAtDate(yesterday),
|
|
309
|
+
createMsgAtDate(yesterday),
|
|
310
|
+
createMsgAtDate(now),
|
|
311
|
+
createMsgAtDate(now),
|
|
312
|
+
]);
|
|
313
|
+
|
|
314
|
+
const resultPage = await store.getMessages({
|
|
315
|
+
threadId: thread.id,
|
|
316
|
+
fromDate: yesterday,
|
|
317
|
+
page: 0,
|
|
318
|
+
perPage: 3,
|
|
319
|
+
format: 'v1',
|
|
320
|
+
});
|
|
321
|
+
expect(resultPage.total).toBe(5);
|
|
322
|
+
expect(resultPage.messages).toHaveLength(3);
|
|
323
|
+
|
|
324
|
+
expect(new Date((resultPage.messages[0] as MastraMessageV1).createdAt).toISOString()).toBe(now.toISOString());
|
|
325
|
+
expect(new Date((resultPage.messages[1] as MastraMessageV1).createdAt).toISOString()).toBe(now.toISOString());
|
|
326
|
+
expect(new Date((resultPage.messages[2] as MastraMessageV1).createdAt).toISOString()).toBe(
|
|
327
|
+
yesterday.toISOString(),
|
|
328
|
+
);
|
|
329
|
+
});
|
|
304
330
|
});
|
|
305
331
|
|
|
306
332
|
describe('Edge Cases and Error Handling', () => {
|
|
@@ -752,78 +778,76 @@ describe('PostgresStore', () => {
|
|
|
752
778
|
it('should retrieve evals by agent name', async () => {
|
|
753
779
|
const agentName = `test-agent-${randomUUID()}`;
|
|
754
780
|
|
|
755
|
-
// Create sample evals
|
|
756
|
-
const liveEval = createSampleEval(agentName, false);
|
|
781
|
+
// Create sample evals using the imported helper
|
|
782
|
+
const liveEval = createSampleEval(agentName, false); // createSampleEval returns snake_case
|
|
757
783
|
const testEval = createSampleEval(agentName, true);
|
|
758
784
|
const otherAgentEval = createSampleEval(`other-agent-${randomUUID()}`, false);
|
|
759
785
|
|
|
760
|
-
// Insert evals
|
|
786
|
+
// Insert evals - ensure DB columns are snake_case
|
|
761
787
|
await store.insert({
|
|
762
788
|
tableName: TABLE_EVALS,
|
|
763
789
|
record: {
|
|
764
|
-
agent_name: liveEval.
|
|
790
|
+
agent_name: liveEval.agent_name, // Use snake_case
|
|
765
791
|
input: liveEval.input,
|
|
766
792
|
output: liveEval.output,
|
|
767
793
|
result: liveEval.result,
|
|
768
|
-
metric_name: liveEval.
|
|
794
|
+
metric_name: liveEval.metric_name, // Use snake_case
|
|
769
795
|
instructions: liveEval.instructions,
|
|
770
|
-
test_info:
|
|
771
|
-
global_run_id: liveEval.
|
|
772
|
-
run_id: liveEval.
|
|
773
|
-
created_at: liveEval.
|
|
774
|
-
createdAt: new Date(liveEval.createdAt),
|
|
796
|
+
test_info: liveEval.test_info, // test_info from helper can be undefined or object
|
|
797
|
+
global_run_id: liveEval.global_run_id, // Use snake_case
|
|
798
|
+
run_id: liveEval.run_id, // Use snake_case
|
|
799
|
+
created_at: new Date(liveEval.created_at as string), // created_at from helper is string or Date
|
|
775
800
|
},
|
|
776
801
|
});
|
|
777
802
|
|
|
778
803
|
await store.insert({
|
|
779
804
|
tableName: TABLE_EVALS,
|
|
780
805
|
record: {
|
|
781
|
-
agent_name: testEval.
|
|
806
|
+
agent_name: testEval.agent_name,
|
|
782
807
|
input: testEval.input,
|
|
783
808
|
output: testEval.output,
|
|
784
809
|
result: testEval.result,
|
|
785
|
-
metric_name: testEval.
|
|
810
|
+
metric_name: testEval.metric_name,
|
|
786
811
|
instructions: testEval.instructions,
|
|
787
|
-
test_info: JSON.stringify(testEval.
|
|
788
|
-
global_run_id: testEval.
|
|
789
|
-
run_id: testEval.
|
|
790
|
-
created_at: testEval.
|
|
791
|
-
createdAt: new Date(testEval.createdAt),
|
|
812
|
+
test_info: testEval.test_info ? JSON.stringify(testEval.test_info) : null,
|
|
813
|
+
global_run_id: testEval.global_run_id,
|
|
814
|
+
run_id: testEval.run_id,
|
|
815
|
+
created_at: new Date(testEval.created_at as string),
|
|
792
816
|
},
|
|
793
817
|
});
|
|
794
818
|
|
|
795
819
|
await store.insert({
|
|
796
820
|
tableName: TABLE_EVALS,
|
|
797
821
|
record: {
|
|
798
|
-
agent_name: otherAgentEval.
|
|
822
|
+
agent_name: otherAgentEval.agent_name,
|
|
799
823
|
input: otherAgentEval.input,
|
|
800
824
|
output: otherAgentEval.output,
|
|
801
825
|
result: otherAgentEval.result,
|
|
802
|
-
metric_name: otherAgentEval.
|
|
826
|
+
metric_name: otherAgentEval.metric_name,
|
|
803
827
|
instructions: otherAgentEval.instructions,
|
|
804
|
-
test_info: null
|
|
805
|
-
global_run_id: otherAgentEval.
|
|
806
|
-
run_id: otherAgentEval.
|
|
807
|
-
created_at: otherAgentEval.
|
|
808
|
-
createdAt: new Date(otherAgentEval.createdAt),
|
|
828
|
+
test_info: otherAgentEval.test_info, // Can be null/undefined directly
|
|
829
|
+
global_run_id: otherAgentEval.global_run_id,
|
|
830
|
+
run_id: otherAgentEval.run_id,
|
|
831
|
+
created_at: new Date(otherAgentEval.created_at as string),
|
|
809
832
|
},
|
|
810
833
|
});
|
|
811
834
|
|
|
812
835
|
// Test getting all evals for the agent
|
|
813
836
|
const allEvals = await store.getEvalsByAgentName(agentName);
|
|
814
837
|
expect(allEvals).toHaveLength(2);
|
|
815
|
-
|
|
838
|
+
// EvalRow type expects camelCase, but PostgresStore.transformEvalRow converts snake_case from DB to camelCase
|
|
839
|
+
expect(allEvals.map(e => e.runId)).toEqual(expect.arrayContaining([liveEval.run_id, testEval.run_id]));
|
|
816
840
|
|
|
817
841
|
// Test getting only live evals
|
|
818
842
|
const liveEvals = await store.getEvalsByAgentName(agentName, 'live');
|
|
819
843
|
expect(liveEvals).toHaveLength(1);
|
|
820
|
-
expect(liveEvals[0].runId).toBe(liveEval.
|
|
844
|
+
expect(liveEvals[0].runId).toBe(liveEval.run_id); // Comparing with snake_case run_id from original data
|
|
821
845
|
|
|
822
846
|
// Test getting only test evals
|
|
823
|
-
const
|
|
824
|
-
expect(
|
|
825
|
-
expect(
|
|
826
|
-
expect(
|
|
847
|
+
const testEvalsResult = await store.getEvalsByAgentName(agentName, 'test');
|
|
848
|
+
expect(testEvalsResult).toHaveLength(1);
|
|
849
|
+
expect(testEvalsResult[0].runId).toBe(testEval.run_id);
|
|
850
|
+
expect(testEvalsResult[0].testInfo).toEqual(testEval.test_info);
|
|
827
851
|
|
|
828
852
|
// Test getting evals for non-existent agent
|
|
829
853
|
const nonExistentEvals = await store.getEvalsByAgentName('non-existent-agent');
|
|
@@ -931,6 +955,402 @@ describe('PostgresStore', () => {
|
|
|
931
955
|
});
|
|
932
956
|
});
|
|
933
957
|
|
|
958
|
+
describe('Pagination Features', () => {
|
|
959
|
+
beforeEach(async () => {
|
|
960
|
+
await store.clearTable({ tableName: TABLE_EVALS });
|
|
961
|
+
await store.clearTable({ tableName: TABLE_TRACES });
|
|
962
|
+
await store.clearTable({ tableName: TABLE_MESSAGES });
|
|
963
|
+
await store.clearTable({ tableName: TABLE_THREADS });
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
describe('getEvals with pagination', () => {
|
|
967
|
+
it('should return paginated evals with total count (page/perPage)', async () => {
|
|
968
|
+
const agentName = 'pagination-agent-evals';
|
|
969
|
+
const evalPromises = Array.from({ length: 25 }, (_, i) => {
|
|
970
|
+
const evalData = createSampleEval(agentName, i % 2 === 0);
|
|
971
|
+
return store.insert({
|
|
972
|
+
tableName: TABLE_EVALS,
|
|
973
|
+
record: {
|
|
974
|
+
run_id: evalData.run_id,
|
|
975
|
+
agent_name: evalData.agent_name,
|
|
976
|
+
input: evalData.input,
|
|
977
|
+
output: evalData.output,
|
|
978
|
+
result: evalData.result,
|
|
979
|
+
metric_name: evalData.metric_name,
|
|
980
|
+
instructions: evalData.instructions,
|
|
981
|
+
test_info: evalData.test_info,
|
|
982
|
+
global_run_id: evalData.global_run_id,
|
|
983
|
+
created_at: new Date(evalData.created_at as string),
|
|
984
|
+
},
|
|
985
|
+
});
|
|
986
|
+
});
|
|
987
|
+
await Promise.all(evalPromises);
|
|
988
|
+
|
|
989
|
+
const page1 = await store.getEvals({ agentName, page: 0, perPage: 10 });
|
|
990
|
+
expect(page1.evals).toHaveLength(10);
|
|
991
|
+
expect(page1.total).toBe(25);
|
|
992
|
+
expect(page1.page).toBe(0);
|
|
993
|
+
expect(page1.perPage).toBe(10);
|
|
994
|
+
expect(page1.hasMore).toBe(true);
|
|
995
|
+
|
|
996
|
+
const page3 = await store.getEvals({ agentName, page: 2, perPage: 10 });
|
|
997
|
+
expect(page3.evals).toHaveLength(5);
|
|
998
|
+
expect(page3.total).toBe(25);
|
|
999
|
+
expect(page3.page).toBe(2);
|
|
1000
|
+
expect(page3.hasMore).toBe(false);
|
|
1001
|
+
});
|
|
1002
|
+
|
|
1003
|
+
it('should support limit/offset pagination for getEvals', async () => {
|
|
1004
|
+
const agentName = 'pagination-agent-lo-evals';
|
|
1005
|
+
const evalPromises = Array.from({ length: 15 }, () => {
|
|
1006
|
+
const evalData = createSampleEval(agentName);
|
|
1007
|
+
return store.insert({
|
|
1008
|
+
tableName: TABLE_EVALS,
|
|
1009
|
+
record: {
|
|
1010
|
+
run_id: evalData.run_id,
|
|
1011
|
+
agent_name: evalData.agent_name,
|
|
1012
|
+
input: evalData.input,
|
|
1013
|
+
output: evalData.output,
|
|
1014
|
+
result: evalData.result,
|
|
1015
|
+
metric_name: evalData.metric_name,
|
|
1016
|
+
instructions: evalData.instructions,
|
|
1017
|
+
test_info: evalData.test_info,
|
|
1018
|
+
global_run_id: evalData.global_run_id,
|
|
1019
|
+
created_at: new Date(evalData.created_at as string),
|
|
1020
|
+
},
|
|
1021
|
+
});
|
|
1022
|
+
});
|
|
1023
|
+
await Promise.all(evalPromises);
|
|
1024
|
+
|
|
1025
|
+
const result = await store.getEvals({ agentName, limit: 5, offset: 10 });
|
|
1026
|
+
expect(result.evals).toHaveLength(5);
|
|
1027
|
+
expect(result.total).toBe(15);
|
|
1028
|
+
expect(result.page).toBeUndefined(); // Page is undefined for limit/offset
|
|
1029
|
+
expect(result.perPage).toBeUndefined(); // PerPage is undefined for limit/offset
|
|
1030
|
+
expect(result.hasMore).toBe(false);
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
it('should filter by type with pagination for getEvals', async () => {
|
|
1034
|
+
const agentName = 'pagination-agent-type-evals';
|
|
1035
|
+
const testEvalPromises = Array.from({ length: 10 }, () => {
|
|
1036
|
+
const evalData = createSampleEval(agentName, true);
|
|
1037
|
+
return store.insert({
|
|
1038
|
+
tableName: TABLE_EVALS,
|
|
1039
|
+
record: {
|
|
1040
|
+
run_id: evalData.run_id,
|
|
1041
|
+
agent_name: evalData.agent_name,
|
|
1042
|
+
input: evalData.input,
|
|
1043
|
+
output: evalData.output,
|
|
1044
|
+
result: evalData.result,
|
|
1045
|
+
metric_name: evalData.metric_name,
|
|
1046
|
+
instructions: evalData.instructions,
|
|
1047
|
+
test_info: evalData.test_info,
|
|
1048
|
+
global_run_id: evalData.global_run_id,
|
|
1049
|
+
created_at: new Date(evalData.created_at as string),
|
|
1050
|
+
},
|
|
1051
|
+
});
|
|
1052
|
+
});
|
|
1053
|
+
const liveEvalPromises = Array.from({ length: 8 }, () => {
|
|
1054
|
+
const evalData = createSampleEval(agentName, false);
|
|
1055
|
+
return store.insert({
|
|
1056
|
+
tableName: TABLE_EVALS,
|
|
1057
|
+
record: {
|
|
1058
|
+
run_id: evalData.run_id,
|
|
1059
|
+
agent_name: evalData.agent_name,
|
|
1060
|
+
input: evalData.input,
|
|
1061
|
+
output: evalData.output,
|
|
1062
|
+
result: evalData.result,
|
|
1063
|
+
metric_name: evalData.metric_name,
|
|
1064
|
+
instructions: evalData.instructions,
|
|
1065
|
+
test_info: evalData.test_info,
|
|
1066
|
+
global_run_id: evalData.global_run_id,
|
|
1067
|
+
created_at: new Date(evalData.created_at as string),
|
|
1068
|
+
},
|
|
1069
|
+
});
|
|
1070
|
+
});
|
|
1071
|
+
await Promise.all([...testEvalPromises, ...liveEvalPromises]);
|
|
1072
|
+
|
|
1073
|
+
const testResults = await store.getEvals({ agentName, type: 'test', page: 0, perPage: 5 });
|
|
1074
|
+
expect(testResults.evals).toHaveLength(5);
|
|
1075
|
+
expect(testResults.total).toBe(10);
|
|
1076
|
+
|
|
1077
|
+
const liveResults = await store.getEvals({ agentName, type: 'live', page: 1, perPage: 3 });
|
|
1078
|
+
expect(liveResults.evals).toHaveLength(3);
|
|
1079
|
+
expect(liveResults.total).toBe(8);
|
|
1080
|
+
expect(liveResults.hasMore).toBe(true);
|
|
1081
|
+
});
|
|
1082
|
+
|
|
1083
|
+
it('should filter by date with pagination for getEvals', async () => {
|
|
1084
|
+
const agentName = 'pagination-agent-date-evals';
|
|
1085
|
+
const now = new Date();
|
|
1086
|
+
const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
|
1087
|
+
const dayBeforeYesterday = new Date(now.getTime() - 48 * 60 * 60 * 1000);
|
|
1088
|
+
|
|
1089
|
+
const createEvalAtDate = (date: Date) => {
|
|
1090
|
+
const evalData = createSampleEval(agentName, false, date); // Pass date to helper
|
|
1091
|
+
return store.insert({
|
|
1092
|
+
tableName: TABLE_EVALS,
|
|
1093
|
+
record: {
|
|
1094
|
+
run_id: evalData.run_id, // Use snake_case from helper
|
|
1095
|
+
agent_name: evalData.agent_name,
|
|
1096
|
+
input: evalData.input,
|
|
1097
|
+
output: evalData.output,
|
|
1098
|
+
result: evalData.result,
|
|
1099
|
+
metric_name: evalData.metric_name,
|
|
1100
|
+
instructions: evalData.instructions,
|
|
1101
|
+
test_info: evalData.test_info,
|
|
1102
|
+
global_run_id: evalData.global_run_id,
|
|
1103
|
+
created_at: evalData.created_at, // Use created_at from helper (already Date or ISO string)
|
|
1104
|
+
},
|
|
1105
|
+
});
|
|
1106
|
+
};
|
|
1107
|
+
|
|
1108
|
+
await Promise.all([
|
|
1109
|
+
createEvalAtDate(dayBeforeYesterday),
|
|
1110
|
+
createEvalAtDate(dayBeforeYesterday),
|
|
1111
|
+
createEvalAtDate(yesterday),
|
|
1112
|
+
createEvalAtDate(yesterday),
|
|
1113
|
+
createEvalAtDate(yesterday),
|
|
1114
|
+
createEvalAtDate(now),
|
|
1115
|
+
createEvalAtDate(now),
|
|
1116
|
+
createEvalAtDate(now),
|
|
1117
|
+
createEvalAtDate(now),
|
|
1118
|
+
]);
|
|
1119
|
+
|
|
1120
|
+
const fromYesterday = await store.getEvals({ agentName, fromDate: yesterday, page: 0, perPage: 3 });
|
|
1121
|
+
expect(fromYesterday.total).toBe(7); // 3 yesterday + 4 now
|
|
1122
|
+
expect(fromYesterday.evals).toHaveLength(3);
|
|
1123
|
+
// Evals are sorted DESC, so first 3 are from 'now'
|
|
1124
|
+
fromYesterday.evals.forEach(e =>
|
|
1125
|
+
expect(new Date(e.createdAt).getTime()).toBeGreaterThanOrEqual(yesterday.getTime()),
|
|
1126
|
+
);
|
|
1127
|
+
|
|
1128
|
+
const onlyDayBefore = await store.getEvals({
|
|
1129
|
+
agentName,
|
|
1130
|
+
toDate: new Date(yesterday.getTime() - 1),
|
|
1131
|
+
page: 0,
|
|
1132
|
+
perPage: 5,
|
|
1133
|
+
});
|
|
1134
|
+
expect(onlyDayBefore.total).toBe(2);
|
|
1135
|
+
expect(onlyDayBefore.evals).toHaveLength(2);
|
|
1136
|
+
});
|
|
1137
|
+
});
|
|
1138
|
+
|
|
1139
|
+
describe('getTraces with pagination', () => {
|
|
1140
|
+
it('should return paginated traces with total count', async () => {
|
|
1141
|
+
const tracePromises = Array.from({ length: 18 }, (_, i) =>
|
|
1142
|
+
store.insert({ tableName: TABLE_TRACES, record: createSampleTraceForDB(`test-trace-${i}`, 'pg-test-scope') }),
|
|
1143
|
+
);
|
|
1144
|
+
await Promise.all(tracePromises);
|
|
1145
|
+
|
|
1146
|
+
const page1 = await store.getTraces({
|
|
1147
|
+
scope: 'pg-test-scope',
|
|
1148
|
+
page: 0,
|
|
1149
|
+
perPage: 8,
|
|
1150
|
+
returnPaginationResults: true,
|
|
1151
|
+
});
|
|
1152
|
+
expect(page1.traces).toHaveLength(8);
|
|
1153
|
+
expect(page1.total).toBe(18);
|
|
1154
|
+
expect(page1.page).toBe(0);
|
|
1155
|
+
expect(page1.perPage).toBe(8);
|
|
1156
|
+
expect(page1.hasMore).toBe(true);
|
|
1157
|
+
|
|
1158
|
+
const page3 = await store.getTraces({
|
|
1159
|
+
scope: 'pg-test-scope',
|
|
1160
|
+
page: 2,
|
|
1161
|
+
perPage: 8,
|
|
1162
|
+
returnPaginationResults: true,
|
|
1163
|
+
});
|
|
1164
|
+
expect(page3.traces).toHaveLength(2);
|
|
1165
|
+
expect(page3.total).toBe(18);
|
|
1166
|
+
expect(page3.hasMore).toBe(false);
|
|
1167
|
+
});
|
|
1168
|
+
|
|
1169
|
+
it('should filter by attributes with pagination for getTraces', async () => {
|
|
1170
|
+
const tracesWithAttr = Array.from({ length: 8 }, (_, i) =>
|
|
1171
|
+
store.insert({
|
|
1172
|
+
tableName: TABLE_TRACES,
|
|
1173
|
+
record: createSampleTraceForDB(`trace-${i}`, 'pg-attr-scope', { environment: 'prod' }),
|
|
1174
|
+
}),
|
|
1175
|
+
);
|
|
1176
|
+
const tracesWithoutAttr = Array.from({ length: 5 }, (_, i) =>
|
|
1177
|
+
store.insert({
|
|
1178
|
+
tableName: TABLE_TRACES,
|
|
1179
|
+
record: createSampleTraceForDB(`trace-other-${i}`, 'pg-attr-scope', { environment: 'dev' }),
|
|
1180
|
+
}),
|
|
1181
|
+
);
|
|
1182
|
+
await Promise.all([...tracesWithAttr, ...tracesWithoutAttr]);
|
|
1183
|
+
|
|
1184
|
+
const prodTraces = await store.getTraces({
|
|
1185
|
+
scope: 'pg-attr-scope',
|
|
1186
|
+
attributes: { environment: 'prod' },
|
|
1187
|
+
page: 0,
|
|
1188
|
+
perPage: 5,
|
|
1189
|
+
returnPaginationResults: true,
|
|
1190
|
+
});
|
|
1191
|
+
expect(prodTraces.traces).toHaveLength(5);
|
|
1192
|
+
expect(prodTraces.total).toBe(8);
|
|
1193
|
+
expect(prodTraces.hasMore).toBe(true);
|
|
1194
|
+
});
|
|
1195
|
+
|
|
1196
|
+
it('should filter by date with pagination for getTraces', async () => {
|
|
1197
|
+
const scope = 'pg-date-traces';
|
|
1198
|
+
const now = new Date();
|
|
1199
|
+
const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
|
1200
|
+
const dayBeforeYesterday = new Date(now.getTime() - 48 * 60 * 60 * 1000);
|
|
1201
|
+
|
|
1202
|
+
await Promise.all([
|
|
1203
|
+
store.insert({
|
|
1204
|
+
tableName: TABLE_TRACES,
|
|
1205
|
+
record: createSampleTraceForDB('t1', scope, undefined, dayBeforeYesterday),
|
|
1206
|
+
}),
|
|
1207
|
+
store.insert({ tableName: TABLE_TRACES, record: createSampleTraceForDB('t2', scope, undefined, yesterday) }),
|
|
1208
|
+
store.insert({ tableName: TABLE_TRACES, record: createSampleTraceForDB('t3', scope, undefined, yesterday) }),
|
|
1209
|
+
store.insert({ tableName: TABLE_TRACES, record: createSampleTraceForDB('t4', scope, undefined, now) }),
|
|
1210
|
+
store.insert({ tableName: TABLE_TRACES, record: createSampleTraceForDB('t5', scope, undefined, now) }),
|
|
1211
|
+
]);
|
|
1212
|
+
|
|
1213
|
+
const fromYesterday = await store.getTraces({
|
|
1214
|
+
scope,
|
|
1215
|
+
fromDate: yesterday,
|
|
1216
|
+
page: 0,
|
|
1217
|
+
perPage: 2,
|
|
1218
|
+
returnPaginationResults: true,
|
|
1219
|
+
});
|
|
1220
|
+
expect(fromYesterday.total).toBe(4); // 2 yesterday + 2 now
|
|
1221
|
+
expect(fromYesterday.traces).toHaveLength(2);
|
|
1222
|
+
fromYesterday.traces.forEach(t =>
|
|
1223
|
+
expect(new Date(t.createdAt).getTime()).toBeGreaterThanOrEqual(yesterday.getTime()),
|
|
1224
|
+
);
|
|
1225
|
+
|
|
1226
|
+
const onlyNow = await store.getTraces({
|
|
1227
|
+
scope,
|
|
1228
|
+
fromDate: now,
|
|
1229
|
+
toDate: now,
|
|
1230
|
+
page: 0,
|
|
1231
|
+
perPage: 5,
|
|
1232
|
+
returnPaginationResults: true,
|
|
1233
|
+
});
|
|
1234
|
+
expect(onlyNow.total).toBe(2);
|
|
1235
|
+
expect(onlyNow.traces).toHaveLength(2);
|
|
1236
|
+
});
|
|
1237
|
+
|
|
1238
|
+
it('should return array when returnPaginationResults is false or undefined', async () => {
|
|
1239
|
+
await store.insert({ tableName: TABLE_TRACES, record: createSampleTraceForDB('trace-arr', 'pg-array-scope') });
|
|
1240
|
+
const tracesArray = await store.getTraces({ scope: 'pg-array-scope', page: 0, perPage: 5 }); // returnPaginationResults is undefined
|
|
1241
|
+
expect(Array.isArray(tracesArray)).toBe(true);
|
|
1242
|
+
expect(tracesArray.length).toBe(1);
|
|
1243
|
+
});
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
describe('getMessages with pagination', () => {
|
|
1247
|
+
it('should return paginated messages with total count', async () => {
|
|
1248
|
+
const thread = createSampleThread();
|
|
1249
|
+
await store.saveThread({ thread });
|
|
1250
|
+
const messagePromises = Array.from({ length: 15 }, (_, i) =>
|
|
1251
|
+
store.saveMessages({
|
|
1252
|
+
messages: [{ ...createSampleMessage(thread.id), content: [{ type: 'text', text: `Message ${i + 1}` }] }],
|
|
1253
|
+
}),
|
|
1254
|
+
);
|
|
1255
|
+
await Promise.all(messagePromises);
|
|
1256
|
+
|
|
1257
|
+
const page1 = await store.getMessages({ threadId: thread.id, page: 0, perPage: 5, format: 'v1' });
|
|
1258
|
+
expect(page1.messages).toHaveLength(5);
|
|
1259
|
+
expect(page1.total).toBe(15);
|
|
1260
|
+
expect(page1.page).toBe(0);
|
|
1261
|
+
expect(page1.perPage).toBe(5);
|
|
1262
|
+
expect(page1.hasMore).toBe(true);
|
|
1263
|
+
|
|
1264
|
+
const page3 = await store.getMessages({ threadId: thread.id, page: 2, perPage: 5, format: 'v1' });
|
|
1265
|
+
expect(page3.messages).toHaveLength(5);
|
|
1266
|
+
expect(page3.total).toBe(15);
|
|
1267
|
+
expect(page3.hasMore).toBe(false);
|
|
1268
|
+
});
|
|
1269
|
+
|
|
1270
|
+
it('should filter by date with pagination for getMessages', async () => {
|
|
1271
|
+
const thread = createSampleThread();
|
|
1272
|
+
await store.saveThread({ thread });
|
|
1273
|
+
const now = new Date();
|
|
1274
|
+
const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
|
1275
|
+
const dayBeforeYesterday = new Date(now.getTime() - 48 * 60 * 60 * 1000);
|
|
1276
|
+
|
|
1277
|
+
const createMsgAtDate = (date: Date) => {
|
|
1278
|
+
return store.saveMessages({ messages: [{ ...createSampleMessage(thread.id), createdAt: date }] });
|
|
1279
|
+
};
|
|
1280
|
+
await Promise.all([
|
|
1281
|
+
createMsgAtDate(dayBeforeYesterday),
|
|
1282
|
+
createMsgAtDate(dayBeforeYesterday),
|
|
1283
|
+
createMsgAtDate(yesterday),
|
|
1284
|
+
createMsgAtDate(yesterday),
|
|
1285
|
+
createMsgAtDate(yesterday),
|
|
1286
|
+
createMsgAtDate(now),
|
|
1287
|
+
createMsgAtDate(now),
|
|
1288
|
+
]);
|
|
1289
|
+
|
|
1290
|
+
const resultPage = await store.getMessages({
|
|
1291
|
+
threadId: thread.id,
|
|
1292
|
+
fromDate: yesterday,
|
|
1293
|
+
page: 0,
|
|
1294
|
+
perPage: 3,
|
|
1295
|
+
format: 'v1',
|
|
1296
|
+
});
|
|
1297
|
+
expect(resultPage.total).toBe(5);
|
|
1298
|
+
expect(resultPage.messages).toHaveLength(3);
|
|
1299
|
+
|
|
1300
|
+
expect(new Date((resultPage.messages[0] as MastraMessageV1).createdAt).toISOString()).toBe(now.toISOString());
|
|
1301
|
+
expect(new Date((resultPage.messages[1] as MastraMessageV1).createdAt).toISOString()).toBe(now.toISOString());
|
|
1302
|
+
expect(new Date((resultPage.messages[2] as MastraMessageV1).createdAt).toISOString()).toBe(
|
|
1303
|
+
yesterday.toISOString(),
|
|
1304
|
+
);
|
|
1305
|
+
});
|
|
1306
|
+
|
|
1307
|
+
it('should maintain backward compatibility for getMessages (no pagination params)', async () => {
|
|
1308
|
+
const thread = createSampleThread();
|
|
1309
|
+
await store.saveThread({ thread });
|
|
1310
|
+
await store.saveMessages({ messages: [createSampleMessage(thread.id)] });
|
|
1311
|
+
|
|
1312
|
+
const messages = await store.getMessages({ threadId: thread.id, format: 'v1' });
|
|
1313
|
+
expect(Array.isArray(messages)).toBe(true);
|
|
1314
|
+
expect(messages.length).toBe(1);
|
|
1315
|
+
// @ts-expect-error - messages should not have pagination properties
|
|
1316
|
+
expect(messages.total).toBeUndefined();
|
|
1317
|
+
});
|
|
1318
|
+
});
|
|
1319
|
+
|
|
1320
|
+
describe('getThreadsByResourceId with pagination', () => {
|
|
1321
|
+
it('should return paginated threads with total count', async () => {
|
|
1322
|
+
const resourceId = `pg-paginated-resource-${randomUUID()}`;
|
|
1323
|
+
const threadPromises = Array.from({ length: 17 }, () =>
|
|
1324
|
+
store.saveThread({ thread: { ...createSampleThread(), resourceId } }),
|
|
1325
|
+
);
|
|
1326
|
+
await Promise.all(threadPromises);
|
|
1327
|
+
|
|
1328
|
+
const page1 = await store.getThreadsByResourceId({ resourceId, page: 0, perPage: 7 });
|
|
1329
|
+
expect(page1.threads).toHaveLength(7);
|
|
1330
|
+
expect(page1.total).toBe(17);
|
|
1331
|
+
expect(page1.page).toBe(0);
|
|
1332
|
+
expect(page1.perPage).toBe(7);
|
|
1333
|
+
expect(page1.hasMore).toBe(true);
|
|
1334
|
+
|
|
1335
|
+
const page3 = await store.getThreadsByResourceId({ resourceId, page: 2, perPage: 7 });
|
|
1336
|
+
expect(page3.threads).toHaveLength(3); // 17 total, 7 per page, 3rd page has 17 - 2*7 = 3
|
|
1337
|
+
expect(page3.total).toBe(17);
|
|
1338
|
+
expect(page3.hasMore).toBe(false);
|
|
1339
|
+
});
|
|
1340
|
+
|
|
1341
|
+
it('should return array when no pagination params for getThreadsByResourceId', async () => {
|
|
1342
|
+
const resourceId = `pg-non-paginated-resource-${randomUUID()}`;
|
|
1343
|
+
await store.saveThread({ thread: { ...createSampleThread(), resourceId } });
|
|
1344
|
+
|
|
1345
|
+
const threads = await store.getThreadsByResourceId({ resourceId });
|
|
1346
|
+
expect(Array.isArray(threads)).toBe(true);
|
|
1347
|
+
expect(threads.length).toBe(1);
|
|
1348
|
+
// @ts-expect-error - threads should not have pagination properties
|
|
1349
|
+
expect(threads.total).toBeUndefined();
|
|
1350
|
+
});
|
|
1351
|
+
});
|
|
1352
|
+
});
|
|
1353
|
+
|
|
934
1354
|
describe('Permission Handling', () => {
|
|
935
1355
|
const schemaRestrictedUser = 'mastra_schema_restricted_storage';
|
|
936
1356
|
const restrictedPassword = 'test123';
|