@mastra/upstash 0.10.2 → 0.10.3-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.
@@ -360,6 +360,136 @@ describe('UpstashStore', () => {
360
360
  const retrievedMessages = await store.getMessages({ threadId, format: 'v2' });
361
361
  expect(retrievedMessages[0].content).toEqual(messages[0].content);
362
362
  });
363
+
364
+ describe('getPaginatedMessages', () => {
365
+ it('should return paginated messages with total count', async () => {
366
+ const thread = createSampleThread();
367
+ await store.saveThread({ thread });
368
+
369
+ const messages = Array.from({ length: 15 }, (_, i) => createSampleMessage(thread.id, `Message ${i + 1}`));
370
+
371
+ await store.saveMessages({ messages, format: 'v2' });
372
+
373
+ const page1 = await store.getMessages({
374
+ threadId: thread.id,
375
+ page: 0,
376
+ perPage: 5,
377
+ format: 'v2',
378
+ });
379
+ expect(page1.messages).toHaveLength(5);
380
+ expect(page1.total).toBe(15);
381
+ expect(page1.page).toBe(0);
382
+ expect(page1.perPage).toBe(5);
383
+ expect(page1.hasMore).toBe(true);
384
+
385
+ const page3 = await store.getMessages({
386
+ threadId: thread.id,
387
+ page: 2,
388
+ perPage: 5,
389
+ format: 'v2',
390
+ });
391
+ expect(page3.messages).toHaveLength(5);
392
+ expect(page3.total).toBe(15);
393
+ expect(page3.hasMore).toBe(false);
394
+
395
+ const page4 = await store.getMessages({
396
+ threadId: thread.id,
397
+ page: 3,
398
+ perPage: 5,
399
+ format: 'v2',
400
+ });
401
+ expect(page4.messages).toHaveLength(0);
402
+ expect(page4.total).toBe(15);
403
+ expect(page4.hasMore).toBe(false);
404
+ });
405
+
406
+ it('should maintain chronological order in pagination', async () => {
407
+ const thread = createSampleThread();
408
+ await store.saveThread({ thread });
409
+
410
+ const messages = Array.from({ length: 10 }, (_, i) => {
411
+ const message = createSampleMessage(thread.id, `Message ${i + 1}`);
412
+ // Ensure different timestamps
413
+ message.createdAt = new Date(Date.now() + i * 1000);
414
+ return message;
415
+ });
416
+
417
+ await store.saveMessages({ messages, format: 'v2' });
418
+
419
+ const page1 = await store.getMessages({
420
+ threadId: thread.id,
421
+ page: 0,
422
+ perPage: 3,
423
+ format: 'v2',
424
+ });
425
+
426
+ // Check that messages are in chronological order
427
+ for (let i = 1; i < page1.messages.length; i++) {
428
+ const prevMessage = page1.messages[i - 1] as MastraMessageV2;
429
+ const currentMessage = page1.messages[i] as MastraMessageV2;
430
+ expect(new Date(prevMessage.createdAt).getTime()).toBeLessThanOrEqual(
431
+ new Date(currentMessage.createdAt).getTime(),
432
+ );
433
+ }
434
+ });
435
+
436
+ it('should maintain backward compatibility when no pagination params provided', async () => {
437
+ const thread = createSampleThread();
438
+ await store.saveThread({ thread });
439
+
440
+ const messages = Array.from({ length: 5 }, (_, i) => createSampleMessage(thread.id, `Message ${i + 1}`));
441
+
442
+ await store.saveMessages({ messages, format: 'v2' });
443
+
444
+ // Test original format without pagination - should return array
445
+ const messagesV1 = await store.getMessages({
446
+ threadId: thread.id,
447
+ format: 'v1',
448
+ });
449
+ expect(Array.isArray(messagesV1)).toBe(true);
450
+ expect(messagesV1).toHaveLength(5);
451
+
452
+ const messagesV2 = await store.getMessages({
453
+ threadId: thread.id,
454
+ format: 'v2',
455
+ });
456
+ expect(Array.isArray(messagesV2)).toBe(true);
457
+ expect(messagesV2).toHaveLength(5);
458
+ });
459
+
460
+ it('should support date filtering with pagination', async () => {
461
+ const thread = createSampleThread();
462
+ await store.saveThread({ thread });
463
+
464
+ const now = new Date();
465
+ const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000);
466
+ const tomorrow = new Date(now.getTime() + 24 * 60 * 60 * 1000);
467
+
468
+ const oldMessages = Array.from({ length: 3 }, (_, i) => {
469
+ const message = createSampleMessage(thread.id, `Old Message ${i + 1}`);
470
+ message.createdAt = yesterday;
471
+ return message;
472
+ });
473
+
474
+ const newMessages = Array.from({ length: 4 }, (_, i) => {
475
+ const message = createSampleMessage(thread.id, `New Message ${i + 1}`);
476
+ message.createdAt = tomorrow;
477
+ return message;
478
+ });
479
+
480
+ await store.saveMessages({ messages: [...oldMessages, ...newMessages], format: 'v2' });
481
+
482
+ const recentMessages = await store.getMessages({
483
+ threadId: thread.id,
484
+ page: 0,
485
+ perPage: 10,
486
+ fromDate: now,
487
+ format: 'v2',
488
+ });
489
+ expect(recentMessages.messages).toHaveLength(4);
490
+ expect(recentMessages.total).toBe(4);
491
+ });
492
+ });
363
493
  });
