@mastra/upstash 0.10.2-alpha.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.
- package/.turbo/turbo-build.log +8 -8
- package/CHANGELOG.md +41 -0
- package/PAGINATION.md +397 -0
- package/dist/_tsup-dts-rollup.d.cts +69 -4
- package/dist/_tsup-dts-rollup.d.ts +69 -4
- package/dist/index.cjs +2552 -1958
- package/dist/index.js +2552 -1958
- package/package.json +10 -10
- package/src/storage/index.ts +543 -108
- package/src/storage/upstash.test.ts +313 -0
|
@@ -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
|
});
|