@mastra/pg 0.10.2-alpha.0 → 0.10.2-alpha.1
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 +12 -0
- package/dist/_tsup-dts-rollup.d.cts +30 -40
- package/dist/_tsup-dts-rollup.d.ts +30 -40
- package/dist/index.cjs +226 -196
- package/dist/index.js +226 -196
- package/package.json +3 -3
- package/src/storage/index.test.ts +208 -190
- package/src/storage/index.ts +283 -318
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { randomUUID } from 'crypto';
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
createSampleEval,
|
|
4
|
+
createSampleTraceForDB,
|
|
5
|
+
createSampleThread,
|
|
6
|
+
createSampleMessageV1,
|
|
7
|
+
createSampleMessageV2,
|
|
8
|
+
createSampleWorkflowSnapshot,
|
|
9
|
+
resetRole,
|
|
10
|
+
} from '@internal/storage-test-utils';
|
|
11
|
+
import type { MastraMessageV1, StorageThreadType } from '@mastra/core/memory';
|
|
12
|
+
import type { StorageColumn, TABLE_NAMES } from '@mastra/core/storage';
|
|
4
13
|
import {
|
|
5
14
|
TABLE_WORKFLOW_SNAPSHOT,
|
|
6
15
|
TABLE_MESSAGES,
|
|
@@ -27,58 +36,6 @@ const connectionString = `postgresql://${TEST_CONFIG.user}:${TEST_CONFIG.passwor
|
|
|
27
36
|
|
|
28
37
|
vi.setConfig({ testTimeout: 60_000, hookTimeout: 60_000 });
|
|
29
38
|
|
|
30
|
-
// Sample test data factory functions
|
|
31
|
-
const createSampleThread = () => ({
|
|
32
|
-
id: `thread-${randomUUID()}`,
|
|
33
|
-
resourceId: `resource-${randomUUID()}`,
|
|
34
|
-
title: 'Test Thread',
|
|
35
|
-
createdAt: new Date(),
|
|
36
|
-
updatedAt: new Date(),
|
|
37
|
-
metadata: { key: 'value' },
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
let role: 'user' | 'assistant' = 'assistant';
|
|
41
|
-
const getRole = () => {
|
|
42
|
-
if (role === `user`) role = `assistant`;
|
|
43
|
-
else role = `user`;
|
|
44
|
-
return role;
|
|
45
|
-
};
|
|
46
|
-
const createSampleMessage = (threadId: string): MastraMessageV1 => ({
|
|
47
|
-
id: `msg-${randomUUID()}`,
|
|
48
|
-
resourceId: `resource-${randomUUID()}`,
|
|
49
|
-
role: getRole(),
|
|
50
|
-
type: 'text',
|
|
51
|
-
threadId,
|
|
52
|
-
content: [{ type: 'text', text: 'Hello' }],
|
|
53
|
-
createdAt: new Date(),
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
const createSampleWorkflowSnapshot = (status: WorkflowRunState['context'][string]['status'], createdAt?: Date) => {
|
|
57
|
-
const runId = `run-${randomUUID()}`;
|
|
58
|
-
const stepId = `step-${randomUUID()}`;
|
|
59
|
-
const timestamp = createdAt || new Date();
|
|
60
|
-
const snapshot = {
|
|
61
|
-
result: { success: true },
|
|
62
|
-
value: {},
|
|
63
|
-
context: {
|
|
64
|
-
[stepId]: {
|
|
65
|
-
status,
|
|
66
|
-
payload: {},
|
|
67
|
-
error: undefined,
|
|
68
|
-
startedAt: timestamp.getTime(),
|
|
69
|
-
endedAt: new Date(timestamp.getTime() + 15000).getTime(),
|
|
70
|
-
},
|
|
71
|
-
input: {},
|
|
72
|
-
},
|
|
73
|
-
serializedStepGraph: [],
|
|
74
|
-
activePaths: [],
|
|
75
|
-
suspendedPaths: {},
|
|
76
|
-
runId,
|
|
77
|
-
timestamp: timestamp.getTime(),
|
|
78
|
-
} as unknown as WorkflowRunState;
|
|
79
|
-
return { snapshot, runId, stepId };
|
|
80
|
-
};
|
|
81
|
-
|
|
82
39
|
const checkWorkflowSnapshot = (snapshot: WorkflowRunState | string, stepId: string, status: string) => {
|
|
83
40
|
if (typeof snapshot === 'string') {
|
|
84
41
|
throw new Error('Expected WorkflowRunState, got string');
|
|
@@ -210,7 +167,7 @@ describe('PostgresStore', () => {
|
|
|
210
167
|
await store.saveThread({ thread });
|
|
211
168
|
|
|
212
169
|
// Add some messages
|
|
213
|
-
const messages = [
|
|
170
|
+
const messages = [createSampleMessageV1({ threadId: thread.id }), createSampleMessageV1({ threadId: thread.id })];
|
|
214
171
|
await store.saveMessages({ messages });
|
|
215
172
|
|
|
216
173
|
await store.deleteThread({ threadId: thread.id });
|
|
@@ -229,7 +186,7 @@ describe('PostgresStore', () => {
|
|
|
229
186
|
const thread = createSampleThread();
|
|
230
187
|
await store.saveThread({ thread });
|
|
231
188
|
|
|
232
|
-
const messages = [
|
|
189
|
+
const messages = [createSampleMessageV1({ threadId: thread.id }), createSampleMessageV1({ threadId: thread.id })];
|
|
233
190
|
|
|
234
191
|
// Save messages
|
|
235
192
|
const savedMessages = await store.saveMessages({ messages });
|
|
@@ -254,24 +211,18 @@ describe('PostgresStore', () => {
|
|
|
254
211
|
const thread = createSampleThread();
|
|
255
212
|
await store.saveThread({ thread });
|
|
256
213
|
|
|
257
|
-
const
|
|
258
|
-
{ ...createSampleMessage(thread.id), content: [{ type: 'text', text: 'First' }] },
|
|
259
|
-
{
|
|
260
|
-
...createSampleMessage(thread.id),
|
|
261
|
-
content: [{ type: 'text', text: 'Second' }],
|
|
262
|
-
},
|
|
263
|
-
{ ...createSampleMessage(thread.id), content: [{ type: 'text', text: 'Third' }] },
|
|
264
|
-
] satisfies MastraMessageV1[];
|
|
214
|
+
const messageContent = ['First', 'Second', 'Third'];
|
|
265
215
|
|
|
266
|
-
|
|
216
|
+
const messages = messageContent.map(content => createSampleMessageV2({ threadId: thread.id, content }));
|
|
267
217
|
|
|
268
|
-
|
|
218
|
+
await store.saveMessages({ messages, format: 'v2' });
|
|
219
|
+
|
|
220
|
+
const retrievedMessages = await store.getMessages({ threadId: thread.id, format: 'v2' });
|
|
269
221
|
expect(retrievedMessages).toHaveLength(3);
|
|
270
222
|
|
|
271
223
|
// Verify order is maintained
|
|
272
224
|
retrievedMessages.forEach((msg, idx) => {
|
|
273
|
-
|
|
274
|
-
expect(msg.content[0].text).toBe(messages[idx].content[0].text);
|
|
225
|
+
expect((msg.content.parts[0] as any).text).toEqual(messageContent[idx]);
|
|
275
226
|
});
|
|
276
227
|
});
|
|
277
228
|
|
|
@@ -280,8 +231,8 @@ describe('PostgresStore', () => {
|
|
|
280
231
|
await store.saveThread({ thread });
|
|
281
232
|
|
|
282
233
|
const messages = [
|
|
283
|
-
|
|
284
|
-
{ ...
|
|
234
|
+
createSampleMessageV1({ threadId: thread.id }),
|
|
235
|
+
{ ...createSampleMessageV1({ threadId: thread.id }), id: null } as any, // This will cause an error
|
|
285
236
|
];
|
|
286
237
|
|
|
287
238
|
await expect(store.saveMessages({ messages })).rejects.toThrow();
|
|
@@ -290,43 +241,6 @@ describe('PostgresStore', () => {
|
|
|
290
241
|
const savedMessages = await store.getMessages({ threadId: thread.id });
|
|
291
242
|
expect(savedMessages).toHaveLength(0);
|
|
292
243
|
});
|
|
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
|
-
});
|
|
330
244
|
});
|
|
331
245
|
|
|
332
246
|
describe('Edge Cases and Error Handling', () => {
|
|
@@ -887,6 +801,96 @@ describe('PostgresStore', () => {
|
|
|
887
801
|
});
|
|
888
802
|
});
|
|
889
803
|
|
|
804
|
+
describe('alterTable', () => {
|
|
805
|
+
const TEST_TABLE = 'test_alter_table';
|
|
806
|
+
const BASE_SCHEMA = {
|
|
807
|
+
id: { type: 'integer', primaryKey: true, nullable: false },
|
|
808
|
+
name: { type: 'text', nullable: true },
|
|
809
|
+
} as Record<string, StorageColumn>;
|
|
810
|
+
|
|
811
|
+
beforeEach(async () => {
|
|
812
|
+
await store.createTable({ tableName: TEST_TABLE as TABLE_NAMES, schema: BASE_SCHEMA });
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
afterEach(async () => {
|
|
816
|
+
await store.clearTable({ tableName: TEST_TABLE as TABLE_NAMES });
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
it('adds a new column to an existing table', async () => {
|
|
820
|
+
await store.alterTable({
|
|
821
|
+
tableName: TEST_TABLE as TABLE_NAMES,
|
|
822
|
+
schema: { ...BASE_SCHEMA, age: { type: 'integer', nullable: true } },
|
|
823
|
+
ifNotExists: ['age'],
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
await store.insert({
|
|
827
|
+
tableName: TEST_TABLE as TABLE_NAMES,
|
|
828
|
+
record: { id: 1, name: 'Alice', age: 42 },
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
const row = await store.load<{ id: string; name: string; age?: number }>({
|
|
832
|
+
tableName: TEST_TABLE as TABLE_NAMES,
|
|
833
|
+
keys: { id: '1' },
|
|
834
|
+
});
|
|
835
|
+
expect(row?.age).toBe(42);
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
it('is idempotent when adding an existing column', async () => {
|
|
839
|
+
await store.alterTable({
|
|
840
|
+
tableName: TEST_TABLE as TABLE_NAMES,
|
|
841
|
+
schema: { ...BASE_SCHEMA, foo: { type: 'text', nullable: true } },
|
|
842
|
+
ifNotExists: ['foo'],
|
|
843
|
+
});
|
|
844
|
+
// Add the column again (should not throw)
|
|
845
|
+
await expect(
|
|
846
|
+
store.alterTable({
|
|
847
|
+
tableName: TEST_TABLE as TABLE_NAMES,
|
|
848
|
+
schema: { ...BASE_SCHEMA, foo: { type: 'text', nullable: true } },
|
|
849
|
+
ifNotExists: ['foo'],
|
|
850
|
+
}),
|
|
851
|
+
).resolves.not.toThrow();
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
it('should add a default value to a column when using not null', async () => {
|
|
855
|
+
await store.insert({
|
|
856
|
+
tableName: TEST_TABLE as TABLE_NAMES,
|
|
857
|
+
record: { id: 1, name: 'Bob' },
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
await expect(
|
|
861
|
+
store.alterTable({
|
|
862
|
+
tableName: TEST_TABLE as TABLE_NAMES,
|
|
863
|
+
schema: { ...BASE_SCHEMA, text_column: { type: 'text', nullable: false } },
|
|
864
|
+
ifNotExists: ['text_column'],
|
|
865
|
+
}),
|
|
866
|
+
).resolves.not.toThrow();
|
|
867
|
+
|
|
868
|
+
await expect(
|
|
869
|
+
store.alterTable({
|
|
870
|
+
tableName: TEST_TABLE as TABLE_NAMES,
|
|
871
|
+
schema: { ...BASE_SCHEMA, timestamp_column: { type: 'timestamp', nullable: false } },
|
|
872
|
+
ifNotExists: ['timestamp_column'],
|
|
873
|
+
}),
|
|
874
|
+
).resolves.not.toThrow();
|
|
875
|
+
|
|
876
|
+
await expect(
|
|
877
|
+
store.alterTable({
|
|
878
|
+
tableName: TEST_TABLE as TABLE_NAMES,
|
|
879
|
+
schema: { ...BASE_SCHEMA, bigint_column: { type: 'bigint', nullable: false } },
|
|
880
|
+
ifNotExists: ['bigint_column'],
|
|
881
|
+
}),
|
|
882
|
+
).resolves.not.toThrow();
|
|
883
|
+
|
|
884
|
+
await expect(
|
|
885
|
+
store.alterTable({
|
|
886
|
+
tableName: TEST_TABLE as TABLE_NAMES,
|
|
887
|
+
schema: { ...BASE_SCHEMA, jsonb_column: { type: 'jsonb', nullable: false } },
|
|
888
|
+
ifNotExists: ['jsonb_column'],
|
|
889
|
+
}),
|
|
890
|
+
).resolves.not.toThrow();
|
|
891
|
+
});
|
|
892
|
+
});
|
|
893
|
+
|
|
890
894
|
describe('Schema Support', () => {
|
|
891
895
|
const customSchema = 'mastra_test';
|
|
892
896
|
let customSchemaStore: PostgresStore;
|
|
@@ -1022,11 +1026,11 @@ describe('PostgresStore', () => {
|
|
|
1022
1026
|
});
|
|
1023
1027
|
await Promise.all(evalPromises);
|
|
1024
1028
|
|
|
1025
|
-
const result = await store.getEvals({ agentName,
|
|
1029
|
+
const result = await store.getEvals({ agentName, perPage: 5, page: 2 });
|
|
1026
1030
|
expect(result.evals).toHaveLength(5);
|
|
1027
1031
|
expect(result.total).toBe(15);
|
|
1028
|
-
expect(result.page).
|
|
1029
|
-
expect(result.perPage).
|
|
1032
|
+
expect(result.page).toBe(2);
|
|
1033
|
+
expect(result.perPage).toBe(5);
|
|
1030
1034
|
expect(result.hasMore).toBe(false);
|
|
1031
1035
|
});
|
|
1032
1036
|
|
|
@@ -1117,7 +1121,7 @@ describe('PostgresStore', () => {
|
|
|
1117
1121
|
createEvalAtDate(now),
|
|
1118
1122
|
]);
|
|
1119
1123
|
|
|
1120
|
-
const fromYesterday = await store.getEvals({ agentName,
|
|
1124
|
+
const fromYesterday = await store.getEvals({ agentName, dateRange: { start: yesterday }, page: 0, perPage: 3 });
|
|
1121
1125
|
expect(fromYesterday.total).toBe(7); // 3 yesterday + 4 now
|
|
1122
1126
|
expect(fromYesterday.evals).toHaveLength(3);
|
|
1123
1127
|
// Evals are sorted DESC, so first 3 are from 'now'
|
|
@@ -1127,7 +1131,9 @@ describe('PostgresStore', () => {
|
|
|
1127
1131
|
|
|
1128
1132
|
const onlyDayBefore = await store.getEvals({
|
|
1129
1133
|
agentName,
|
|
1130
|
-
|
|
1134
|
+
dateRange: {
|
|
1135
|
+
end: new Date(yesterday.getTime() - 1),
|
|
1136
|
+
},
|
|
1131
1137
|
page: 0,
|
|
1132
1138
|
perPage: 5,
|
|
1133
1139
|
});
|
|
@@ -1143,11 +1149,10 @@ describe('PostgresStore', () => {
|
|
|
1143
1149
|
);
|
|
1144
1150
|
await Promise.all(tracePromises);
|
|
1145
1151
|
|
|
1146
|
-
const page1 = await store.
|
|
1152
|
+
const page1 = await store.getTracesPaginated({
|
|
1147
1153
|
scope: 'pg-test-scope',
|
|
1148
1154
|
page: 0,
|
|
1149
1155
|
perPage: 8,
|
|
1150
|
-
returnPaginationResults: true,
|
|
1151
1156
|
});
|
|
1152
1157
|
expect(page1.traces).toHaveLength(8);
|
|
1153
1158
|
expect(page1.total).toBe(18);
|
|
@@ -1155,11 +1160,10 @@ describe('PostgresStore', () => {
|
|
|
1155
1160
|
expect(page1.perPage).toBe(8);
|
|
1156
1161
|
expect(page1.hasMore).toBe(true);
|
|
1157
1162
|
|
|
1158
|
-
const page3 = await store.
|
|
1163
|
+
const page3 = await store.getTracesPaginated({
|
|
1159
1164
|
scope: 'pg-test-scope',
|
|
1160
1165
|
page: 2,
|
|
1161
1166
|
perPage: 8,
|
|
1162
|
-
returnPaginationResults: true,
|
|
1163
1167
|
});
|
|
1164
1168
|
expect(page3.traces).toHaveLength(2);
|
|
1165
1169
|
expect(page3.total).toBe(18);
|
|
@@ -1181,12 +1185,11 @@ describe('PostgresStore', () => {
|
|
|
1181
1185
|
);
|
|
1182
1186
|
await Promise.all([...tracesWithAttr, ...tracesWithoutAttr]);
|
|
1183
1187
|
|
|
1184
|
-
const prodTraces = await store.
|
|
1188
|
+
const prodTraces = await store.getTracesPaginated({
|
|
1185
1189
|
scope: 'pg-attr-scope',
|
|
1186
1190
|
attributes: { environment: 'prod' },
|
|
1187
1191
|
page: 0,
|
|
1188
1192
|
perPage: 5,
|
|
1189
|
-
returnPaginationResults: true,
|
|
1190
1193
|
});
|
|
1191
1194
|
expect(prodTraces.traces).toHaveLength(5);
|
|
1192
1195
|
expect(prodTraces.total).toBe(8);
|
|
@@ -1210,12 +1213,13 @@ describe('PostgresStore', () => {
|
|
|
1210
1213
|
store.insert({ tableName: TABLE_TRACES, record: createSampleTraceForDB('t5', scope, undefined, now) }),
|
|
1211
1214
|
]);
|
|
1212
1215
|
|
|
1213
|
-
const fromYesterday = await store.
|
|
1216
|
+
const fromYesterday = await store.getTracesPaginated({
|
|
1214
1217
|
scope,
|
|
1215
|
-
|
|
1218
|
+
dateRange: {
|
|
1219
|
+
start: yesterday,
|
|
1220
|
+
},
|
|
1216
1221
|
page: 0,
|
|
1217
1222
|
perPage: 2,
|
|
1218
|
-
returnPaginationResults: true,
|
|
1219
1223
|
});
|
|
1220
1224
|
expect(fromYesterday.total).toBe(4); // 2 yesterday + 2 now
|
|
1221
1225
|
expect(fromYesterday.traces).toHaveLength(2);
|
|
@@ -1223,97 +1227,109 @@ describe('PostgresStore', () => {
|
|
|
1223
1227
|
expect(new Date(t.createdAt).getTime()).toBeGreaterThanOrEqual(yesterday.getTime()),
|
|
1224
1228
|
);
|
|
1225
1229
|
|
|
1226
|
-
const onlyNow = await store.
|
|
1230
|
+
const onlyNow = await store.getTracesPaginated({
|
|
1227
1231
|
scope,
|
|
1228
|
-
|
|
1229
|
-
|
|
1232
|
+
dateRange: {
|
|
1233
|
+
start: now,
|
|
1234
|
+
end: now,
|
|
1235
|
+
},
|
|
1230
1236
|
page: 0,
|
|
1231
1237
|
perPage: 5,
|
|
1232
|
-
returnPaginationResults: true,
|
|
1233
1238
|
});
|
|
1234
1239
|
expect(onlyNow.total).toBe(2);
|
|
1235
1240
|
expect(onlyNow.traces).toHaveLength(2);
|
|
1236
1241
|
});
|
|
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
1242
|
});
|
|
1245
1243
|
|
|
1246
1244
|
describe('getMessages with pagination', () => {
|
|
1247
1245
|
it('should return paginated messages with total count', async () => {
|
|
1248
1246
|
const thread = createSampleThread();
|
|
1249
1247
|
await store.saveThread({ thread });
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1248
|
+
// Reset role to 'assistant' before creating messages
|
|
1249
|
+
resetRole();
|
|
1250
|
+
// Create messages sequentially to ensure unique timestamps
|
|
1251
|
+
for (let i = 0; i < 15; i++) {
|
|
1252
|
+
const message = createSampleMessageV1({ threadId: thread.id, content: `Message ${i + 1}` });
|
|
1253
|
+
await store.saveMessages({
|
|
1254
|
+
messages: [message],
|
|
1255
|
+
});
|
|
1256
|
+
await new Promise(r => setTimeout(r, 5));
|
|
1257
|
+
}
|
|
1256
1258
|
|
|
1257
|
-
const page1 = await store.
|
|
1259
|
+
const page1 = await store.getMessagesPaginated({
|
|
1260
|
+
threadId: thread.id,
|
|
1261
|
+
selectBy: { pagination: { page: 0, perPage: 5 } },
|
|
1262
|
+
format: 'v2',
|
|
1263
|
+
});
|
|
1264
|
+
console.log(page1);
|
|
1258
1265
|
expect(page1.messages).toHaveLength(5);
|
|
1259
1266
|
expect(page1.total).toBe(15);
|
|
1260
1267
|
expect(page1.page).toBe(0);
|
|
1261
1268
|
expect(page1.perPage).toBe(5);
|
|
1262
1269
|
expect(page1.hasMore).toBe(true);
|
|
1263
1270
|
|
|
1264
|
-
const page3 = await store.
|
|
1271
|
+
const page3 = await store.getMessagesPaginated({
|
|
1272
|
+
threadId: thread.id,
|
|
1273
|
+
selectBy: { pagination: { page: 2, perPage: 5 } },
|
|
1274
|
+
format: 'v2',
|
|
1275
|
+
});
|
|
1265
1276
|
expect(page3.messages).toHaveLength(5);
|
|
1266
1277
|
expect(page3.total).toBe(15);
|
|
1267
1278
|
expect(page3.hasMore).toBe(false);
|
|
1268
1279
|
});
|
|
1269
1280
|
|
|
1270
1281
|
it('should filter by date with pagination for getMessages', async () => {
|
|
1271
|
-
const
|
|
1272
|
-
await store.saveThread({ thread });
|
|
1282
|
+
const threadData = createSampleThread();
|
|
1283
|
+
const thread = await store.saveThread({ thread: threadData as StorageThreadType });
|
|
1273
1284
|
const now = new Date();
|
|
1274
|
-
const yesterday = new Date(
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1285
|
+
const yesterday = new Date(
|
|
1286
|
+
now.getFullYear(),
|
|
1287
|
+
now.getMonth(),
|
|
1288
|
+
now.getDate() - 1,
|
|
1289
|
+
now.getHours(),
|
|
1290
|
+
now.getMinutes(),
|
|
1291
|
+
now.getSeconds(),
|
|
1292
|
+
);
|
|
1293
|
+
const dayBeforeYesterday = new Date(
|
|
1294
|
+
now.getFullYear(),
|
|
1295
|
+
now.getMonth(),
|
|
1296
|
+
now.getDate() - 2,
|
|
1297
|
+
now.getHours(),
|
|
1298
|
+
now.getMinutes(),
|
|
1299
|
+
now.getSeconds(),
|
|
1300
|
+
);
|
|
1289
1301
|
|
|
1290
|
-
|
|
1302
|
+
// Ensure timestamps are distinct for reliable sorting by creating them with a slight delay for testing clarity
|
|
1303
|
+
const messagesToSave: MastraMessageV1[] = [];
|
|
1304
|
+
messagesToSave.push(createSampleMessageV1({ threadId: thread.id, createdAt: dayBeforeYesterday }));
|
|
1305
|
+
await new Promise(r => setTimeout(r, 5));
|
|
1306
|
+
messagesToSave.push(createSampleMessageV1({ threadId: thread.id, createdAt: dayBeforeYesterday }));
|
|
1307
|
+
await new Promise(r => setTimeout(r, 5));
|
|
1308
|
+
messagesToSave.push(createSampleMessageV1({ threadId: thread.id, createdAt: yesterday }));
|
|
1309
|
+
await new Promise(r => setTimeout(r, 5));
|
|
1310
|
+
messagesToSave.push(createSampleMessageV1({ threadId: thread.id, createdAt: yesterday }));
|
|
1311
|
+
await new Promise(r => setTimeout(r, 5));
|
|
1312
|
+
messagesToSave.push(createSampleMessageV1({ threadId: thread.id, createdAt: now }));
|
|
1313
|
+
await new Promise(r => setTimeout(r, 5));
|
|
1314
|
+
messagesToSave.push(createSampleMessageV1({ threadId: thread.id, createdAt: now }));
|
|
1315
|
+
|
|
1316
|
+
await store.saveMessages({ messages: messagesToSave, format: 'v1' });
|
|
1317
|
+
// Total 6 messages: 2 now, 2 yesterday, 2 dayBeforeYesterday (oldest to newest)
|
|
1318
|
+
|
|
1319
|
+
const fromYesterday = await store.getMessagesPaginated({
|
|
1291
1320
|
threadId: thread.id,
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
perPage: 3,
|
|
1295
|
-
format: 'v1',
|
|
1321
|
+
selectBy: { pagination: { page: 0, perPage: 3, dateRange: { start: yesterday } } },
|
|
1322
|
+
format: 'v2',
|
|
1296
1323
|
});
|
|
1297
|
-
expect(
|
|
1298
|
-
expect(
|
|
1299
|
-
|
|
1300
|
-
expect(new Date(
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
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();
|
|
1324
|
+
expect(fromYesterday.total).toBe(4);
|
|
1325
|
+
expect(fromYesterday.messages).toHaveLength(3);
|
|
1326
|
+
const firstMessageTime = new Date((fromYesterday.messages[0] as MastraMessageV1).createdAt).getTime();
|
|
1327
|
+
expect(firstMessageTime).toBeGreaterThanOrEqual(new Date(yesterday.toISOString()).getTime());
|
|
1328
|
+
if (fromYesterday.messages.length > 0) {
|
|
1329
|
+
expect(new Date((fromYesterday.messages[0] as MastraMessageV1).createdAt).toISOString().slice(0, 10)).toEqual(
|
|
1330
|
+
yesterday.toISOString().slice(0, 10),
|
|
1331
|
+
);
|
|
1332
|
+
}
|
|
1317
1333
|
});
|
|
1318
1334
|
});
|
|
1319
1335
|
|
|
@@ -1325,28 +1341,30 @@ describe('PostgresStore', () => {
|
|
|
1325
1341
|
);
|
|
1326
1342
|
await Promise.all(threadPromises);
|
|
1327
1343
|
|
|
1328
|
-
const page1 = await store.
|
|
1344
|
+
const page1 = await store.getThreadsByResourceIdPaginated({ resourceId, page: 0, perPage: 7 });
|
|
1329
1345
|
expect(page1.threads).toHaveLength(7);
|
|
1330
1346
|
expect(page1.total).toBe(17);
|
|
1331
1347
|
expect(page1.page).toBe(0);
|
|
1332
1348
|
expect(page1.perPage).toBe(7);
|
|
1333
1349
|
expect(page1.hasMore).toBe(true);
|
|
1334
1350
|
|
|
1335
|
-
const page3 = await store.
|
|
1351
|
+
const page3 = await store.getThreadsByResourceIdPaginated({ resourceId, page: 2, perPage: 7 });
|
|
1336
1352
|
expect(page3.threads).toHaveLength(3); // 17 total, 7 per page, 3rd page has 17 - 2*7 = 3
|
|
1337
1353
|
expect(page3.total).toBe(17);
|
|
1338
1354
|
expect(page3.hasMore).toBe(false);
|
|
1339
1355
|
});
|
|
1340
1356
|
|
|
1341
|
-
it('should return
|
|
1357
|
+
it('should return paginated results when no pagination params for getThreadsByResourceId', async () => {
|
|
1342
1358
|
const resourceId = `pg-non-paginated-resource-${randomUUID()}`;
|
|
1343
1359
|
await store.saveThread({ thread: { ...createSampleThread(), resourceId } });
|
|
1344
1360
|
|
|
1345
|
-
const
|
|
1346
|
-
expect(Array.isArray(threads)).toBe(true);
|
|
1347
|
-
expect(threads.length).toBe(1);
|
|
1348
|
-
|
|
1349
|
-
expect(
|
|
1361
|
+
const results = await store.getThreadsByResourceIdPaginated({ resourceId });
|
|
1362
|
+
expect(Array.isArray(results.threads)).toBe(true);
|
|
1363
|
+
expect(results.threads.length).toBe(1);
|
|
1364
|
+
expect(results.total).toBe(1);
|
|
1365
|
+
expect(results.page).toBe(0);
|
|
1366
|
+
expect(results.perPage).toBe(100);
|
|
1367
|
+
expect(results.hasMore).toBe(false);
|
|
1350
1368
|
});
|
|
1351
1369
|
});
|
|
1352
1370
|
});
|