@mastra/upstash 0.10.2 → 0.10.3-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 +17 -17
- package/CHANGELOG.md +26 -0
- package/PAGINATION.md +397 -0
- package/dist/_tsup-dts-rollup.d.cts +80 -6
- package/dist/_tsup-dts-rollup.d.ts +80 -6
- package/dist/index.cjs +1794 -1267
- package/dist/index.js +1794 -1267
- package/package.json +11 -10
- package/src/storage/index.ts +576 -121
- package/src/storage/upstash.test.ts +428 -53
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from 'crypto';
|
|
2
|
+
import { createSampleMessageV2, createSampleThread, createSampleWorkflowSnapshot } from '@internal/storage-test-utils';
|
|
2
3
|
import type { MastraMessageV2 } from '@mastra/core';
|
|
3
4
|
import type { TABLE_NAMES } from '@mastra/core/storage';
|
|
4
5
|
import {
|
|
@@ -9,56 +10,13 @@ import {
|
|
|
9
10
|
TABLE_TRACES,
|
|
10
11
|
} from '@mastra/core/storage';
|
|
11
12
|
import type { WorkflowRunState } from '@mastra/core/workflows';
|
|
12
|
-
import { describe, it, expect, beforeAll, beforeEach, afterAll, vi } from 'vitest';
|
|
13
|
+
import { describe, it, expect, beforeAll, beforeEach, afterAll, vi, afterEach } from 'vitest';
|
|
13
14
|
|
|
14
15
|
import { UpstashStore } from './index';
|
|
15
16
|
|
|
16
17
|
// Increase timeout for all tests in this file to 30 seconds
|
|
17
18
|
vi.setConfig({ testTimeout: 200_000, hookTimeout: 200_000 });
|
|
18
19
|
|
|
19
|
-
const createSampleThread = (date?: Date) => ({
|
|
20
|
-
id: `thread-${randomUUID()}`,
|
|
21
|
-
resourceId: `resource-${randomUUID()}`,
|
|
22
|
-
title: 'Test Thread',
|
|
23
|
-
createdAt: date || new Date(),
|
|
24
|
-
updatedAt: date || new Date(),
|
|
25
|
-
metadata: { key: 'value' },
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
const createSampleMessage = (threadId: string, content: string = 'Hello'): MastraMessageV2 => ({
|
|
29
|
-
id: `msg-${randomUUID()}`,
|
|
30
|
-
role: 'user',
|
|
31
|
-
threadId,
|
|
32
|
-
content: { format: 2, parts: [{ type: 'text', text: content }] },
|
|
33
|
-
createdAt: new Date(),
|
|
34
|
-
resourceId: `resource-${randomUUID()}`,
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
const createSampleWorkflowSnapshot = (status: string, createdAt?: Date) => {
|
|
38
|
-
const runId = `run-${randomUUID()}`;
|
|
39
|
-
const stepId = `step-${randomUUID()}`;
|
|
40
|
-
const timestamp = createdAt || new Date();
|
|
41
|
-
const snapshot: WorkflowRunState = {
|
|
42
|
-
value: {},
|
|
43
|
-
context: {
|
|
44
|
-
[stepId]: {
|
|
45
|
-
status: status,
|
|
46
|
-
payload: {},
|
|
47
|
-
error: undefined,
|
|
48
|
-
startedAt: timestamp.getTime(),
|
|
49
|
-
endedAt: new Date(timestamp.getTime() + 15000).getTime(),
|
|
50
|
-
},
|
|
51
|
-
input: {},
|
|
52
|
-
} as WorkflowRunState['context'],
|
|
53
|
-
serializedStepGraph: [],
|
|
54
|
-
activePaths: [],
|
|
55
|
-
suspendedPaths: {},
|
|
56
|
-
runId,
|
|
57
|
-
timestamp: timestamp.getTime(),
|
|
58
|
-
};
|
|
59
|
-
return { snapshot, runId, stepId };
|
|
60
|
-
};
|
|
61
|
-
|
|
62
20
|
const createSampleTrace = (name: string, scope?: string, attributes?: Record<string, string>) => ({
|
|
63
21
|
id: `trace-${randomUUID()}`,
|
|
64
22
|
parentSpanId: `span-${randomUUID()}`,
|
|
@@ -176,7 +134,7 @@ describe('UpstashStore', () => {
|
|
|
176
134
|
|
|
177
135
|
it('should create and retrieve a thread', async () => {
|
|
178
136
|
const now = new Date();
|
|
179
|
-
const thread = createSampleThread(now);
|
|
137
|
+
const thread = createSampleThread({ date: now });
|
|
180
138
|
|
|
181
139
|
const savedThread = await store.saveThread({ thread });
|
|
182
140
|
expect(savedThread).toEqual(thread);
|
|
@@ -236,6 +194,23 @@ describe('UpstashStore', () => {
|
|
|
236
194
|
const retrievedThreads = await store.getThreadsByResourceId({ resourceId });
|
|
237
195
|
expect(retrievedThreads).toHaveLength(total);
|
|
238
196
|
});
|
|
197
|
+
it('should delete thread and its messages', async () => {
|
|
198
|
+
const thread = createSampleThread();
|
|
199
|
+
await store.saveThread({ thread });
|
|
200
|
+
|
|
201
|
+
// Add some messages
|
|
202
|
+
const messages = [createSampleMessageV2({ threadId: thread.id }), createSampleMessageV2({ threadId: thread.id })];
|
|
203
|
+
await store.saveMessages({ messages, format: 'v2' });
|
|
204
|
+
|
|
205
|
+
await store.deleteThread({ threadId: thread.id });
|
|
206
|
+
|
|
207
|
+
const retrievedThread = await store.getThreadById({ threadId: thread.id });
|
|
208
|
+
expect(retrievedThread).toBeNull();
|
|
209
|
+
|
|
210
|
+
// Verify messages were also deleted
|
|
211
|
+
const retrievedMessages = await store.getMessages({ threadId: thread.id });
|
|
212
|
+
expect(retrievedMessages).toHaveLength(0);
|
|
213
|
+
});
|
|
239
214
|
});
|
|
240
215
|
|
|
241
216
|
describe('Date Handling', () => {
|
|
@@ -245,7 +220,7 @@ describe('UpstashStore', () => {
|
|
|
245
220
|
|
|
246
221
|
it('should handle Date objects in thread operations', async () => {
|
|
247
222
|
const now = new Date();
|
|
248
|
-
const thread = createSampleThread(now);
|
|
223
|
+
const thread = createSampleThread({ date: now });
|
|
249
224
|
|
|
250
225
|
await store.saveThread({ thread });
|
|
251
226
|
const retrievedThread = await store.getThreadById({ threadId: thread.id });
|
|
@@ -257,7 +232,7 @@ describe('UpstashStore', () => {
|
|
|
257
232
|
|
|
258
233
|
it('should handle ISO string dates in thread operations', async () => {
|
|
259
234
|
const now = new Date();
|
|
260
|
-
const thread = createSampleThread(now);
|
|
235
|
+
const thread = createSampleThread({ date: now });
|
|
261
236
|
|
|
262
237
|
await store.saveThread({ thread });
|
|
263
238
|
const retrievedThread = await store.getThreadById({ threadId: thread.id });
|
|
@@ -269,7 +244,7 @@ describe('UpstashStore', () => {
|
|
|
269
244
|
|
|
270
245
|
it('should handle mixed date formats in thread operations', async () => {
|
|
271
246
|
const now = new Date();
|
|
272
|
-
const thread = createSampleThread(now);
|
|
247
|
+
const thread = createSampleThread({ date: now });
|
|
273
248
|
|
|
274
249
|
await store.saveThread({ thread });
|
|
275
250
|
const retrievedThread = await store.getThreadById({ threadId: thread.id });
|
|
@@ -281,8 +256,8 @@ describe('UpstashStore', () => {
|
|
|
281
256
|
|
|
282
257
|
it('should handle date serialization in getThreadsByResourceId', async () => {
|
|
283
258
|
const now = new Date();
|
|
284
|
-
const thread1 = createSampleThread(now);
|
|
285
|
-
const thread2 = { ...createSampleThread(now), resourceId: thread1.resourceId };
|
|
259
|
+
const thread1 = createSampleThread({ date: now });
|
|
260
|
+
const thread2 = { ...createSampleThread({ date: now }), resourceId: thread1.resourceId };
|
|
286
261
|
const threads = [thread1, thread2];
|
|
287
262
|
|
|
288
263
|
await Promise.all(threads.map(thread => store.saveThread({ thread })));
|
|
@@ -320,9 +295,9 @@ describe('UpstashStore', () => {
|
|
|
320
295
|
|
|
321
296
|
it('should save and retrieve messages in order', async () => {
|
|
322
297
|
const messages: MastraMessageV2[] = [
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
298
|
+
createSampleMessageV2({ threadId, content: 'First' }),
|
|
299
|
+
createSampleMessageV2({ threadId, content: 'Second' }),
|
|
300
|
+
createSampleMessageV2({ threadId, content: 'Third' }),
|
|
326
301
|
];
|
|
327
302
|
|
|
328
303
|
await store.saveMessages({ messages: messages, format: 'v2' });
|
|
@@ -360,6 +335,140 @@ describe('UpstashStore', () => {
|
|
|
360
335
|
const retrievedMessages = await store.getMessages({ threadId, format: 'v2' });
|
|
361
336
|
expect(retrievedMessages[0].content).toEqual(messages[0].content);
|
|
362
337
|
});
|
|
338
|
+
|
|
339
|
+
describe('getPaginatedMessages', () => {
|
|
340
|
+
it('should return paginated messages with total count', async () => {
|
|
341
|
+
const thread = createSampleThread();
|
|
342
|
+
await store.saveThread({ thread });
|
|
343
|
+
|
|
344
|
+
const messages = Array.from({ length: 15 }, (_, i) =>
|
|
345
|
+
createSampleMessageV2({ threadId: thread.id, content: `Message ${i + 1}` }),
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
await store.saveMessages({ messages, format: 'v2' });
|
|
349
|
+
|
|
350
|
+
const page1 = await store.getMessages({
|
|
351
|
+
threadId: thread.id,
|
|
352
|
+
page: 0,
|
|
353
|
+
perPage: 5,
|
|
354
|
+
format: 'v2',
|
|
355
|
+
});
|
|
356
|
+
expect(page1.messages).toHaveLength(5);
|
|
357
|
+
expect(page1.total).toBe(15);
|
|
358
|
+
expect(page1.page).toBe(0);
|
|
359
|
+
expect(page1.perPage).toBe(5);
|
|
360
|
+
expect(page1.hasMore).toBe(true);
|
|
361
|
+
|
|
362
|
+
const page3 = await store.getMessages({
|
|
363
|
+
threadId: thread.id,
|
|
364
|
+
page: 2,
|
|
365
|
+
perPage: 5,
|
|
366
|
+
format: 'v2',
|
|
367
|
+
});
|
|
368
|
+
expect(page3.messages).toHaveLength(5);
|
|
369
|
+
expect(page3.total).toBe(15);
|
|
370
|
+
expect(page3.hasMore).toBe(false);
|
|
371
|
+
|
|
372
|
+
const page4 = await store.getMessages({
|
|
373
|
+
threadId: thread.id,
|
|
374
|
+
page: 3,
|
|
375
|
+
perPage: 5,
|
|
376
|
+
format: 'v2',
|
|
377
|
+
});
|
|
378
|
+
expect(page4.messages).toHaveLength(0);
|
|
379
|
+
expect(page4.total).toBe(15);
|
|
380
|
+
expect(page4.hasMore).toBe(false);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it('should maintain chronological order in pagination', async () => {
|
|
384
|
+
const thread = createSampleThread();
|
|
385
|
+
await store.saveThread({ thread });
|
|
386
|
+
|
|
387
|
+
const messages = Array.from({ length: 10 }, (_, i) => {
|
|
388
|
+
const message = createSampleMessageV2({ threadId: thread.id, content: `Message ${i + 1}` });
|
|
389
|
+
// Ensure different timestamps
|
|
390
|
+
message.createdAt = new Date(Date.now() + i * 1000);
|
|
391
|
+
return message;
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
await store.saveMessages({ messages, format: 'v2' });
|
|
395
|
+
|
|
396
|
+
const page1 = await store.getMessages({
|
|
397
|
+
threadId: thread.id,
|
|
398
|
+
page: 0,
|
|
399
|
+
perPage: 3,
|
|
400
|
+
format: 'v2',
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// Check that messages are in chronological order
|
|
404
|
+
for (let i = 1; i < page1.messages.length; i++) {
|
|
405
|
+
const prevMessage = page1.messages[i - 1] as MastraMessageV2;
|
|
406
|
+
const currentMessage = page1.messages[i] as MastraMessageV2;
|
|
407
|
+
expect(new Date(prevMessage.createdAt).getTime()).toBeLessThanOrEqual(
|
|
408
|
+
new Date(currentMessage.createdAt).getTime(),
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it('should maintain backward compatibility when no pagination params provided', async () => {
|
|
414
|
+
const thread = createSampleThread();
|
|
415
|
+
await store.saveThread({ thread });
|
|
416
|
+
|
|
417
|
+
const messages = Array.from({ length: 5 }, (_, i) =>
|
|
418
|
+
createSampleMessageV2({ threadId: thread.id, content: `Message ${i + 1}` }),
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
await store.saveMessages({ messages, format: 'v2' });
|
|
422
|
+
|
|
423
|
+
// Test original format without pagination - should return array
|
|
424
|
+
const messagesV1 = await store.getMessages({
|
|
425
|
+
threadId: thread.id,
|
|
426
|
+
format: 'v1',
|
|
427
|
+
});
|
|
428
|
+
expect(Array.isArray(messagesV1)).toBe(true);
|
|
429
|
+
expect(messagesV1).toHaveLength(5);
|
|
430
|
+
|
|
431
|
+
const messagesV2 = await store.getMessages({
|
|
432
|
+
threadId: thread.id,
|
|
433
|
+
format: 'v2',
|
|
434
|
+
});
|
|
435
|
+
expect(Array.isArray(messagesV2)).toBe(true);
|
|
436
|
+
expect(messagesV2).toHaveLength(5);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it('should support date filtering with pagination', async () => {
|
|
440
|
+
const thread = createSampleThread();
|
|
441
|
+
await store.saveThread({ thread });
|
|
442
|
+
|
|
443
|
+
const now = new Date();
|
|
444
|
+
const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
|
445
|
+
const tomorrow = new Date(now.getTime() + 24 * 60 * 60 * 1000);
|
|
446
|
+
|
|
447
|
+
const oldMessages = Array.from({ length: 3 }, (_, i) => {
|
|
448
|
+
const message = createSampleMessageV2({ threadId: thread.id, content: `Old Message ${i + 1}` });
|
|
449
|
+
message.createdAt = yesterday;
|
|
450
|
+
return message;
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
const newMessages = Array.from({ length: 4 }, (_, i) => {
|
|
454
|
+
const message = createSampleMessageV2({ threadId: thread.id, content: `New Message ${i + 1}` });
|
|
455
|
+
message.createdAt = tomorrow;
|
|
456
|
+
return message;
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
await store.saveMessages({ messages: [...oldMessages, ...newMessages], format: 'v2' });
|
|
460
|
+
|
|
461
|
+
const recentMessages = await store.getMessages({
|
|
462
|
+
threadId: thread.id,
|
|
463
|
+
page: 0,
|
|
464
|
+
perPage: 10,
|
|
465
|
+
fromDate: now,
|
|
466
|
+
format: 'v2',
|
|
467
|
+
});
|
|
468
|
+
expect(recentMessages.messages).toHaveLength(4);
|
|
469
|
+
expect(recentMessages.total).toBe(4);
|
|
470
|
+
});
|
|
471
|
+
});
|
|
363
472
|
});
|
|
364
473
|
|
|
365
474
|
describe('Trace Operations', () => {
|
|
@@ -864,4 +973,270 @@ describe('UpstashStore', () => {
|
|
|
864
973
|
expect(runs.length).toBe(0);
|
|
865
974
|
});
|
|
866
975
|
});
|
|
976
|
+
|
|
977
|
+
describe('alterTable (no-op/schemaless)', () => {
|
|
978
|
+
const TEST_TABLE = 'test_alter_table'; // Use "table" or "collection" as appropriate
|
|
979
|
+
beforeEach(async () => {
|
|
980
|
+
await store.clearTable({ tableName: TEST_TABLE as TABLE_NAMES });
|
|
981
|
+
});
|
|
982
|
+
|
|
983
|
+
afterEach(async () => {
|
|
984
|
+
await store.clearTable({ tableName: TEST_TABLE as TABLE_NAMES });
|
|
985
|
+
});
|
|
986
|
+
|
|
987
|
+
it('allows inserting records with new fields without alterTable', async () => {
|
|
988
|
+
await store.insert({
|
|
989
|
+
tableName: TEST_TABLE as TABLE_NAMES,
|
|
990
|
+
record: { id: '1', name: 'Alice' },
|
|
991
|
+
});
|
|
992
|
+
await store.insert({
|
|
993
|
+
tableName: TEST_TABLE as TABLE_NAMES,
|
|
994
|
+
record: { id: '2', name: 'Bob', newField: 123 },
|
|
995
|
+
});
|
|
996
|
+
|
|
997
|
+
const row = await store.load<{ id: string; name: string; newField?: number }>({
|
|
998
|
+
tableName: TEST_TABLE as TABLE_NAMES,
|
|
999
|
+
keys: { id: '2' },
|
|
1000
|
+
});
|
|
1001
|
+
expect(row?.newField).toBe(123);
|
|
1002
|
+
});
|
|
1003
|
+
|
|
1004
|
+
it('does not throw when calling alterTable (no-op)', async () => {
|
|
1005
|
+
await expect(
|
|
1006
|
+
store.alterTable({
|
|
1007
|
+
tableName: TEST_TABLE as TABLE_NAMES,
|
|
1008
|
+
schema: {
|
|
1009
|
+
id: { type: 'text', primaryKey: true, nullable: false },
|
|
1010
|
+
name: { type: 'text', nullable: true },
|
|
1011
|
+
extra: { type: 'integer', nullable: true },
|
|
1012
|
+
},
|
|
1013
|
+
ifNotExists: [],
|
|
1014
|
+
}),
|
|
1015
|
+
).resolves.not.toThrow();
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
it('can add multiple new fields at write time', async () => {
|
|
1019
|
+
await store.insert({
|
|
1020
|
+
tableName: TEST_TABLE as TABLE_NAMES,
|
|
1021
|
+
record: { id: '3', name: 'Charlie', age: 30, city: 'Paris' },
|
|
1022
|
+
});
|
|
1023
|
+
const row = await store.load<{ id: string; name: string; age?: number; city?: string }>({
|
|
1024
|
+
tableName: TEST_TABLE as TABLE_NAMES,
|
|
1025
|
+
keys: { id: '3' },
|
|
1026
|
+
});
|
|
1027
|
+
expect(row?.age).toBe(30);
|
|
1028
|
+
expect(row?.city).toBe('Paris');
|
|
1029
|
+
});
|
|
1030
|
+
|
|
1031
|
+
it('can retrieve all fields, including dynamically added ones', async () => {
|
|
1032
|
+
await store.insert({
|
|
1033
|
+
tableName: TEST_TABLE as TABLE_NAMES,
|
|
1034
|
+
record: { id: '4', name: 'Dana', hobby: 'skiing' },
|
|
1035
|
+
});
|
|
1036
|
+
const row = await store.load<{ id: string; name: string; hobby?: string }>({
|
|
1037
|
+
tableName: TEST_TABLE as TABLE_NAMES,
|
|
1038
|
+
keys: { id: '4' },
|
|
1039
|
+
});
|
|
1040
|
+
expect(row?.hobby).toBe('skiing');
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1043
|
+
it('does not restrict or error on arbitrary new fields', async () => {
|
|
1044
|
+
await expect(
|
|
1045
|
+
store.insert({
|
|
1046
|
+
tableName: TEST_TABLE as TABLE_NAMES,
|
|
1047
|
+
record: { id: '5', weirdField: { nested: true }, another: [1, 2, 3] },
|
|
1048
|
+
}),
|
|
1049
|
+
).resolves.not.toThrow();
|
|
1050
|
+
|
|
1051
|
+
const row = await store.load<{ id: string; weirdField?: any; another?: any }>({
|
|
1052
|
+
tableName: TEST_TABLE as TABLE_NAMES,
|
|
1053
|
+
keys: { id: '5' },
|
|
1054
|
+
});
|
|
1055
|
+
expect(row?.weirdField).toEqual({ nested: true });
|
|
1056
|
+
expect(row?.another).toEqual([1, 2, 3]);
|
|
1057
|
+
});
|
|
1058
|
+
});
|
|
1059
|
+
|
|
1060
|
+
describe('Pagination Features', () => {
|
|
1061
|
+
beforeEach(async () => {
|
|
1062
|
+
// Clear all test data
|
|
1063
|
+
await store.clearTable({ tableName: TABLE_THREADS });
|
|
1064
|
+
await store.clearTable({ tableName: TABLE_MESSAGES });
|
|
1065
|
+
await store.clearTable({ tableName: TABLE_EVALS });
|
|
1066
|
+
await store.clearTable({ tableName: TABLE_TRACES });
|
|
1067
|
+
});
|
|
1068
|
+
|
|
1069
|
+
describe('getEvals with pagination', () => {
|
|
1070
|
+
it('should return paginated evals with total count', async () => {
|
|
1071
|
+
const agentName = 'test-agent';
|
|
1072
|
+
const evals = Array.from({ length: 25 }, (_, i) => createSampleEval(agentName, i % 2 === 0));
|
|
1073
|
+
|
|
1074
|
+
// Insert all evals
|
|
1075
|
+
for (const evalRecord of evals) {
|
|
1076
|
+
await store.insert({
|
|
1077
|
+
tableName: TABLE_EVALS,
|
|
1078
|
+
record: evalRecord,
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
// Test page-based pagination
|
|
1083
|
+
const page1 = await store.getEvals({ agentName, page: 0, perPage: 10 });
|
|
1084
|
+
expect(page1.evals).toHaveLength(10);
|
|
1085
|
+
expect(page1.total).toBe(25);
|
|
1086
|
+
expect(page1.page).toBe(0);
|
|
1087
|
+
expect(page1.perPage).toBe(10);
|
|
1088
|
+
expect(page1.hasMore).toBe(true);
|
|
1089
|
+
|
|
1090
|
+
const page2 = await store.getEvals({ agentName, page: 1, perPage: 10 });
|
|
1091
|
+
expect(page2.evals).toHaveLength(10);
|
|
1092
|
+
expect(page2.total).toBe(25);
|
|
1093
|
+
expect(page2.hasMore).toBe(true);
|
|
1094
|
+
|
|
1095
|
+
const page3 = await store.getEvals({ agentName, page: 2, perPage: 10 });
|
|
1096
|
+
expect(page3.evals).toHaveLength(5);
|
|
1097
|
+
expect(page3.total).toBe(25);
|
|
1098
|
+
expect(page3.hasMore).toBe(false);
|
|
1099
|
+
});
|
|
1100
|
+
|
|
1101
|
+
it('should support limit/offset pagination', async () => {
|
|
1102
|
+
const agentName = 'test-agent-2';
|
|
1103
|
+
const evals = Array.from({ length: 15 }, () => createSampleEval(agentName));
|
|
1104
|
+
|
|
1105
|
+
for (const evalRecord of evals) {
|
|
1106
|
+
await store.insert({
|
|
1107
|
+
tableName: TABLE_EVALS,
|
|
1108
|
+
record: evalRecord,
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
// Test offset-based pagination
|
|
1113
|
+
const result1 = await store.getEvals({ agentName, limit: 5, offset: 0 });
|
|
1114
|
+
expect(result1.evals).toHaveLength(5);
|
|
1115
|
+
expect(result1.total).toBe(15);
|
|
1116
|
+
expect(result1.hasMore).toBe(true);
|
|
1117
|
+
|
|
1118
|
+
const result2 = await store.getEvals({ agentName, limit: 5, offset: 10 });
|
|
1119
|
+
expect(result2.evals).toHaveLength(5);
|
|
1120
|
+
expect(result2.total).toBe(15);
|
|
1121
|
+
expect(result2.hasMore).toBe(false);
|
|
1122
|
+
});
|
|
1123
|
+
|
|
1124
|
+
it('should filter by type with pagination', async () => {
|
|
1125
|
+
const agentName = 'test-agent-3';
|
|
1126
|
+
const testEvals = Array.from({ length: 10 }, () => createSampleEval(agentName, true));
|
|
1127
|
+
const liveEvals = Array.from({ length: 8 }, () => createSampleEval(agentName, false));
|
|
1128
|
+
|
|
1129
|
+
for (const evalRecord of [...testEvals, ...liveEvals]) {
|
|
1130
|
+
await store.insert({
|
|
1131
|
+
tableName: TABLE_EVALS,
|
|
1132
|
+
record: evalRecord,
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
const testResults = await store.getEvals({ agentName, type: 'test', page: 0, perPage: 5 });
|
|
1137
|
+
expect(testResults.evals).toHaveLength(5);
|
|
1138
|
+
expect(testResults.total).toBe(10);
|
|
1139
|
+
|
|
1140
|
+
const liveResults = await store.getEvals({ agentName, type: 'live', page: 0, perPage: 5 });
|
|
1141
|
+
expect(liveResults.evals).toHaveLength(5);
|
|
1142
|
+
expect(liveResults.total).toBe(8);
|
|
1143
|
+
});
|
|
1144
|
+
});
|
|
1145
|
+
|
|
1146
|
+
describe('getTracesPaginated', () => {
|
|
1147
|
+
it('should return paginated traces with total count', async () => {
|
|
1148
|
+
const traces = Array.from({ length: 18 }, (_, i) => createSampleTrace(`test-trace-${i}`, 'test-scope'));
|
|
1149
|
+
|
|
1150
|
+
for (const trace of traces) {
|
|
1151
|
+
await store.insert({
|
|
1152
|
+
tableName: TABLE_TRACES,
|
|
1153
|
+
record: trace,
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
const page1 = await store.getTraces({
|
|
1158
|
+
scope: 'test-scope',
|
|
1159
|
+
page: 0,
|
|
1160
|
+
perPage: 8,
|
|
1161
|
+
returnPaginationResults: true,
|
|
1162
|
+
});
|
|
1163
|
+
expect(page1.traces).toHaveLength(8);
|
|
1164
|
+
expect(page1.total).toBe(18);
|
|
1165
|
+
expect(page1.page).toBe(0);
|
|
1166
|
+
expect(page1.perPage).toBe(8);
|
|
1167
|
+
expect(page1.hasMore).toBe(true);
|
|
1168
|
+
|
|
1169
|
+
const page3 = await store.getTraces({
|
|
1170
|
+
scope: 'test-scope',
|
|
1171
|
+
page: 2,
|
|
1172
|
+
perPage: 8,
|
|
1173
|
+
returnPaginationResults: true,
|
|
1174
|
+
});
|
|
1175
|
+
expect(page3.traces).toHaveLength(2);
|
|
1176
|
+
expect(page3.total).toBe(18);
|
|
1177
|
+
expect(page3.hasMore).toBe(false);
|
|
1178
|
+
});
|
|
1179
|
+
|
|
1180
|
+
it('should filter by attributes with pagination', async () => {
|
|
1181
|
+
const tracesWithAttr = Array.from({ length: 8 }, (_, i) =>
|
|
1182
|
+
createSampleTrace(`trace-${i}`, 'test-scope', { environment: 'prod' }),
|
|
1183
|
+
);
|
|
1184
|
+
const tracesWithoutAttr = Array.from({ length: 5 }, (_, i) =>
|
|
1185
|
+
createSampleTrace(`trace-other-${i}`, 'test-scope', { environment: 'dev' }),
|
|
1186
|
+
);
|
|
1187
|
+
|
|
1188
|
+
for (const trace of [...tracesWithAttr, ...tracesWithoutAttr]) {
|
|
1189
|
+
await store.insert({
|
|
1190
|
+
tableName: TABLE_TRACES,
|
|
1191
|
+
record: trace,
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
const prodTraces = await store.getTraces({
|
|
1196
|
+
scope: 'test-scope',
|
|
1197
|
+
attributes: { environment: 'prod' },
|
|
1198
|
+
page: 0,
|
|
1199
|
+
perPage: 5,
|
|
1200
|
+
returnPaginationResults: true,
|
|
1201
|
+
});
|
|
1202
|
+
expect(prodTraces.traces).toHaveLength(5);
|
|
1203
|
+
expect(prodTraces.total).toBe(8);
|
|
1204
|
+
expect(prodTraces.hasMore).toBe(true);
|
|
1205
|
+
|
|
1206
|
+
const devTraces = await store.getTraces({
|
|
1207
|
+
scope: 'test-scope',
|
|
1208
|
+
attributes: { environment: 'dev' },
|
|
1209
|
+
page: 0,
|
|
1210
|
+
perPage: 10,
|
|
1211
|
+
returnPaginationResults: true,
|
|
1212
|
+
});
|
|
1213
|
+
expect(devTraces.traces).toHaveLength(5);
|
|
1214
|
+
expect(devTraces.total).toBe(5);
|
|
1215
|
+
expect(devTraces.hasMore).toBe(false);
|
|
1216
|
+
});
|
|
1217
|
+
});
|
|
1218
|
+
|
|
1219
|
+
describe('Enhanced existing methods with pagination', () => {
|
|
1220
|
+
it('should support pagination in getThreadsByResourceId', async () => {
|
|
1221
|
+
const resourceId = 'enhanced-resource';
|
|
1222
|
+
const threads = Array.from({ length: 17 }, () => ({
|
|
1223
|
+
...createSampleThread(),
|
|
1224
|
+
resourceId,
|
|
1225
|
+
}));
|
|
1226
|
+
|
|
1227
|
+
for (const thread of threads) {
|
|
1228
|
+
await store.saveThread({ thread });
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
const page1 = await store.getThreadsByResourceId({ resourceId, page: 0, perPage: 7 });
|
|
1232
|
+
expect(page1.threads).toHaveLength(7);
|
|
1233
|
+
|
|
1234
|
+
const page3 = await store.getThreadsByResourceId({ resourceId, page: 2, perPage: 7 });
|
|
1235
|
+
expect(page3.threads).toHaveLength(3);
|
|
1236
|
+
|
|
1237
|
+
const limited = await store.getThreadsByResourceId({ resourceId, page: 1, perPage: 5 });
|
|
1238
|
+
expect(limited.threads).toHaveLength(5);
|
|
1239
|
+
});
|
|
1240
|
+
});
|
|
1241
|
+
});
|
|
867
1242
|
});
|