364
494
 
365
495
  describe('Trace Operations', () => {
@@ -864,4 +994,187 @@ describe('UpstashStore', () => {
864
994
  expect(runs.length).toBe(0);
865
995
  });
866
996
  });
997
+
998
+ describe('Pagination Features', () => {
999
+ beforeEach(async () => {
1000
+ // Clear all test data
1001
+ await store.clearTable({ tableName: TABLE_THREADS });
1002
+ await store.clearTable({ tableName: TABLE_MESSAGES });
1003
+ await store.clearTable({ tableName: TABLE_EVALS });
1004
+ await store.clearTable({ tableName: TABLE_TRACES });
1005
+ });
1006
+
1007
+ describe('getEvals with pagination', () => {
1008
+ it('should return paginated evals with total count', async () => {
1009
+ const agentName = 'test-agent';
1010
+ const evals = Array.from({ length: 25 }, (_, i) => createSampleEval(agentName, i % 2 === 0));
1011
+
1012
+ // Insert all evals
1013
+ for (const evalRecord of evals) {
1014
+ await store.insert({
1015
+ tableName: TABLE_EVALS,
1016
+ record: evalRecord,
1017
+ });
1018
+ }
1019
+
1020
+ // Test page-based pagination
1021
+ const page1 = await store.getEvals({ agentName, page: 0, perPage: 10 });
1022
+ expect(page1.evals).toHaveLength(10);
1023
+ expect(page1.total).toBe(25);
1024
+ expect(page1.page).toBe(0);
1025
+ expect(page1.perPage).toBe(10);
1026
+ expect(page1.hasMore).toBe(true);
1027
+
1028
+ const page2 = await store.getEvals({ agentName, page: 1, perPage: 10 });
1029
+ expect(page2.evals).toHaveLength(10);
1030
+ expect(page2.total).toBe(25);
1031
+ expect(page2.hasMore).toBe(true);
1032
+
1033
+ const page3 = await store.getEvals({ agentName, page: 2, perPage: 10 });
1034
+ expect(page3.evals).toHaveLength(5);
1035
+ expect(page3.total).toBe(25);
1036
+ expect(page3.hasMore).toBe(false);
1037
+ });
1038
+
1039
+ it('should support limit/offset pagination', async () => {
1040
+ const agentName = 'test-agent-2';
1041
+ const evals = Array.from({ length: 15 }, () => createSampleEval(agentName));
1042
+
1043
+ for (const evalRecord of evals) {
1044
+ await store.insert({
1045
+ tableName: TABLE_EVALS,
1046
+ record: evalRecord,
1047
+ });
1048
+ }
1049
+
1050
+ // Test offset-based pagination
1051
+ const result1 = await store.getEvals({ agentName, limit: 5, offset: 0 });
1052
+ expect(result1.evals).toHaveLength(5);
1053
+ expect(result1.total).toBe(15);
1054
+ expect(result1.hasMore).toBe(true);
1055
+
1056
+ const result2 = await store.getEvals({ agentName, limit: 5, offset: 10 });
1057
+ expect(result2.evals).toHaveLength(5);
1058
+ expect(result2.total).toBe(15);
1059
+ expect(result2.hasMore).toBe(false);
1060
+ });
1061
+
1062
+ it('should filter by type with pagination', async () => {
1063
+ const agentName = 'test-agent-3';
1064
+ const testEvals = Array.from({ length: 10 }, () => createSampleEval(agentName, true));
1065
+ const liveEvals = Array.from({ length: 8 }, () => createSampleEval(agentName, false));
1066
+
1067
+ for (const evalRecord of [...testEvals, ...liveEvals]) {
1068
+ await store.insert({
1069
+ tableName: TABLE_EVALS,
1070
+ record: evalRecord,
1071
+ });
1072
+ }
1073
+
1074
+ const testResults = await store.getEvals({ agentName, type: 'test', page: 0, perPage: 5 });
1075
+ expect(testResults.evals).toHaveLength(5);
1076
+ expect(testResults.total).toBe(10);
1077
+
1078
+ const liveResults = await store.getEvals({ agentName, type: 'live', page: 0, perPage: 5 });
1079
+ expect(liveResults.evals).toHaveLength(5);
1080
+ expect(liveResults.total).toBe(8);
1081
+ });
1082
+ });
1083
+
1084
+ describe('getTracesPaginated', () => {
1085
+ it('should return paginated traces with total count', async () => {
1086
+ const traces = Array.from({ length: 18 }, (_, i) => createSampleTrace(`test-trace-${i}`, 'test-scope'));
1087
+
1088
+ for (const trace of traces) {
1089
+ await store.insert({
1090
+ tableName: TABLE_TRACES,
1091
+ record: trace,
1092
+ });
1093
+ }
1094
+
1095
+ const page1 = await store.getTraces({
1096
+ scope: 'test-scope',
1097
+ page: 0,
1098
+ perPage: 8,
1099
+ returnPaginationResults: true,
1100
+ });
1101
+ expect(page1.traces).toHaveLength(8);
1102
+ expect(page1.total).toBe(18);
1103
+ expect(page1.page).toBe(0);
1104
+ expect(page1.perPage).toBe(8);
1105
+ expect(page1.hasMore).toBe(true);
1106
+
1107
+ const page3 = await store.getTraces({
1108
+ scope: 'test-scope',
1109
+ page: 2,
1110
+ perPage: 8,
1111
+ returnPaginationResults: true,
1112
+ });
1113
+ expect(page3.traces).toHaveLength(2);
1114
+ expect(page3.total).toBe(18);
1115
+ expect(page3.hasMore).toBe(false);
1116
+ });
1117
+
1118
+ it('should filter by attributes with pagination', async () => {
1119
+ const tracesWithAttr = Array.from({ length: 8 }, (_, i) =>
1120
+ createSampleTrace(`trace-${i}`, 'test-scope', { environment: 'prod' }),
1121
+ );
1122
+ const tracesWithoutAttr = Array.from({ length: 5 }, (_, i) =>
1123
+ createSampleTrace(`trace-other-${i}`, 'test-scope', { environment: 'dev' }),
1124
+ );
1125
+
1126
+ for (const trace of [...tracesWithAttr, ...tracesWithoutAttr]) {
1127
+ await store.insert({
1128
+ tableName: TABLE_TRACES,
1129
+ record: trace,
1130
+ });
1131
+ }
1132
+
1133
+ const prodTraces = await store.getTraces({
1134
+ scope: 'test-scope',
1135
+ attributes: { environment: 'prod' },
1136
+ page: 0,
1137
+ perPage: 5,
1138
+ returnPaginationResults: true,
1139
+ });
1140
+ expect(prodTraces.traces).toHaveLength(5);
1141
+ expect(prodTraces.total).toBe(8);
1142
+ expect(prodTraces.hasMore).toBe(true);
1143
+
1144
+ const devTraces = await store.getTraces({
1145
+ scope: 'test-scope',
1146
+ attributes: { environment: 'dev' },
1147
+ page: 0,
1148
+ perPage: 10,
1149
+ returnPaginationResults: true,
1150
+ });
1151
+ expect(devTraces.traces).toHaveLength(5);
1152
+ expect(devTraces.total).toBe(5);
1153
+ expect(devTraces.hasMore).toBe(false);
1154
+ });
1155
+ });
1156
+
1157
+ describe('Enhanced existing methods with pagination', () => {
1158
+ it('should support pagination in getThreadsByResourceId', async () => {
1159
+ const resourceId = 'enhanced-resource';
1160
+ const threads = Array.from({ length: 17 }, () => ({
1161
+ ...createSampleThread(),
1162
+ resourceId,
1163
+ }));
1164
+
1165
+ for (const thread of threads) {
1166
+ await store.saveThread({ thread });
1167
+ }
1168
+
1169
+ const page1 = await store.getThreadsByResourceId({ resourceId, page: 0, perPage: 7 });
1170
+ expect(page1.threads).toHaveLength(7);
1171
+
1172
+ const page3 = await store.getThreadsByResourceId({ resourceId, page: 2, perPage: 7 });
1173
+ expect(page3.threads).toHaveLength(3);
1174
+
1175
+ const limited = await store.getThreadsByResourceId({ resourceId, page: 1, perPage: 5 });
1176
+ expect(limited.threads).toHaveLength(5);
1177
+ });
1178
+ });
1179
+ });
867
1180
  });