@mastra/pg 0.2.7-alpha.1 → 0.2.7-alpha.3

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/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import { createHash } from 'crypto';
2
2
  import { MastraVector } from '@mastra/core/vector';
3
3
  import pg from 'pg';
4
4
  import { BaseFilterTranslator } from '@mastra/core/vector/filter';
5
- import { MastraStorage } from '@mastra/core/storage';
5
+ import { MastraStorage, TABLE_EVALS, TABLE_TRACES, TABLE_WORKFLOW_SNAPSHOT, TABLE_THREADS, TABLE_MESSAGES } from '@mastra/core/storage';
6
6
  import pgPromise from 'pg-promise';
7
7
 
8
8
  // src/vector/index.ts
@@ -701,12 +701,46 @@ var PostgresStore = class extends MastraStorage {
701
701
  port: config.port,
702
702
  database: config.database,
703
703
  user: config.user,
704
- password: config.password
704
+ password: config.password,
705
+ ssl: config.ssl
705
706
  }
706
707
  );
707
708
  }
708
- getEvalsByAgentName(_agentName, _type) {
709
- throw new Error("Method not implemented.");
709
+ getEvalsByAgentName(agentName, type) {
710
+ try {
711
+ const baseQuery = `SELECT * FROM ${TABLE_EVALS} WHERE agent_name = $1`;
712
+ const typeCondition = type === "test" ? " AND test_info IS NOT NULL AND test_info->>'testPath' IS NOT NULL" : type === "live" ? " AND (test_info IS NULL OR test_info->>'testPath' IS NULL)" : "";
713
+ const query = `${baseQuery}${typeCondition} ORDER BY created_at DESC`;
714
+ return this.db.manyOrNone(query, [agentName]).then((rows) => rows?.map((row) => this.transformEvalRow(row)) ?? []);
715
+ } catch (error) {
716
+ if (error instanceof Error && error.message.includes("relation") && error.message.includes("does not exist")) {
717
+ return Promise.resolve([]);
718
+ }
719
+ console.error("Failed to get evals for the specified agent: " + error?.message);
720
+ throw error;
721
+ }
722
+ }
723
+ transformEvalRow(row) {
724
+ let testInfoValue = null;
725
+ if (row.test_info) {
726
+ try {
727
+ testInfoValue = typeof row.test_info === "string" ? JSON.parse(row.test_info) : row.test_info;
728
+ } catch (e) {
729
+ console.warn("Failed to parse test_info:", e);
730
+ }
731
+ }
732
+ return {
733
+ agentName: row.agent_name,
734
+ input: row.input,
735
+ output: row.output,
736
+ result: row.result,
737
+ metricName: row.metric_name,
738
+ instructions: row.instructions,
739
+ testInfo: testInfoValue,
740
+ globalRunId: row.global_run_id,
741
+ runId: row.run_id,
742
+ createdAt: row.created_at
743
+ };
710
744
  }
711
745
  async batchInsert({ tableName, records }) {
712
746
  try {
@@ -726,7 +760,8 @@ var PostgresStore = class extends MastraStorage {
726
760
  scope,
727
761
  page,
728
762
  perPage,
729
- attributes
763
+ attributes,
764
+ filters
730
765
  }) {
731
766
  let idx = 1;
732
767
  const limit = perPage;
@@ -744,6 +779,11 @@ var PostgresStore = class extends MastraStorage {
744
779
  conditions.push(`attributes->>'${key}' = $${idx++}`);
745
780
  });
746
781
  }
782
+ if (filters) {
783
+ Object.entries(filters).forEach(([key, value]) => {
784
+ conditions.push(`${key} = $${idx++}`);
785
+ });
786
+ }
747
787
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
748
788
  if (name) {
749
789
  args.push(name);
@@ -756,15 +796,17 @@ var PostgresStore = class extends MastraStorage {
756
796
  args.push(value);
757
797
  }
758
798
  }
799
+ if (filters) {
800
+ for (const [key, value] of Object.entries(filters)) {
801
+ args.push(value);
802
+ }
803
+ }
759
804
  console.log(
760
805
  "QUERY",
761
- `SELECT * FROM ${MastraStorage.TABLE_TRACES} ${whereClause} ORDER BY "createdAt" DESC LIMIT ${limit} OFFSET ${offset}`,
762
- args
763
- );
764
- const result = await this.db.manyOrNone(
765
- `SELECT * FROM ${MastraStorage.TABLE_TRACES} ${whereClause} ORDER BY "createdAt" DESC LIMIT ${limit} OFFSET ${offset}`,
806
+ `SELECT * FROM ${TABLE_TRACES} ${whereClause} ORDER BY "createdAt" DESC LIMIT ${limit} OFFSET ${offset}`,
766
807
  args
767
808
  );
809
+ const result = await this.db.manyOrNone(`SELECT * FROM ${TABLE_TRACES} ${whereClause} ORDER BY "createdAt" DESC LIMIT ${limit} OFFSET ${offset}`, args);
768
810
  if (!result) {
769
811
  return [];
770
812
  }
@@ -800,7 +842,7 @@ var PostgresStore = class extends MastraStorage {
800
842
  CREATE TABLE IF NOT EXISTS ${tableName} (
801
843
  ${columns}
802
844
  );
803
- ${tableName === MastraStorage.TABLE_WORKFLOW_SNAPSHOT ? `
845
+ ${tableName === TABLE_WORKFLOW_SNAPSHOT ? `
804
846
  DO $$ BEGIN
805
847
  IF NOT EXISTS (
806
848
  SELECT 1 FROM pg_constraint WHERE conname = 'mastra_workflow_snapshot_workflow_name_run_id_key'
@@ -849,7 +891,7 @@ var PostgresStore = class extends MastraStorage {
849
891
  if (!result) {
850
892
  return null;
851
893
  }
852
- if (tableName === MastraStorage.TABLE_WORKFLOW_SNAPSHOT) {
894
+ if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
853
895
  const snapshot = result;
854
896
  if (typeof snapshot.snapshot === "string") {
855
897
  snapshot.snapshot = JSON.parse(snapshot.snapshot);
@@ -872,7 +914,7 @@ var PostgresStore = class extends MastraStorage {
872
914
  metadata,
873
915
  "createdAt",
874
916
  "updatedAt"
875
- FROM "${MastraStorage.TABLE_THREADS}"
917
+ FROM "${TABLE_THREADS}"
876
918
  WHERE id = $1`,
877
919
  [threadId]
878
920
  );
@@ -900,7 +942,7 @@ var PostgresStore = class extends MastraStorage {
900
942
  metadata,
901
943
  "createdAt",
902
944
  "updatedAt"
903
- FROM "${MastraStorage.TABLE_THREADS}"
945
+ FROM "${TABLE_THREADS}"
904
946
  WHERE "resourceId" = $1`,
905
947
  [resourceId]
906
948
  );
@@ -918,7 +960,7 @@ var PostgresStore = class extends MastraStorage {
918
960
  async saveThread({ thread }) {
919
961
  try {
920
962
  await this.db.none(
921
- `INSERT INTO "${MastraStorage.TABLE_THREADS}" (
963
+ `INSERT INTO "${TABLE_THREADS}" (
922
964
  id,
923
965
  "resourceId",
924
966
  title,
@@ -962,7 +1004,7 @@ var PostgresStore = class extends MastraStorage {
962
1004
  ...metadata
963
1005
  };
964
1006
  const thread = await this.db.one(
965
- `UPDATE "${MastraStorage.TABLE_THREADS}"
1007
+ `UPDATE "${TABLE_THREADS}"
966
1008
  SET title = $1,
967
1009
  metadata = $2,
968
1010
  "updatedAt" = $3
@@ -984,8 +1026,8 @@ var PostgresStore = class extends MastraStorage {
984
1026
  async deleteThread({ threadId }) {
985
1027
  try {
986
1028
  await this.db.tx(async (t) => {
987
- await t.none(`DELETE FROM "${MastraStorage.TABLE_MESSAGES}" WHERE thread_id = $1`, [threadId]);
988
- await t.none(`DELETE FROM "${MastraStorage.TABLE_THREADS}" WHERE id = $1`, [threadId]);
1029
+ await t.none(`DELETE FROM "${TABLE_MESSAGES}" WHERE thread_id = $1`, [threadId]);
1030
+ await t.none(`DELETE FROM "${TABLE_THREADS}" WHERE id = $1`, [threadId]);
989
1031
  });
990
1032
  } catch (error) {
991
1033
  console.error("Error deleting thread:", error);
@@ -1004,7 +1046,7 @@ var PostgresStore = class extends MastraStorage {
1004
1046
  SELECT
1005
1047
  *,
1006
1048
  ROW_NUMBER() OVER (ORDER BY "createdAt" DESC) as row_num
1007
- FROM "${MastraStorage.TABLE_MESSAGES}"
1049
+ FROM "${TABLE_MESSAGES}"
1008
1050
  WHERE thread_id = $1
1009
1051
  )
1010
1052
  SELECT
@@ -1047,7 +1089,7 @@ var PostgresStore = class extends MastraStorage {
1047
1089
  type,
1048
1090
  "createdAt",
1049
1091
  thread_id AS "threadId"
1050
- FROM "${MastraStorage.TABLE_MESSAGES}"
1092
+ FROM "${TABLE_MESSAGES}"
1051
1093
  WHERE thread_id = $1
1052
1094
  AND id != ALL($2)
1053
1095
  ORDER BY "createdAt" DESC
@@ -1085,7 +1127,7 @@ var PostgresStore = class extends MastraStorage {
1085
1127
  await this.db.tx(async (t) => {
1086
1128
  for (const message of messages) {
1087
1129
  await t.none(
1088
- `INSERT INTO "${MastraStorage.TABLE_MESSAGES}" (id, thread_id, content, "createdAt", role, type)
1130
+ `INSERT INTO "${TABLE_MESSAGES}" (id, thread_id, content, "createdAt", role, type)
1089
1131
  VALUES ($1, $2, $3, $4, $5, $6)`,
1090
1132
  [
1091
1133
  message.id,
@@ -1112,7 +1154,7 @@ var PostgresStore = class extends MastraStorage {
1112
1154
  try {
1113
1155
  const now = (/* @__PURE__ */ new Date()).toISOString();
1114
1156
  await this.db.none(
1115
- `INSERT INTO "${MastraStorage.TABLE_WORKFLOW_SNAPSHOT}" (
1157
+ `INSERT INTO "${TABLE_WORKFLOW_SNAPSHOT}" (
1116
1158
  workflow_name,
1117
1159
  run_id,
1118
1160
  snapshot,
@@ -1135,7 +1177,7 @@ var PostgresStore = class extends MastraStorage {
1135
1177
  }) {
1136
1178
  try {
1137
1179
  const result = await this.load({
1138
- tableName: MastraStorage.TABLE_WORKFLOW_SNAPSHOT,
1180
+ tableName: TABLE_WORKFLOW_SNAPSHOT,
1139
1181
  keys: {
1140
1182
  workflow_name: workflowName,
1141
1183
  run_id: runId
@@ -1150,6 +1192,67 @@ var PostgresStore = class extends MastraStorage {
1150
1192
  throw error;
1151
1193
  }
1152
1194
  }
1195
+ async getWorkflowRuns({
1196
+ workflowName,
1197
+ fromDate,
1198
+ toDate,
1199
+ limit,
1200
+ offset
1201
+ } = {}) {
1202
+ const conditions = [];
1203
+ const values = [];
1204
+ let paramIndex = 1;
1205
+ if (workflowName) {
1206
+ conditions.push(`workflow_name = $${paramIndex}`);
1207
+ values.push(workflowName);
1208
+ paramIndex++;
1209
+ }
1210
+ if (fromDate) {
1211
+ conditions.push(`"createdAt" >= $${paramIndex}`);
1212
+ values.push(fromDate);
1213
+ paramIndex++;
1214
+ }
1215
+ if (toDate) {
1216
+ conditions.push(`"createdAt" <= $${paramIndex}`);
1217
+ values.push(toDate);
1218
+ paramIndex++;
1219
+ }
1220
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1221
+ let total = 0;
1222
+ if (limit !== void 0 && offset !== void 0) {
1223
+ const countResult = await this.db.one(
1224
+ `SELECT COUNT(*) as count FROM ${TABLE_WORKFLOW_SNAPSHOT} ${whereClause}`,
1225
+ values
1226
+ );
1227
+ total = Number(countResult.count);
1228
+ }
1229
+ const query = `
1230
+ SELECT * FROM ${TABLE_WORKFLOW_SNAPSHOT}
1231
+ ${whereClause}
1232
+ ORDER BY "createdAt" DESC
1233
+ ${limit !== void 0 && offset !== void 0 ? ` LIMIT $${paramIndex} OFFSET $${paramIndex + 1}` : ""}
1234
+ `;
1235
+ const queryValues = limit !== void 0 && offset !== void 0 ? [...values, limit, offset] : values;
1236
+ const result = await this.db.manyOrNone(query, queryValues);
1237
+ const runs = (result || []).map((row) => {
1238
+ let parsedSnapshot = row.snapshot;
1239
+ if (typeof parsedSnapshot === "string") {
1240
+ try {
1241
+ parsedSnapshot = JSON.parse(row.snapshot);
1242
+ } catch (e) {
1243
+ console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
1244
+ }
1245
+ }
1246
+ return {
1247
+ workflowName: row.workflow_name,
1248
+ runId: row.run_id,
1249
+ snapshot: parsedSnapshot,
1250
+ createdAt: row.createdAt,
1251
+ updatedAt: row.updatedAt
1252
+ };
1253
+ });
1254
+ return { runs, total: total || runs.length };
1255
+ }
1153
1256
  async close() {
1154
1257
  this.pgp.end();
1155
1258
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/pg",
3
- "version": "0.2.7-alpha.1",
3
+ "version": "0.2.7-alpha.3",
4
4
  "description": "Postgres provider for Mastra - includes both vector and db storage capabilities",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -21,7 +21,7 @@
21
21
  "dependencies": {
22
22
  "pg": "^8.13.3",
23
23
  "pg-promise": "^11.11.0",
24
- "@mastra/core": "^0.8.0-alpha.1"
24
+ "@mastra/core": "^0.8.0-alpha.3"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@microsoft/api-extractor": "^7.52.1",
@@ -1,4 +1,6 @@
1
1
  import { randomUUID } from 'crypto';
2
+ import type { MetricResult } from '@mastra/core/eval';
3
+ import { TABLE_WORKFLOW_SNAPSHOT, TABLE_MESSAGES, TABLE_THREADS, TABLE_EVALS } from '@mastra/core/storage';
2
4
  import type { WorkflowRunState } from '@mastra/core/workflows';
3
5
  import { describe, it, expect, beforeAll, beforeEach, afterAll } from 'vitest';
4
6
 
@@ -33,6 +35,49 @@ const createSampleMessage = (threadId: string) =>
33
35
  createdAt: new Date(),
34
36
  }) as any;
35
37
 
38
+ const createSampleWorkflowSnapshot = (status: string, createdAt?: Date) => {
39
+ const runId = `run-${randomUUID()}`;
40
+ const stepId = `step-${randomUUID()}`;
41
+ const timestamp = createdAt || new Date();
42
+ const snapshot = {
43
+ result: { success: true },
44
+ value: {},
45
+ context: {
46
+ steps: {
47
+ [stepId]: {
48
+ status,
49
+ payload: {},
50
+ error: undefined,
51
+ },
52
+ },
53
+ triggerData: {},
54
+ attempts: {},
55
+ },
56
+ activePaths: [],
57
+ runId,
58
+ timestamp: timestamp.getTime(),
59
+ } as WorkflowRunState;
60
+ return { snapshot, runId, stepId };
61
+ };
62
+
63
+ const createSampleEval = (agentName: string, isTest = false) => {
64
+ const testInfo = isTest ? { testPath: 'test/path.ts', testName: 'Test Name' } : undefined;
65
+
66
+ return {
67
+ id: randomUUID(),
68
+ agentName,
69
+ input: 'Sample input',
70
+ output: 'Sample output',
71
+ result: { score: 0.8 } as MetricResult,
72
+ metricName: 'sample-metric',
73
+ instructions: 'Sample instructions',
74
+ testInfo,
75
+ globalRunId: `global-${randomUUID()}`,
76
+ runId: `run-${randomUUID()}`,
77
+ createdAt: new Date().toISOString(),
78
+ };
79
+ };
80
+
36
81
  describe('PostgresStore', () => {
37
82
  let store: PostgresStore;
38
83
 
@@ -43,15 +88,15 @@ describe('PostgresStore', () => {
43
88
 
44
89
  beforeEach(async () => {
45
90
  // Clear tables before each test
46
- await store.clearTable({ tableName: 'mastra_workflow_snapshot' });
47
- await store.clearTable({ tableName: 'mastra_messages' });
48
- await store.clearTable({ tableName: 'mastra_threads' });
91
+ await store.clearTable({ tableName: TABLE_WORKFLOW_SNAPSHOT });
92
+ await store.clearTable({ tableName: TABLE_MESSAGES });
93
+ await store.clearTable({ tableName: TABLE_THREADS });
94
+ await store.clearTable({ tableName: TABLE_EVALS });
49
95
  });
50
96
 
51
97
  describe('Thread Operations', () => {
52
98
  it('should create and retrieve a thread', async () => {
53
99
  const thread = createSampleThread();
54
- console.log('Saving thread:', thread);
55
100
 
56
101
  // Save thread
57
102
  const savedThread = await store.__saveThread({ thread });
@@ -81,7 +126,6 @@ describe('PostgresStore', () => {
81
126
 
82
127
  it('should update thread title and metadata', async () => {
83
128
  const thread = createSampleThread();
84
- console.log('Saving thread:', thread);
85
129
  await store.__saveThread({ thread });
86
130
 
87
131
  const newMetadata = { newKey: 'newValue' };
@@ -362,7 +406,7 @@ describe('PostgresStore', () => {
362
406
  await store.persistWorkflowSnapshot({
363
407
  workflowName,
364
408
  runId,
365
- snapshot: complexSnapshot as WorkflowRunState,
409
+ snapshot: complexSnapshot as unknown as WorkflowRunState,
366
410
  });
367
411
 
368
412
  const loadedSnapshot = await store.loadWorkflowSnapshot({
@@ -374,6 +418,233 @@ describe('PostgresStore', () => {
374
418
  });
375
419
  });
376
420
 
421
+ describe('getWorkflowRuns', () => {
422
+ beforeEach(async () => {
423
+ await store.clearTable({ tableName: TABLE_WORKFLOW_SNAPSHOT });
424
+ });
425
+ it('returns empty array when no workflows exist', async () => {
426
+ const { runs, total } = await store.__getWorkflowRuns();
427
+ expect(runs).toEqual([]);
428
+ expect(total).toBe(0);
429
+ });
430
+
431
+ it('returns all workflows by default', async () => {
432
+ const workflowName1 = 'default_test_1';
433
+ const workflowName2 = 'default_test_2';
434
+
435
+ const { snapshot: workflow1, runId: runId1, stepId: stepId1 } = createSampleWorkflowSnapshot('completed');
436
+ const { snapshot: workflow2, runId: runId2, stepId: stepId2 } = createSampleWorkflowSnapshot('running');
437
+
438
+ await store.persistWorkflowSnapshot({ workflowName: workflowName1, runId: runId1, snapshot: workflow1 });
439
+ await new Promise(resolve => setTimeout(resolve, 10)); // Small delay to ensure different timestamps
440
+ await store.persistWorkflowSnapshot({ workflowName: workflowName2, runId: runId2, snapshot: workflow2 });
441
+
442
+ const { runs, total } = await store.__getWorkflowRuns();
443
+ expect(runs).toHaveLength(2);
444
+ expect(total).toBe(2);
445
+ expect(runs[0]!.workflowName).toBe(workflowName2); // Most recent first
446
+ expect(runs[1]!.workflowName).toBe(workflowName1);
447
+ const firstSnapshot = runs[0]!.snapshot as WorkflowRunState;
448
+ const secondSnapshot = runs[1]!.snapshot as WorkflowRunState;
449
+ expect(firstSnapshot.context?.steps[stepId2]?.status).toBe('running');
450
+ expect(secondSnapshot.context?.steps[stepId1]?.status).toBe('completed');
451
+ });
452
+
453
+ it('filters by workflow name', async () => {
454
+ const workflowName1 = 'filter_test_1';
455
+ const workflowName2 = 'filter_test_2';
456
+
457
+ const { snapshot: workflow1, runId: runId1, stepId: stepId1 } = createSampleWorkflowSnapshot('completed');
458
+ const { snapshot: workflow2, runId: runId2 } = createSampleWorkflowSnapshot('failed');
459
+
460
+ await store.persistWorkflowSnapshot({ workflowName: workflowName1, runId: runId1, snapshot: workflow1 });
461
+ await new Promise(resolve => setTimeout(resolve, 10)); // Small delay to ensure different timestamps
462
+ await store.persistWorkflowSnapshot({ workflowName: workflowName2, runId: runId2, snapshot: workflow2 });
463
+
464
+ const { runs, total } = await store.__getWorkflowRuns({ workflowName: workflowName1 });
465
+ expect(runs).toHaveLength(1);
466
+ expect(total).toBe(1);
467
+ expect(runs[0]!.workflowName).toBe(workflowName1);
468
+ const snapshot = runs[0]!.snapshot as WorkflowRunState;
469
+ expect(snapshot.context?.steps[stepId1]?.status).toBe('completed');
470
+ });
471
+
472
+ it('filters by date range', async () => {
473
+ const now = new Date();
474
+ const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000);
475
+ const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000);
476
+ const workflowName1 = 'date_test_1';
477
+ const workflowName2 = 'date_test_2';
478
+ const workflowName3 = 'date_test_3';
479
+
480
+ const { snapshot: workflow1, runId: runId1 } = createSampleWorkflowSnapshot('completed');
481
+ const { snapshot: workflow2, runId: runId2, stepId: stepId2 } = createSampleWorkflowSnapshot('running');
482
+ const { snapshot: workflow3, runId: runId3, stepId: stepId3 } = createSampleWorkflowSnapshot('waiting');
483
+
484
+ await store.insert({
485
+ tableName: TABLE_WORKFLOW_SNAPSHOT,
486
+ record: {
487
+ workflow_name: workflowName1,
488
+ run_id: runId1,
489
+ snapshot: workflow1,
490
+ createdAt: twoDaysAgo,
491
+ updatedAt: twoDaysAgo,
492
+ },
493
+ });
494
+ await store.insert({
495
+ tableName: TABLE_WORKFLOW_SNAPSHOT,
496
+ record: {
497
+ workflow_name: workflowName2,
498
+ run_id: runId2,
499
+ snapshot: workflow2,
500
+ createdAt: yesterday,
501
+ updatedAt: yesterday,
502
+ },
503
+ });
504
+ await store.insert({
505
+ tableName: TABLE_WORKFLOW_SNAPSHOT,
506
+ record: {
507
+ workflow_name: workflowName3,
508
+ run_id: runId3,
509
+ snapshot: workflow3,
510
+ createdAt: now,
511
+ updatedAt: now,
512
+ },
513
+ });
514
+
515
+ const { runs } = await store.__getWorkflowRuns({
516
+ fromDate: yesterday,
517
+ toDate: now,
518
+ });
519
+
520
+ expect(runs).toHaveLength(2);
521
+ expect(runs[0]!.workflowName).toBe(workflowName3);
522
+ expect(runs[1]!.workflowName).toBe(workflowName2);
523
+ const firstSnapshot = runs[0]!.snapshot as WorkflowRunState;
524
+ const secondSnapshot = runs[1]!.snapshot as WorkflowRunState;
525
+ expect(firstSnapshot.context?.steps[stepId3]?.status).toBe('waiting');
526
+ expect(secondSnapshot.context?.steps[stepId2]?.status).toBe('running');
527
+ });
528
+
529
+ it('handles pagination', async () => {
530
+ const workflowName1 = 'page_test_1';
531
+ const workflowName2 = 'page_test_2';
532
+ const workflowName3 = 'page_test_3';
533
+
534
+ const { snapshot: workflow1, runId: runId1, stepId: stepId1 } = createSampleWorkflowSnapshot('completed');
535
+ const { snapshot: workflow2, runId: runId2, stepId: stepId2 } = createSampleWorkflowSnapshot('running');
536
+ const { snapshot: workflow3, runId: runId3, stepId: stepId3 } = createSampleWorkflowSnapshot('waiting');
537
+
538
+ await store.persistWorkflowSnapshot({ workflowName: workflowName1, runId: runId1, snapshot: workflow1 });
539
+ await new Promise(resolve => setTimeout(resolve, 10)); // Small delay to ensure different timestamps
540
+ await store.persistWorkflowSnapshot({ workflowName: workflowName2, runId: runId2, snapshot: workflow2 });
541
+ await new Promise(resolve => setTimeout(resolve, 10)); // Small delay to ensure different timestamps
542
+ await store.persistWorkflowSnapshot({ workflowName: workflowName3, runId: runId3, snapshot: workflow3 });
543
+
544
+ // Get first page
545
+ const page1 = await store.__getWorkflowRuns({ limit: 2, offset: 0 });
546
+ expect(page1.runs).toHaveLength(2);
547
+ expect(page1.total).toBe(3); // Total count of all records
548
+ expect(page1.runs[0]!.workflowName).toBe(workflowName3);
549
+ expect(page1.runs[1]!.workflowName).toBe(workflowName2);
550
+ const firstSnapshot = page1.runs[0]!.snapshot as WorkflowRunState;
551
+ const secondSnapshot = page1.runs[1]!.snapshot as WorkflowRunState;
552
+ expect(firstSnapshot.context?.steps[stepId3]?.status).toBe('waiting');
553
+ expect(secondSnapshot.context?.steps[stepId2]?.status).toBe('running');
554
+
555
+ // Get second page
556
+ const page2 = await store.__getWorkflowRuns({ limit: 2, offset: 2 });
557
+ expect(page2.runs).toHaveLength(1);
558
+ expect(page2.total).toBe(3);
559
+ expect(page2.runs[0]!.workflowName).toBe(workflowName1);
560
+ const snapshot = page2.runs[0]!.snapshot as WorkflowRunState;
561
+ expect(snapshot.context?.steps[stepId1]?.status).toBe('completed');
562
+ });
563
+ });
564
+
565
+ describe('Eval Operations', () => {
566
+ it('should retrieve evals by agent name', async () => {
567
+ const agentName = `test-agent-${randomUUID()}`;
568
+
569
+ // Create sample evals
570
+ const liveEval = createSampleEval(agentName, false);
571
+ const testEval = createSampleEval(agentName, true);
572
+ const otherAgentEval = createSampleEval(`other-agent-${randomUUID()}`, false);
573
+
574
+ // Insert evals
575
+ await store.insert({
576
+ tableName: TABLE_EVALS,
577
+ record: {
578
+ agent_name: liveEval.agentName,
579
+ input: liveEval.input,
580
+ output: liveEval.output,
581
+ result: liveEval.result,
582
+ metric_name: liveEval.metricName,
583
+ instructions: liveEval.instructions,
584
+ test_info: null,
585
+ global_run_id: liveEval.globalRunId,
586
+ run_id: liveEval.runId,
587
+ created_at: liveEval.createdAt,
588
+ createdAt: new Date(liveEval.createdAt),
589
+ },
590
+ });
591
+
592
+ await store.insert({
593
+ tableName: TABLE_EVALS,
594
+ record: {
595
+ agent_name: testEval.agentName,
596
+ input: testEval.input,
597
+ output: testEval.output,
598
+ result: testEval.result,
599
+ metric_name: testEval.metricName,
600
+ instructions: testEval.instructions,
601
+ test_info: JSON.stringify(testEval.testInfo),
602
+ global_run_id: testEval.globalRunId,
603
+ run_id: testEval.runId,
604
+ created_at: testEval.createdAt,
605
+ createdAt: new Date(testEval.createdAt),
606
+ },
607
+ });
608
+
609
+ await store.insert({
610
+ tableName: TABLE_EVALS,
611
+ record: {
612
+ agent_name: otherAgentEval.agentName,
613
+ input: otherAgentEval.input,
614
+ output: otherAgentEval.output,
615
+ result: otherAgentEval.result,
616
+ metric_name: otherAgentEval.metricName,
617
+ instructions: otherAgentEval.instructions,
618
+ test_info: null,
619
+ global_run_id: otherAgentEval.globalRunId,
620
+ run_id: otherAgentEval.runId,
621
+ created_at: otherAgentEval.createdAt,
622
+ createdAt: new Date(otherAgentEval.createdAt),
623
+ },
624
+ });
625
+
626
+ // Test getting all evals for the agent
627
+ const allEvals = await store.getEvalsByAgentName(agentName);
628
+ expect(allEvals).toHaveLength(2);
629
+ expect(allEvals.map(e => e.runId)).toEqual(expect.arrayContaining([liveEval.runId, testEval.runId]));
630
+
631
+ // Test getting only live evals
632
+ const liveEvals = await store.getEvalsByAgentName(agentName, 'live');
633
+ expect(liveEvals).toHaveLength(1);
634
+ expect(liveEvals[0].runId).toBe(liveEval.runId);
635
+
636
+ // Test getting only test evals
637
+ const testEvals = await store.getEvalsByAgentName(agentName, 'test');
638
+ expect(testEvals).toHaveLength(1);
639
+ expect(testEvals[0].runId).toBe(testEval.runId);
640
+ expect(testEvals[0].testInfo).toEqual(testEval.testInfo);
641
+
642
+ // Test getting evals for non-existent agent
643
+ const nonExistentEvals = await store.getEvalsByAgentName('non-existent-agent');
644
+ expect(nonExistentEvals).toHaveLength(0);
645
+ });
646
+ });
647
+
377
648
  afterAll(async () => {
378
649
  await store.close();
379
650
  });