@mastra/dynamodb 0.13.0 → 0.13.1-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/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { DynamoDBClient, DescribeTableCommand } from '@aws-sdk/client-dynamodb';
2
2
  import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
3
- import { MessageList } from '@mastra/core/agent';
4
3
  import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
5
- import { MastraStorage, TABLE_TRACES, TABLE_EVALS, TABLE_WORKFLOW_SNAPSHOT, TABLE_MESSAGES, TABLE_THREADS } from '@mastra/core/storage';
4
+ import { MastraStorage, StoreOperations, TracesStorage, TABLE_TRACES, WorkflowsStorage, MemoryStorage, resolveMessageLimit, ScoresStorage, LegacyEvalsStorage, TABLE_RESOURCES, TABLE_SCORERS, TABLE_EVALS, TABLE_WORKFLOW_SNAPSHOT, TABLE_MESSAGES, TABLE_THREADS } from '@mastra/core/storage';
6
5
  import { Entity, Service } from 'electrodb';
6
+ import { MessageList } from '@mastra/core/agent';
7
7
 
8
8
  // src/storage/index.ts
9
9
 
@@ -294,9 +294,9 @@ var messageEntity = new Entity({
294
294
  }
295
295
  }
296
296
  });
297
- var threadEntity = new Entity({
297
+ var resourceEntity = new Entity({
298
298
  model: {
299
- entity: "thread",
299
+ entity: "resource",
300
300
  version: "1",
301
301
  service: "mastra"
302
302
  },
@@ -310,25 +310,21 @@ var threadEntity = new Entity({
310
310
  type: "string",
311
311
  required: true
312
312
  },
313
- resourceId: {
314
- type: "string",
315
- required: true
316
- },
317
- title: {
313
+ workingMemory: {
318
314
  type: "string",
319
- required: true
315
+ required: false
320
316
  },
321
317
  metadata: {
322
318
  type: "string",
323
319
  required: false,
324
- // Stringify metadata object on set if it's not already a string
320
+ // Stringify content object on set if it's not already a string
325
321
  set: (value) => {
326
322
  if (value && typeof value !== "string") {
327
323
  return JSON.stringify(value);
328
324
  }
329
325
  return value;
330
326
  },
331
- // Parse JSON string to object on get
327
+ // Parse JSON string to object on get ONLY if it looks like JSON
332
328
  get: (value) => {
333
329
  if (value && typeof value === "string") {
334
330
  try {
@@ -346,18 +342,13 @@ var threadEntity = new Entity({
346
342
  indexes: {
347
343
  primary: {
348
344
  pk: { field: "pk", composite: ["entity", "id"] },
349
- sk: { field: "sk", composite: ["id"] }
350
- },
351
- byResource: {
352
- index: "gsi1",
353
- pk: { field: "gsi1pk", composite: ["entity", "resourceId"] },
354
- sk: { field: "gsi1sk", composite: ["createdAt"] }
345
+ sk: { field: "sk", composite: ["entity"] }
355
346
  }
356
347
  }
357
348
  });
358
- var traceEntity = new Entity({
349
+ var scoreEntity = new Entity({
359
350
  model: {
360
- entity: "trace",
351
+ entity: "score",
361
352
  version: "1",
362
353
  service: "mastra"
363
354
  },
@@ -371,123 +362,275 @@ var traceEntity = new Entity({
371
362
  type: "string",
372
363
  required: true
373
364
  },
374
- parentSpanId: {
365
+ scorerId: {
366
+ type: "string",
367
+ required: true
368
+ },
369
+ traceId: {
375
370
  type: "string",
376
371
  required: false
377
372
  },
378
- name: {
373
+ runId: {
379
374
  type: "string",
380
375
  required: true
381
376
  },
382
- traceId: {
377
+ scorer: {
383
378
  type: "string",
384
- required: true
379
+ required: true,
380
+ set: (value) => {
381
+ if (value && typeof value !== "string") {
382
+ return JSON.stringify(value);
383
+ }
384
+ return value;
385
+ },
386
+ get: (value) => {
387
+ if (value && typeof value === "string") {
388
+ try {
389
+ if (value.startsWith("{") || value.startsWith("[")) {
390
+ return JSON.parse(value);
391
+ }
392
+ } catch {
393
+ return value;
394
+ }
395
+ }
396
+ return value;
397
+ }
385
398
  },
386
- scope: {
399
+ extractStepResult: {
387
400
  type: "string",
388
- required: true
401
+ required: false,
402
+ set: (value) => {
403
+ if (value && typeof value !== "string") {
404
+ return JSON.stringify(value);
405
+ }
406
+ return value;
407
+ },
408
+ get: (value) => {
409
+ if (value && typeof value === "string") {
410
+ try {
411
+ if (value.startsWith("{") || value.startsWith("[")) {
412
+ return JSON.parse(value);
413
+ }
414
+ } catch {
415
+ return value;
416
+ }
417
+ }
418
+ return value;
419
+ }
389
420
  },
390
- kind: {
421
+ analyzeStepResult: {
422
+ type: "string",
423
+ required: false,
424
+ set: (value) => {
425
+ if (value && typeof value !== "string") {
426
+ return JSON.stringify(value);
427
+ }
428
+ return value;
429
+ },
430
+ get: (value) => {
431
+ if (value && typeof value === "string") {
432
+ try {
433
+ if (value.startsWith("{") || value.startsWith("[")) {
434
+ return JSON.parse(value);
435
+ }
436
+ } catch {
437
+ return value;
438
+ }
439
+ }
440
+ return value;
441
+ }
442
+ },
443
+ score: {
391
444
  type: "number",
392
445
  required: true
393
446
  },
394
- attributes: {
447
+ reason: {
395
448
  type: "string",
396
- // JSON stringified
397
- required: false,
398
- // Stringify object on set
449
+ required: false
450
+ },
451
+ extractPrompt: {
452
+ type: "string",
453
+ required: false
454
+ },
455
+ analyzePrompt: {
456
+ type: "string",
457
+ required: false
458
+ },
459
+ reasonPrompt: {
460
+ type: "string",
461
+ required: false
462
+ },
463
+ input: {
464
+ type: "string",
465
+ required: true,
399
466
  set: (value) => {
400
467
  if (value && typeof value !== "string") {
401
468
  return JSON.stringify(value);
402
469
  }
403
470
  return value;
404
471
  },
405
- // Parse JSON string to object on get
406
472
  get: (value) => {
407
- return value ? JSON.parse(value) : value;
473
+ if (value && typeof value === "string") {
474
+ try {
475
+ if (value.startsWith("{") || value.startsWith("[")) {
476
+ return JSON.parse(value);
477
+ }
478
+ } catch {
479
+ return value;
480
+ }
481
+ }
482
+ return value;
408
483
  }
409
484
  },
410
- status: {
485
+ output: {
486
+ type: "string",
487
+ required: true,
488
+ set: (value) => {
489
+ if (value && typeof value !== "string") {
490
+ return JSON.stringify(value);
491
+ }
492
+ return value;
493
+ },
494
+ get: (value) => {
495
+ if (value && typeof value === "string") {
496
+ try {
497
+ if (value.startsWith("{") || value.startsWith("[")) {
498
+ return JSON.parse(value);
499
+ }
500
+ } catch {
501
+ return value;
502
+ }
503
+ }
504
+ return value;
505
+ }
506
+ },
507
+ additionalContext: {
411
508
  type: "string",
412
- // JSON stringified
413
509
  required: false,
414
- // Stringify object on set
415
510
  set: (value) => {
416
511
  if (value && typeof value !== "string") {
417
512
  return JSON.stringify(value);
418
513
  }
419
514
  return value;
420
515
  },
421
- // Parse JSON string to object on get
422
516
  get: (value) => {
517
+ if (value && typeof value === "string") {
518
+ try {
519
+ if (value.startsWith("{") || value.startsWith("[")) {
520
+ return JSON.parse(value);
521
+ }
522
+ } catch {
523
+ return value;
524
+ }
525
+ }
423
526
  return value;
424
527
  }
425
528
  },
426
- events: {
529
+ runtimeContext: {
427
530
  type: "string",
428
- // JSON stringified
429
531
  required: false,
430
- // Stringify object on set
431
532
  set: (value) => {
432
533
  if (value && typeof value !== "string") {
433
534
  return JSON.stringify(value);
434
535
  }
435
536
  return value;
436
537
  },
437
- // Parse JSON string to object on get
438
538
  get: (value) => {
539
+ if (value && typeof value === "string") {
540
+ try {
541
+ if (value.startsWith("{") || value.startsWith("[")) {
542
+ return JSON.parse(value);
543
+ }
544
+ } catch {
545
+ return value;
546
+ }
547
+ }
439
548
  return value;
440
549
  }
441
550
  },
442
- links: {
551
+ entityType: {
552
+ type: "string",
553
+ required: false
554
+ },
555
+ entityData: {
443
556
  type: "string",
444
- // JSON stringified
445
557
  required: false,
446
- // Stringify object on set
447
558
  set: (value) => {
448
559
  if (value && typeof value !== "string") {
449
560
  return JSON.stringify(value);
450
561
  }
451
562
  return value;
452
563
  },
453
- // Parse JSON string to object on get
454
564
  get: (value) => {
565
+ if (value && typeof value === "string") {
566
+ try {
567
+ if (value.startsWith("{") || value.startsWith("[")) {
568
+ return JSON.parse(value);
569
+ }
570
+ } catch {
571
+ return value;
572
+ }
573
+ }
455
574
  return value;
456
575
  }
457
576
  },
458
- other: {
577
+ entityId: {
459
578
  type: "string",
460
579
  required: false
461
580
  },
462
- startTime: {
463
- type: "number",
581
+ source: {
582
+ type: "string",
464
583
  required: true
465
584
  },
466
- endTime: {
467
- type: "number",
468
- required: true
585
+ resourceId: {
586
+ type: "string",
587
+ required: false
588
+ },
589
+ threadId: {
590
+ type: "string",
591
+ required: false
469
592
  }
470
593
  },
471
594
  indexes: {
472
595
  primary: {
473
596
  pk: { field: "pk", composite: ["entity", "id"] },
474
- sk: { field: "sk", composite: [] }
597
+ sk: { field: "sk", composite: ["entity"] }
475
598
  },
476
- byName: {
599
+ byScorer: {
477
600
  index: "gsi1",
478
- pk: { field: "gsi1pk", composite: ["entity", "name"] },
479
- sk: { field: "gsi1sk", composite: ["startTime"] }
601
+ pk: { field: "gsi1pk", composite: ["entity", "scorerId"] },
602
+ sk: { field: "gsi1sk", composite: ["createdAt"] }
480
603
  },
481
- byScope: {
604
+ byRun: {
482
605
  index: "gsi2",
483
- pk: { field: "gsi2pk", composite: ["entity", "scope"] },
484
- sk: { field: "gsi2sk", composite: ["startTime"] }
606
+ pk: { field: "gsi2pk", composite: ["entity", "runId"] },
607
+ sk: { field: "gsi2sk", composite: ["createdAt"] }
608
+ },
609
+ byTrace: {
610
+ index: "gsi3",
611
+ pk: { field: "gsi3pk", composite: ["entity", "traceId"] },
612
+ sk: { field: "gsi3sk", composite: ["createdAt"] }
613
+ },
614
+ byEntityData: {
615
+ index: "gsi4",
616
+ pk: { field: "gsi4pk", composite: ["entity", "entityId"] },
617
+ sk: { field: "gsi4sk", composite: ["createdAt"] }
618
+ },
619
+ byResource: {
620
+ index: "gsi5",
621
+ pk: { field: "gsi5pk", composite: ["entity", "resourceId"] },
622
+ sk: { field: "gsi5sk", composite: ["createdAt"] }
623
+ },
624
+ byThread: {
625
+ index: "gsi6",
626
+ pk: { field: "gsi6pk", composite: ["entity", "threadId"] },
627
+ sk: { field: "gsi6sk", composite: ["createdAt"] }
485
628
  }
486
629
  }
487
630
  });
488
- var workflowSnapshotEntity = new Entity({
631
+ var threadEntity = new Entity({
489
632
  model: {
490
- entity: "workflow_snapshot",
633
+ entity: "thread",
491
634
  version: "1",
492
635
  service: "mastra"
493
636
  },
@@ -497,19 +640,22 @@ var workflowSnapshotEntity = new Entity({
497
640
  required: true
498
641
  },
499
642
  ...baseAttributes,
500
- workflow_name: {
643
+ id: {
501
644
  type: "string",
502
645
  required: true
503
646
  },
504
- run_id: {
647
+ resourceId: {
505
648
  type: "string",
506
649
  required: true
507
650
  },
508
- snapshot: {
651
+ title: {
509
652
  type: "string",
510
- // JSON stringified
511
- required: true,
512
- // Stringify snapshot object on set
653
+ required: true
654
+ },
655
+ metadata: {
656
+ type: "string",
657
+ required: false,
658
+ // Stringify metadata object on set if it's not already a string
513
659
  set: (value) => {
514
660
  if (value && typeof value !== "string") {
515
661
  return JSON.stringify(value);
@@ -518,114 +664,1146 @@ var workflowSnapshotEntity = new Entity({
518
664
  },
519
665
  // Parse JSON string to object on get
520
666
  get: (value) => {
521
- return value ? JSON.parse(value) : value;
667
+ if (value && typeof value === "string") {
668
+ try {
669
+ if (value.startsWith("{") || value.startsWith("[")) {
670
+ return JSON.parse(value);
671
+ }
672
+ } catch {
673
+ return value;
674
+ }
675
+ }
676
+ return value;
522
677
  }
523
- },
524
- resourceId: {
525
- type: "string",
526
- required: false
527
678
  }
528
679
  },
529
680
  indexes: {
530
681
  primary: {
531
- pk: { field: "pk", composite: ["entity", "workflow_name"] },
532
- sk: { field: "sk", composite: ["run_id"] }
682
+ pk: { field: "pk", composite: ["entity", "id"] },
683
+ sk: { field: "sk", composite: ["id"] }
533
684
  },
534
- // GSI to allow querying by run_id efficiently without knowing the workflow_name
685
+ byResource: {
686
+ index: "gsi1",
687
+ pk: { field: "gsi1pk", composite: ["entity", "resourceId"] },
688
+ sk: { field: "gsi1sk", composite: ["createdAt"] }
689
+ }
690
+ }
691
+ });
692
+ var traceEntity = new Entity({
693
+ model: {
694
+ entity: "trace",
695
+ version: "1",
696
+ service: "mastra"
697
+ },
698
+ attributes: {
699
+ entity: {
700
+ type: "string",
701
+ required: true
702
+ },
703
+ ...baseAttributes,
704
+ id: {
705
+ type: "string",
706
+ required: true
707
+ },
708
+ parentSpanId: {
709
+ type: "string",
710
+ required: false
711
+ },
712
+ name: {
713
+ type: "string",
714
+ required: true
715
+ },
716
+ traceId: {
717
+ type: "string",
718
+ required: true
719
+ },
720
+ scope: {
721
+ type: "string",
722
+ required: true
723
+ },
724
+ kind: {
725
+ type: "number",
726
+ required: true
727
+ },
728
+ attributes: {
729
+ type: "string",
730
+ // JSON stringified
731
+ required: false,
732
+ // Stringify object on set
733
+ set: (value) => {
734
+ if (value && typeof value !== "string") {
735
+ return JSON.stringify(value);
736
+ }
737
+ return value;
738
+ },
739
+ // Parse JSON string to object on get
740
+ get: (value) => {
741
+ return value ? JSON.parse(value) : value;
742
+ }
743
+ },
744
+ status: {
745
+ type: "string",
746
+ // JSON stringified
747
+ required: false,
748
+ // Stringify object on set
749
+ set: (value) => {
750
+ if (value && typeof value !== "string") {
751
+ return JSON.stringify(value);
752
+ }
753
+ return value;
754
+ },
755
+ // Parse JSON string to object on get
756
+ get: (value) => {
757
+ return value;
758
+ }
759
+ },
760
+ events: {
761
+ type: "string",
762
+ // JSON stringified
763
+ required: false,
764
+ // Stringify object on set
765
+ set: (value) => {
766
+ if (value && typeof value !== "string") {
767
+ return JSON.stringify(value);
768
+ }
769
+ return value;
770
+ },
771
+ // Parse JSON string to object on get
772
+ get: (value) => {
773
+ return value;
774
+ }
775
+ },
776
+ links: {
777
+ type: "string",
778
+ // JSON stringified
779
+ required: false,
780
+ // Stringify object on set
781
+ set: (value) => {
782
+ if (value && typeof value !== "string") {
783
+ return JSON.stringify(value);
784
+ }
785
+ return value;
786
+ },
787
+ // Parse JSON string to object on get
788
+ get: (value) => {
789
+ return value;
790
+ }
791
+ },
792
+ other: {
793
+ type: "string",
794
+ required: false
795
+ },
796
+ startTime: {
797
+ type: "number",
798
+ required: true
799
+ },
800
+ endTime: {
801
+ type: "number",
802
+ required: true
803
+ }
804
+ },
805
+ indexes: {
806
+ primary: {
807
+ pk: { field: "pk", composite: ["entity", "id"] },
808
+ sk: { field: "sk", composite: [] }
809
+ },
810
+ byName: {
811
+ index: "gsi1",
812
+ pk: { field: "gsi1pk", composite: ["entity", "name"] },
813
+ sk: { field: "gsi1sk", composite: ["startTime"] }
814
+ },
815
+ byScope: {
816
+ index: "gsi2",
817
+ pk: { field: "gsi2pk", composite: ["entity", "scope"] },
818
+ sk: { field: "gsi2sk", composite: ["startTime"] }
819
+ }
820
+ }
821
+ });
822
+ var workflowSnapshotEntity = new Entity({
823
+ model: {
824
+ entity: "workflow_snapshot",
825
+ version: "1",
826
+ service: "mastra"
827
+ },
828
+ attributes: {
829
+ entity: {
830
+ type: "string",
831
+ required: true
832
+ },
833
+ ...baseAttributes,
834
+ workflow_name: {
835
+ type: "string",
836
+ required: true
837
+ },
838
+ run_id: {
839
+ type: "string",
840
+ required: true
841
+ },
842
+ snapshot: {
843
+ type: "string",
844
+ // JSON stringified
845
+ required: true,
846
+ // Stringify snapshot object on set
847
+ set: (value) => {
848
+ if (value && typeof value !== "string") {
849
+ return JSON.stringify(value);
850
+ }
851
+ return value;
852
+ },
853
+ // Parse JSON string to object on get
854
+ get: (value) => {
855
+ return value ? JSON.parse(value) : value;
856
+ }
857
+ },
858
+ resourceId: {
859
+ type: "string",
860
+ required: false
861
+ }
862
+ },
863
+ indexes: {
864
+ primary: {
865
+ pk: { field: "pk", composite: ["entity", "workflow_name"] },
866
+ sk: { field: "sk", composite: ["run_id"] }
867
+ },
868
+ // GSI to allow querying by run_id efficiently without knowing the workflow_name
535
869
  gsi2: {
536
870
  index: "gsi2",
537
871
  pk: { field: "gsi2pk", composite: ["entity", "run_id"] },
538
872
  sk: { field: "gsi2sk", composite: ["workflow_name"] }
539
873
  }
540
874
  }
541
- });
542
-
543
- // src/entities/index.ts
544
- function getElectroDbService(client, tableName) {
545
- return new Service(
546
- {
547
- thread: threadEntity,
548
- message: messageEntity,
549
- eval: evalEntity,
550
- trace: traceEntity,
551
- workflowSnapshot: workflowSnapshotEntity
552
- },
553
- {
554
- client,
555
- table: tableName
556
- }
557
- );
558
- }
559
-
560
- // src/storage/index.ts
561
- var DynamoDBStore = class extends MastraStorage {
562
- tableName;
563
- client;
564
- service;
565
- hasInitialized = null;
566
- constructor({ name, config }) {
567
- super({ name });
875
+ });
876
+
877
+ // src/entities/index.ts
878
+ function getElectroDbService(client, tableName) {
879
+ return new Service(
880
+ {
881
+ thread: threadEntity,
882
+ message: messageEntity,
883
+ eval: evalEntity,
884
+ trace: traceEntity,
885
+ workflow_snapshot: workflowSnapshotEntity,
886
+ resource: resourceEntity,
887
+ score: scoreEntity
888
+ },
889
+ {
890
+ client,
891
+ table: tableName
892
+ }
893
+ );
894
+ }
895
+ var LegacyEvalsDynamoDB = class extends LegacyEvalsStorage {
896
+ service;
897
+ tableName;
898
+ constructor({ service, tableName }) {
899
+ super();
900
+ this.service = service;
901
+ this.tableName = tableName;
902
+ }
903
+ // Eval operations
904
+ async getEvalsByAgentName(agentName, type) {
905
+ this.logger.debug("Getting evals for agent", { agentName, type });
906
+ try {
907
+ const query = this.service.entities.eval.query.byAgent({ entity: "eval", agent_name: agentName });
908
+ const results = await query.go({ order: "desc", limit: 100 });
909
+ if (!results.data.length) {
910
+ return [];
911
+ }
912
+ let filteredData = results.data;
913
+ if (type) {
914
+ filteredData = filteredData.filter((evalRecord) => {
915
+ try {
916
+ const testInfo = evalRecord.test_info && typeof evalRecord.test_info === "string" ? JSON.parse(evalRecord.test_info) : void 0;
917
+ if (type === "test" && !testInfo) {
918
+ return false;
919
+ }
920
+ if (type === "live" && testInfo) {
921
+ return false;
922
+ }
923
+ } catch (e) {
924
+ this.logger.warn("Failed to parse test_info during filtering", { record: evalRecord, error: e });
925
+ }
926
+ return true;
927
+ });
928
+ }
929
+ return filteredData.map((evalRecord) => {
930
+ try {
931
+ return {
932
+ input: evalRecord.input,
933
+ output: evalRecord.output,
934
+ // Safely parse result and test_info
935
+ result: evalRecord.result && typeof evalRecord.result === "string" ? JSON.parse(evalRecord.result) : void 0,
936
+ agentName: evalRecord.agent_name,
937
+ createdAt: evalRecord.created_at,
938
+ // Keep as string from DDB?
939
+ metricName: evalRecord.metric_name,
940
+ instructions: evalRecord.instructions,
941
+ runId: evalRecord.run_id,
942
+ globalRunId: evalRecord.global_run_id,
943
+ testInfo: evalRecord.test_info && typeof evalRecord.test_info === "string" ? JSON.parse(evalRecord.test_info) : void 0
944
+ };
945
+ } catch (parseError) {
946
+ this.logger.error("Failed to parse eval record", { record: evalRecord, error: parseError });
947
+ return {
948
+ agentName: evalRecord.agent_name,
949
+ createdAt: evalRecord.created_at,
950
+ runId: evalRecord.run_id,
951
+ globalRunId: evalRecord.global_run_id
952
+ };
953
+ }
954
+ });
955
+ } catch (error) {
956
+ throw new MastraError(
957
+ {
958
+ id: "STORAGE_DYNAMODB_STORE_GET_EVALS_BY_AGENT_NAME_FAILED",
959
+ domain: ErrorDomain.STORAGE,
960
+ category: ErrorCategory.THIRD_PARTY,
961
+ details: { agentName }
962
+ },
963
+ error
964
+ );
965
+ }
966
+ }
967
+ async getEvals(options = {}) {
968
+ const { agentName, type, page = 0, perPage = 100, dateRange } = options;
969
+ this.logger.debug("Getting evals with pagination", { agentName, type, page, perPage, dateRange });
970
+ try {
971
+ let query;
972
+ if (agentName) {
973
+ query = this.service.entities.eval.query.byAgent({ entity: "eval", agent_name: agentName });
974
+ } else {
975
+ query = this.service.entities.eval.query.byEntity({ entity: "eval" });
976
+ }
977
+ const results = await query.go({
978
+ order: "desc",
979
+ pages: "all"
980
+ // Get all pages to apply filtering and pagination
981
+ });
982
+ if (!results.data.length) {
983
+ return {
984
+ evals: [],
985
+ total: 0,
986
+ page,
987
+ perPage,
988
+ hasMore: false
989
+ };
990
+ }
991
+ let filteredData = results.data;
992
+ if (type) {
993
+ filteredData = filteredData.filter((evalRecord) => {
994
+ try {
995
+ const testInfo = evalRecord.test_info && typeof evalRecord.test_info === "string" ? JSON.parse(evalRecord.test_info) : void 0;
996
+ if (type === "test" && !testInfo) {
997
+ return false;
998
+ }
999
+ if (type === "live" && testInfo) {
1000
+ return false;
1001
+ }
1002
+ } catch (e) {
1003
+ this.logger.warn("Failed to parse test_info during filtering", { record: evalRecord, error: e });
1004
+ }
1005
+ return true;
1006
+ });
1007
+ }
1008
+ if (dateRange) {
1009
+ const fromDate = dateRange.start;
1010
+ const toDate = dateRange.end;
1011
+ filteredData = filteredData.filter((evalRecord) => {
1012
+ const recordDate = new Date(evalRecord.created_at);
1013
+ if (fromDate && recordDate < fromDate) {
1014
+ return false;
1015
+ }
1016
+ if (toDate && recordDate > toDate) {
1017
+ return false;
1018
+ }
1019
+ return true;
1020
+ });
1021
+ }
1022
+ const total = filteredData.length;
1023
+ const start = page * perPage;
1024
+ const end = start + perPage;
1025
+ const paginatedData = filteredData.slice(start, end);
1026
+ const evals = paginatedData.map((evalRecord) => {
1027
+ try {
1028
+ return {
1029
+ input: evalRecord.input,
1030
+ output: evalRecord.output,
1031
+ result: evalRecord.result && typeof evalRecord.result === "string" ? JSON.parse(evalRecord.result) : void 0,
1032
+ agentName: evalRecord.agent_name,
1033
+ createdAt: evalRecord.created_at,
1034
+ metricName: evalRecord.metric_name,
1035
+ instructions: evalRecord.instructions,
1036
+ runId: evalRecord.run_id,
1037
+ globalRunId: evalRecord.global_run_id,
1038
+ testInfo: evalRecord.test_info && typeof evalRecord.test_info === "string" ? JSON.parse(evalRecord.test_info) : void 0
1039
+ };
1040
+ } catch (parseError) {
1041
+ this.logger.error("Failed to parse eval record", { record: evalRecord, error: parseError });
1042
+ return {
1043
+ agentName: evalRecord.agent_name,
1044
+ createdAt: evalRecord.created_at,
1045
+ runId: evalRecord.run_id,
1046
+ globalRunId: evalRecord.global_run_id
1047
+ };
1048
+ }
1049
+ });
1050
+ const hasMore = end < total;
1051
+ return {
1052
+ evals,
1053
+ total,
1054
+ page,
1055
+ perPage,
1056
+ hasMore
1057
+ };
1058
+ } catch (error) {
1059
+ throw new MastraError(
1060
+ {
1061
+ id: "STORAGE_DYNAMODB_STORE_GET_EVALS_FAILED",
1062
+ domain: ErrorDomain.STORAGE,
1063
+ category: ErrorCategory.THIRD_PARTY,
1064
+ details: {
1065
+ agentName: agentName || "all",
1066
+ type: type || "all",
1067
+ page,
1068
+ perPage
1069
+ }
1070
+ },
1071
+ error
1072
+ );
1073
+ }
1074
+ }
1075
+ };
1076
+ var MemoryStorageDynamoDB = class extends MemoryStorage {
1077
+ service;
1078
+ constructor({ service }) {
1079
+ super();
1080
+ this.service = service;
1081
+ }
1082
+ // Helper function to parse message data (handle JSON fields)
1083
+ parseMessageData(data) {
1084
+ return {
1085
+ ...data,
1086
+ // Ensure dates are Date objects if needed (ElectroDB might return strings)
1087
+ createdAt: data.createdAt ? new Date(data.createdAt) : void 0,
1088
+ updatedAt: data.updatedAt ? new Date(data.updatedAt) : void 0
1089
+ // Other fields like content, toolCallArgs etc. are assumed to be correctly
1090
+ // transformed by the ElectroDB entity getters.
1091
+ };
1092
+ }
1093
+ async getThreadById({ threadId }) {
1094
+ this.logger.debug("Getting thread by ID", { threadId });
1095
+ try {
1096
+ const result = await this.service.entities.thread.get({ entity: "thread", id: threadId }).go();
1097
+ if (!result.data) {
1098
+ return null;
1099
+ }
1100
+ const data = result.data;
1101
+ return {
1102
+ ...data,
1103
+ // Convert date strings back to Date objects for consistency
1104
+ createdAt: typeof data.createdAt === "string" ? new Date(data.createdAt) : data.createdAt,
1105
+ updatedAt: typeof data.updatedAt === "string" ? new Date(data.updatedAt) : data.updatedAt
1106
+ // metadata: data.metadata ? JSON.parse(data.metadata) : undefined, // REMOVED by AI
1107
+ // metadata is already transformed by the entity's getter
1108
+ };
1109
+ } catch (error) {
1110
+ throw new MastraError(
1111
+ {
1112
+ id: "STORAGE_DYNAMODB_STORE_GET_THREAD_BY_ID_FAILED",
1113
+ domain: ErrorDomain.STORAGE,
1114
+ category: ErrorCategory.THIRD_PARTY,
1115
+ details: { threadId }
1116
+ },
1117
+ error
1118
+ );
1119
+ }
1120
+ }
1121
+ async getThreadsByResourceId({ resourceId }) {
1122
+ this.logger.debug("Getting threads by resource ID", { resourceId });
1123
+ try {
1124
+ const result = await this.service.entities.thread.query.byResource({ entity: "thread", resourceId }).go();
1125
+ if (!result.data.length) {
1126
+ return [];
1127
+ }
1128
+ return result.data.map((data) => ({
1129
+ ...data,
1130
+ // Convert date strings back to Date objects for consistency
1131
+ createdAt: typeof data.createdAt === "string" ? new Date(data.createdAt) : data.createdAt,
1132
+ updatedAt: typeof data.updatedAt === "string" ? new Date(data.updatedAt) : data.updatedAt
1133
+ // metadata: data.metadata ? JSON.parse(data.metadata) : undefined, // REMOVED by AI
1134
+ // metadata is already transformed by the entity's getter
1135
+ }));
1136
+ } catch (error) {
1137
+ throw new MastraError(
1138
+ {
1139
+ id: "STORAGE_DYNAMODB_STORE_GET_THREADS_BY_RESOURCE_ID_FAILED",
1140
+ domain: ErrorDomain.STORAGE,
1141
+ category: ErrorCategory.THIRD_PARTY,
1142
+ details: { resourceId }
1143
+ },
1144
+ error
1145
+ );
1146
+ }
1147
+ }
1148
+ async saveThread({ thread }) {
1149
+ this.logger.debug("Saving thread", { threadId: thread.id });
1150
+ const now = /* @__PURE__ */ new Date();
1151
+ const threadData = {
1152
+ entity: "thread",
1153
+ id: thread.id,
1154
+ resourceId: thread.resourceId,
1155
+ title: thread.title || `Thread ${thread.id}`,
1156
+ createdAt: thread.createdAt?.toISOString() || now.toISOString(),
1157
+ updatedAt: now.toISOString(),
1158
+ metadata: thread.metadata ? JSON.stringify(thread.metadata) : void 0
1159
+ };
1160
+ try {
1161
+ await this.service.entities.thread.upsert(threadData).go();
1162
+ return {
1163
+ id: thread.id,
1164
+ resourceId: thread.resourceId,
1165
+ title: threadData.title,
1166
+ createdAt: thread.createdAt || now,
1167
+ updatedAt: now,
1168
+ metadata: thread.metadata
1169
+ };
1170
+ } catch (error) {
1171
+ throw new MastraError(
1172
+ {
1173
+ id: "STORAGE_DYNAMODB_STORE_SAVE_THREAD_FAILED",
1174
+ domain: ErrorDomain.STORAGE,
1175
+ category: ErrorCategory.THIRD_PARTY,
1176
+ details: { threadId: thread.id }
1177
+ },
1178
+ error
1179
+ );
1180
+ }
1181
+ }
1182
+ async updateThread({
1183
+ id,
1184
+ title,
1185
+ metadata
1186
+ }) {
1187
+ this.logger.debug("Updating thread", { threadId: id });
1188
+ try {
1189
+ const existingThread = await this.getThreadById({ threadId: id });
1190
+ if (!existingThread) {
1191
+ throw new Error(`Thread not found: ${id}`);
1192
+ }
1193
+ const now = /* @__PURE__ */ new Date();
1194
+ const updateData = {
1195
+ updatedAt: now.toISOString()
1196
+ };
1197
+ if (title) {
1198
+ updateData.title = title;
1199
+ }
1200
+ if (metadata) {
1201
+ const existingMetadata = existingThread.metadata ? typeof existingThread.metadata === "string" ? JSON.parse(existingThread.metadata) : existingThread.metadata : {};
1202
+ const mergedMetadata = { ...existingMetadata, ...metadata };
1203
+ updateData.metadata = JSON.stringify(mergedMetadata);
1204
+ }
1205
+ await this.service.entities.thread.update({ entity: "thread", id }).set(updateData).go();
1206
+ return {
1207
+ ...existingThread,
1208
+ title: title || existingThread.title,
1209
+ metadata: metadata ? { ...existingThread.metadata, ...metadata } : existingThread.metadata,
1210
+ updatedAt: now
1211
+ };
1212
+ } catch (error) {
1213
+ throw new MastraError(
1214
+ {
1215
+ id: "STORAGE_DYNAMODB_STORE_UPDATE_THREAD_FAILED",
1216
+ domain: ErrorDomain.STORAGE,
1217
+ category: ErrorCategory.THIRD_PARTY,
1218
+ details: { threadId: id }
1219
+ },
1220
+ error
1221
+ );
1222
+ }
1223
+ }
1224
+ async deleteThread({ threadId }) {
1225
+ this.logger.debug("Deleting thread", { threadId });
1226
+ try {
1227
+ const messages = await this.getMessages({ threadId });
1228
+ if (messages.length > 0) {
1229
+ const batchSize = 25;
1230
+ for (let i = 0; i < messages.length; i += batchSize) {
1231
+ const batch = messages.slice(i, i + batchSize);
1232
+ await Promise.all(
1233
+ batch.map(
1234
+ (message) => this.service.entities.message.delete({
1235
+ entity: "message",
1236
+ id: message.id,
1237
+ threadId: message.threadId
1238
+ }).go()
1239
+ )
1240
+ );
1241
+ }
1242
+ }
1243
+ await this.service.entities.thread.delete({ entity: "thread", id: threadId }).go();
1244
+ } catch (error) {
1245
+ throw new MastraError(
1246
+ {
1247
+ id: "STORAGE_DYNAMODB_STORE_DELETE_THREAD_FAILED",
1248
+ domain: ErrorDomain.STORAGE,
1249
+ category: ErrorCategory.THIRD_PARTY,
1250
+ details: { threadId }
1251
+ },
1252
+ error
1253
+ );
1254
+ }
1255
+ }
1256
+ async getMessages({
1257
+ threadId,
1258
+ resourceId,
1259
+ selectBy,
1260
+ format
1261
+ }) {
1262
+ this.logger.debug("Getting messages", { threadId, selectBy });
1263
+ try {
1264
+ const messages = [];
1265
+ const limit = resolveMessageLimit({ last: selectBy?.last, defaultLimit: Number.MAX_SAFE_INTEGER });
1266
+ if (selectBy?.include?.length) {
1267
+ const includeMessages = await this._getIncludedMessages(threadId, selectBy);
1268
+ if (includeMessages) {
1269
+ messages.push(...includeMessages);
1270
+ }
1271
+ }
1272
+ if (limit !== 0) {
1273
+ const query = this.service.entities.message.query.byThread({ entity: "message", threadId });
1274
+ let results;
1275
+ if (limit !== Number.MAX_SAFE_INTEGER && limit > 0) {
1276
+ results = await query.go({ limit, order: "desc" });
1277
+ results.data = results.data.reverse();
1278
+ } else {
1279
+ results = await query.go();
1280
+ }
1281
+ let allThreadMessages = results.data.map((data) => this.parseMessageData(data)).filter((msg) => "content" in msg);
1282
+ allThreadMessages.sort((a, b) => {
1283
+ const timeA = a.createdAt.getTime();
1284
+ const timeB = b.createdAt.getTime();
1285
+ if (timeA === timeB) {
1286
+ return a.id.localeCompare(b.id);
1287
+ }
1288
+ return timeA - timeB;
1289
+ });
1290
+ messages.push(...allThreadMessages);
1291
+ }
1292
+ messages.sort((a, b) => {
1293
+ const timeA = a.createdAt.getTime();
1294
+ const timeB = b.createdAt.getTime();
1295
+ if (timeA === timeB) {
1296
+ return a.id.localeCompare(b.id);
1297
+ }
1298
+ return timeA - timeB;
1299
+ });
1300
+ const uniqueMessages = messages.filter(
1301
+ (message, index, self) => index === self.findIndex((m) => m.id === message.id)
1302
+ );
1303
+ const list = new MessageList({ threadId, resourceId }).add(uniqueMessages, "memory");
1304
+ if (format === `v2`) return list.get.all.v2();
1305
+ return list.get.all.v1();
1306
+ } catch (error) {
1307
+ throw new MastraError(
1308
+ {
1309
+ id: "STORAGE_DYNAMODB_STORE_GET_MESSAGES_FAILED",
1310
+ domain: ErrorDomain.STORAGE,
1311
+ category: ErrorCategory.THIRD_PARTY,
1312
+ details: { threadId }
1313
+ },
1314
+ error
1315
+ );
1316
+ }
1317
+ }
1318
+ async saveMessages(args) {
1319
+ const { messages, format = "v1" } = args;
1320
+ this.logger.debug("Saving messages", { count: messages.length });
1321
+ if (!messages.length) {
1322
+ return [];
1323
+ }
1324
+ const threadId = messages[0]?.threadId;
1325
+ if (!threadId) {
1326
+ throw new Error("Thread ID is required");
1327
+ }
1328
+ const messagesToSave = messages.map((msg) => {
1329
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1330
+ return {
1331
+ entity: "message",
1332
+ // Add entity type
1333
+ id: msg.id,
1334
+ threadId: msg.threadId,
1335
+ role: msg.role,
1336
+ type: msg.type,
1337
+ resourceId: msg.resourceId,
1338
+ // Ensure complex fields are stringified if not handled by attribute setters
1339
+ content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content),
1340
+ toolCallArgs: `toolCallArgs` in msg && msg.toolCallArgs ? JSON.stringify(msg.toolCallArgs) : void 0,
1341
+ toolCallIds: `toolCallIds` in msg && msg.toolCallIds ? JSON.stringify(msg.toolCallIds) : void 0,
1342
+ toolNames: `toolNames` in msg && msg.toolNames ? JSON.stringify(msg.toolNames) : void 0,
1343
+ createdAt: msg.createdAt instanceof Date ? msg.createdAt.toISOString() : msg.createdAt || now,
1344
+ updatedAt: now
1345
+ // Add updatedAt
1346
+ };
1347
+ });
1348
+ try {
1349
+ const savedMessageIds = [];
1350
+ for (const messageData of messagesToSave) {
1351
+ if (!messageData.entity) {
1352
+ this.logger.error("Missing entity property in message data for create", { messageData });
1353
+ throw new Error("Internal error: Missing entity property during saveMessages");
1354
+ }
1355
+ try {
1356
+ await this.service.entities.message.put(messageData).go();
1357
+ savedMessageIds.push(messageData.id);
1358
+ } catch (error) {
1359
+ for (const savedId of savedMessageIds) {
1360
+ try {
1361
+ await this.service.entities.message.delete({ entity: "message", id: savedId }).go();
1362
+ } catch (rollbackError) {
1363
+ this.logger.error("Failed to rollback message during save error", {
1364
+ messageId: savedId,
1365
+ error: rollbackError
1366
+ });
1367
+ }
1368
+ }
1369
+ throw error;
1370
+ }
1371
+ }
1372
+ await this.service.entities.thread.update({ entity: "thread", id: threadId }).set({
1373
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1374
+ }).go();
1375
+ const list = new MessageList().add(messages, "memory");
1376
+ if (format === `v1`) return list.get.all.v1();
1377
+ return list.get.all.v2();
1378
+ } catch (error) {
1379
+ throw new MastraError(
1380
+ {
1381
+ id: "STORAGE_DYNAMODB_STORE_SAVE_MESSAGES_FAILED",
1382
+ domain: ErrorDomain.STORAGE,
1383
+ category: ErrorCategory.THIRD_PARTY,
1384
+ details: { count: messages.length }
1385
+ },
1386
+ error
1387
+ );
1388
+ }
1389
+ }
1390
+ async getThreadsByResourceIdPaginated(args) {
1391
+ const { resourceId, page = 0, perPage = 100 } = args;
1392
+ this.logger.debug("Getting threads by resource ID with pagination", { resourceId, page, perPage });
1393
+ try {
1394
+ const query = this.service.entities.thread.query.byResource({ entity: "thread", resourceId });
1395
+ const results = await query.go();
1396
+ const allThreads = results.data;
1397
+ const startIndex = page * perPage;
1398
+ const endIndex = startIndex + perPage;
1399
+ const paginatedThreads = allThreads.slice(startIndex, endIndex);
1400
+ const total = allThreads.length;
1401
+ const hasMore = endIndex < total;
1402
+ return {
1403
+ threads: paginatedThreads,
1404
+ total,
1405
+ page,
1406
+ perPage,
1407
+ hasMore
1408
+ };
1409
+ } catch (error) {
1410
+ throw new MastraError(
1411
+ {
1412
+ id: "STORAGE_DYNAMODB_STORE_GET_THREADS_BY_RESOURCE_ID_PAGINATED_FAILED",
1413
+ domain: ErrorDomain.STORAGE,
1414
+ category: ErrorCategory.THIRD_PARTY,
1415
+ details: { resourceId, page, perPage }
1416
+ },
1417
+ error
1418
+ );
1419
+ }
1420
+ }
1421
+ async getMessagesPaginated(args) {
1422
+ const { threadId, resourceId, selectBy, format = "v1" } = args;
1423
+ const { page = 0, perPage = 40, dateRange } = selectBy?.pagination || {};
1424
+ const fromDate = dateRange?.start;
1425
+ const toDate = dateRange?.end;
1426
+ const limit = resolveMessageLimit({ last: selectBy?.last, defaultLimit: Number.MAX_SAFE_INTEGER });
1427
+ this.logger.debug("Getting messages with pagination", { threadId, page, perPage, fromDate, toDate, limit });
1428
+ try {
1429
+ let messages = [];
1430
+ if (selectBy?.include?.length) {
1431
+ const includeMessages = await this._getIncludedMessages(threadId, selectBy);
1432
+ if (includeMessages) {
1433
+ messages.push(...includeMessages);
1434
+ }
1435
+ }
1436
+ if (limit !== 0) {
1437
+ const query = this.service.entities.message.query.byThread({ entity: "message", threadId });
1438
+ let results;
1439
+ if (limit !== Number.MAX_SAFE_INTEGER && limit > 0) {
1440
+ results = await query.go({ limit, order: "desc" });
1441
+ results.data = results.data.reverse();
1442
+ } else {
1443
+ results = await query.go();
1444
+ }
1445
+ let allThreadMessages = results.data.map((data) => this.parseMessageData(data)).filter((msg) => "content" in msg);
1446
+ allThreadMessages.sort((a, b) => {
1447
+ const timeA = a.createdAt.getTime();
1448
+ const timeB = b.createdAt.getTime();
1449
+ if (timeA === timeB) {
1450
+ return a.id.localeCompare(b.id);
1451
+ }
1452
+ return timeA - timeB;
1453
+ });
1454
+ const excludeIds = messages.map((m) => m.id);
1455
+ if (excludeIds.length > 0) {
1456
+ allThreadMessages = allThreadMessages.filter((msg) => !excludeIds.includes(msg.id));
1457
+ }
1458
+ messages.push(...allThreadMessages);
1459
+ }
1460
+ messages.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
1461
+ if (fromDate || toDate) {
1462
+ messages = messages.filter((msg) => {
1463
+ const createdAt = new Date(msg.createdAt).getTime();
1464
+ if (fromDate && createdAt < new Date(fromDate).getTime()) return false;
1465
+ if (toDate && createdAt > new Date(toDate).getTime()) return false;
1466
+ return true;
1467
+ });
1468
+ }
1469
+ const total = messages.length;
1470
+ const start = page * perPage;
1471
+ const end = start + perPage;
1472
+ const paginatedMessages = messages.slice(start, end);
1473
+ const hasMore = end < total;
1474
+ const list = new MessageList({ threadId, resourceId }).add(paginatedMessages, "memory");
1475
+ const finalMessages = format === "v2" ? list.get.all.v2() : list.get.all.v1();
1476
+ return {
1477
+ messages: finalMessages,
1478
+ total,
1479
+ page,
1480
+ perPage,
1481
+ hasMore
1482
+ };
1483
+ } catch (error) {
1484
+ throw new MastraError(
1485
+ {
1486
+ id: "STORAGE_DYNAMODB_STORE_GET_MESSAGES_PAGINATED_FAILED",
1487
+ domain: ErrorDomain.STORAGE,
1488
+ category: ErrorCategory.THIRD_PARTY,
1489
+ details: { threadId }
1490
+ },
1491
+ error
1492
+ );
1493
+ }
1494
+ }
1495
+ // Helper method to get included messages with context
1496
+ async _getIncludedMessages(threadId, selectBy) {
1497
+ if (!selectBy?.include?.length) {
1498
+ return [];
1499
+ }
1500
+ const includeMessages = [];
1501
+ for (const includeItem of selectBy.include) {
1502
+ try {
1503
+ const { id, threadId: targetThreadId, withPreviousMessages = 0, withNextMessages = 0 } = includeItem;
1504
+ const searchThreadId = targetThreadId || threadId;
1505
+ this.logger.debug("Getting included messages for", {
1506
+ id,
1507
+ targetThreadId,
1508
+ searchThreadId,
1509
+ withPreviousMessages,
1510
+ withNextMessages
1511
+ });
1512
+ const query = this.service.entities.message.query.byThread({ entity: "message", threadId: searchThreadId });
1513
+ const results = await query.go();
1514
+ const allMessages = results.data.map((data) => this.parseMessageData(data)).filter((msg) => "content" in msg && typeof msg.content === "object");
1515
+ this.logger.debug("Found messages in thread", {
1516
+ threadId: searchThreadId,
1517
+ messageCount: allMessages.length,
1518
+ messageIds: allMessages.map((m) => m.id)
1519
+ });
1520
+ allMessages.sort((a, b) => {
1521
+ const timeA = a.createdAt.getTime();
1522
+ const timeB = b.createdAt.getTime();
1523
+ if (timeA === timeB) {
1524
+ return a.id.localeCompare(b.id);
1525
+ }
1526
+ return timeA - timeB;
1527
+ });
1528
+ const targetIndex = allMessages.findIndex((msg) => msg.id === id);
1529
+ if (targetIndex === -1) {
1530
+ this.logger.warn("Target message not found", { id, threadId: searchThreadId });
1531
+ continue;
1532
+ }
1533
+ this.logger.debug("Found target message at index", { id, targetIndex, totalMessages: allMessages.length });
1534
+ const startIndex = Math.max(0, targetIndex - withPreviousMessages);
1535
+ const endIndex = Math.min(allMessages.length, targetIndex + withNextMessages + 1);
1536
+ const contextMessages = allMessages.slice(startIndex, endIndex);
1537
+ this.logger.debug("Context messages", {
1538
+ startIndex,
1539
+ endIndex,
1540
+ contextCount: contextMessages.length,
1541
+ contextIds: contextMessages.map((m) => m.id)
1542
+ });
1543
+ includeMessages.push(...contextMessages);
1544
+ } catch (error) {
1545
+ this.logger.warn("Failed to get included message", { messageId: includeItem.id, error });
1546
+ }
1547
+ }
1548
+ this.logger.debug("Total included messages", {
1549
+ count: includeMessages.length,
1550
+ ids: includeMessages.map((m) => m.id)
1551
+ });
1552
+ return includeMessages;
1553
+ }
1554
+ async updateMessages(args) {
1555
+ const { messages } = args;
1556
+ this.logger.debug("Updating messages", { count: messages.length });
1557
+ if (!messages.length) {
1558
+ return [];
1559
+ }
1560
+ const updatedMessages = [];
1561
+ const affectedThreadIds = /* @__PURE__ */ new Set();
1562
+ try {
1563
+ for (const updateData of messages) {
1564
+ const { id, ...updates } = updateData;
1565
+ const existingMessage = await this.service.entities.message.get({ entity: "message", id }).go();
1566
+ if (!existingMessage.data) {
1567
+ this.logger.warn("Message not found for update", { id });
1568
+ continue;
1569
+ }
1570
+ const existingMsg = this.parseMessageData(existingMessage.data);
1571
+ const originalThreadId = existingMsg.threadId;
1572
+ affectedThreadIds.add(originalThreadId);
1573
+ const updatePayload = {
1574
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1575
+ };
1576
+ if ("role" in updates && updates.role !== void 0) updatePayload.role = updates.role;
1577
+ if ("type" in updates && updates.type !== void 0) updatePayload.type = updates.type;
1578
+ if ("resourceId" in updates && updates.resourceId !== void 0) updatePayload.resourceId = updates.resourceId;
1579
+ if ("threadId" in updates && updates.threadId !== void 0 && updates.threadId !== null) {
1580
+ updatePayload.threadId = updates.threadId;
1581
+ affectedThreadIds.add(updates.threadId);
1582
+ }
1583
+ if (updates.content) {
1584
+ const existingContent = existingMsg.content;
1585
+ let newContent = { ...existingContent };
1586
+ if (updates.content.metadata !== void 0) {
1587
+ newContent.metadata = {
1588
+ ...existingContent.metadata || {},
1589
+ ...updates.content.metadata || {}
1590
+ };
1591
+ }
1592
+ if (updates.content.content !== void 0) {
1593
+ newContent.content = updates.content.content;
1594
+ }
1595
+ if ("parts" in updates.content && updates.content.parts !== void 0) {
1596
+ newContent.parts = updates.content.parts;
1597
+ }
1598
+ updatePayload.content = JSON.stringify(newContent);
1599
+ }
1600
+ await this.service.entities.message.update({ entity: "message", id }).set(updatePayload).go();
1601
+ const updatedMessage = await this.service.entities.message.get({ entity: "message", id }).go();
1602
+ if (updatedMessage.data) {
1603
+ updatedMessages.push(this.parseMessageData(updatedMessage.data));
1604
+ }
1605
+ }
1606
+ for (const threadId of affectedThreadIds) {
1607
+ await this.service.entities.thread.update({ entity: "thread", id: threadId }).set({
1608
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1609
+ }).go();
1610
+ }
1611
+ return updatedMessages;
1612
+ } catch (error) {
1613
+ throw new MastraError(
1614
+ {
1615
+ id: "STORAGE_DYNAMODB_STORE_UPDATE_MESSAGES_FAILED",
1616
+ domain: ErrorDomain.STORAGE,
1617
+ category: ErrorCategory.THIRD_PARTY,
1618
+ details: { count: messages.length }
1619
+ },
1620
+ error
1621
+ );
1622
+ }
1623
+ }
1624
+ async getResourceById({ resourceId }) {
1625
+ this.logger.debug("Getting resource by ID", { resourceId });
1626
+ try {
1627
+ const result = await this.service.entities.resource.get({ entity: "resource", id: resourceId }).go();
1628
+ if (!result.data) {
1629
+ return null;
1630
+ }
1631
+ const data = result.data;
1632
+ return {
1633
+ ...data,
1634
+ // Convert date strings back to Date objects for consistency
1635
+ createdAt: typeof data.createdAt === "string" ? new Date(data.createdAt) : data.createdAt,
1636
+ updatedAt: typeof data.updatedAt === "string" ? new Date(data.updatedAt) : data.updatedAt,
1637
+ // Ensure workingMemory is always returned as a string, regardless of automatic parsing
1638
+ workingMemory: typeof data.workingMemory === "object" ? JSON.stringify(data.workingMemory) : data.workingMemory
1639
+ // metadata is already transformed by the entity's getter
1640
+ };
1641
+ } catch (error) {
1642
+ throw new MastraError(
1643
+ {
1644
+ id: "STORAGE_DYNAMODB_STORE_GET_RESOURCE_BY_ID_FAILED",
1645
+ domain: ErrorDomain.STORAGE,
1646
+ category: ErrorCategory.THIRD_PARTY,
1647
+ details: { resourceId }
1648
+ },
1649
+ error
1650
+ );
1651
+ }
1652
+ }
1653
+ async saveResource({ resource }) {
1654
+ this.logger.debug("Saving resource", { resourceId: resource.id });
1655
+ const now = /* @__PURE__ */ new Date();
1656
+ const resourceData = {
1657
+ entity: "resource",
1658
+ id: resource.id,
1659
+ workingMemory: resource.workingMemory,
1660
+ metadata: resource.metadata ? JSON.stringify(resource.metadata) : void 0,
1661
+ createdAt: resource.createdAt?.toISOString() || now.toISOString(),
1662
+ updatedAt: now.toISOString()
1663
+ };
568
1664
  try {
569
- if (!config.tableName || typeof config.tableName !== "string" || config.tableName.trim() === "") {
570
- throw new Error("DynamoDBStore: config.tableName must be provided and cannot be empty.");
571
- }
572
- if (!/^[a-zA-Z0-9_.-]{3,255}$/.test(config.tableName)) {
573
- throw new Error(
574
- `DynamoDBStore: config.tableName "${config.tableName}" contains invalid characters or is not between 3 and 255 characters long.`
575
- );
576
- }
577
- const dynamoClient = new DynamoDBClient({
578
- region: config.region || "us-east-1",
579
- endpoint: config.endpoint,
580
- credentials: config.credentials
581
- });
582
- this.tableName = config.tableName;
583
- this.client = DynamoDBDocumentClient.from(dynamoClient);
584
- this.service = getElectroDbService(this.client, this.tableName);
1665
+ await this.service.entities.resource.upsert(resourceData).go();
1666
+ return {
1667
+ id: resource.id,
1668
+ workingMemory: resource.workingMemory,
1669
+ metadata: resource.metadata,
1670
+ createdAt: resource.createdAt || now,
1671
+ updatedAt: now
1672
+ };
585
1673
  } catch (error) {
586
1674
  throw new MastraError(
587
1675
  {
588
- id: "STORAGE_DYNAMODB_STORE_CONSTRUCTOR_FAILED",
1676
+ id: "STORAGE_DYNAMODB_STORE_SAVE_RESOURCE_FAILED",
589
1677
  domain: ErrorDomain.STORAGE,
590
- category: ErrorCategory.USER
1678
+ category: ErrorCategory.THIRD_PARTY,
1679
+ details: { resourceId: resource.id }
591
1680
  },
592
1681
  error
593
1682
  );
594
1683
  }
595
1684
  }
596
- /**
597
- * This method is modified for DynamoDB with ElectroDB single-table design.
598
- * It assumes the table is created and managed externally via CDK/CloudFormation.
599
- *
600
- * This implementation only validates that the required table exists and is accessible.
601
- * No table creation is attempted - we simply check if we can access the table.
602
- */
603
- async createTable({ tableName }) {
604
- this.logger.debug("Validating access to externally managed table", { tableName, physicalTable: this.tableName });
1685
+ async updateResource({
1686
+ resourceId,
1687
+ workingMemory,
1688
+ metadata
1689
+ }) {
1690
+ this.logger.debug("Updating resource", { resourceId });
605
1691
  try {
606
- const tableExists = await this.validateTableExists();
607
- if (!tableExists) {
608
- this.logger.error(
609
- `Table ${this.tableName} does not exist or is not accessible. It should be created via CDK/CloudFormation.`
610
- );
611
- throw new Error(
612
- `Table ${this.tableName} does not exist or is not accessible. Ensure it's created via CDK/CloudFormation before using this store.`
613
- );
1692
+ const existingResource = await this.getResourceById({ resourceId });
1693
+ if (!existingResource) {
1694
+ const newResource = {
1695
+ id: resourceId,
1696
+ workingMemory,
1697
+ metadata: metadata || {},
1698
+ createdAt: /* @__PURE__ */ new Date(),
1699
+ updatedAt: /* @__PURE__ */ new Date()
1700
+ };
1701
+ return this.saveResource({ resource: newResource });
614
1702
  }
615
- this.logger.debug(`Table ${this.tableName} exists and is accessible`);
1703
+ const now = /* @__PURE__ */ new Date();
1704
+ const updateData = {
1705
+ updatedAt: now.toISOString()
1706
+ };
1707
+ if (workingMemory !== void 0) {
1708
+ updateData.workingMemory = workingMemory;
1709
+ }
1710
+ if (metadata) {
1711
+ const existingMetadata = existingResource.metadata || {};
1712
+ const mergedMetadata = { ...existingMetadata, ...metadata };
1713
+ updateData.metadata = JSON.stringify(mergedMetadata);
1714
+ }
1715
+ await this.service.entities.resource.update({ entity: "resource", id: resourceId }).set(updateData).go();
1716
+ return {
1717
+ ...existingResource,
1718
+ workingMemory: workingMemory !== void 0 ? workingMemory : existingResource.workingMemory,
1719
+ metadata: metadata ? { ...existingResource.metadata, ...metadata } : existingResource.metadata,
1720
+ updatedAt: now
1721
+ };
616
1722
  } catch (error) {
617
- this.logger.error("Error validating table access", { tableName: this.tableName, error });
618
1723
  throw new MastraError(
619
1724
  {
620
- id: "STORAGE_DYNAMODB_STORE_VALIDATE_TABLE_ACCESS_FAILED",
1725
+ id: "STORAGE_DYNAMODB_STORE_UPDATE_RESOURCE_FAILED",
621
1726
  domain: ErrorDomain.STORAGE,
622
1727
  category: ErrorCategory.THIRD_PARTY,
623
- details: { tableName: this.tableName }
1728
+ details: { resourceId }
624
1729
  },
625
1730
  error
626
1731
  );
627
1732
  }
628
1733
  }
1734
+ };
1735
+ var StoreOperationsDynamoDB = class extends StoreOperations {
1736
+ client;
1737
+ tableName;
1738
+ service;
1739
+ constructor({
1740
+ service,
1741
+ tableName,
1742
+ client
1743
+ }) {
1744
+ super();
1745
+ this.service = service;
1746
+ this.client = client;
1747
+ this.tableName = tableName;
1748
+ }
1749
+ async hasColumn() {
1750
+ return true;
1751
+ }
1752
+ async dropTable() {
1753
+ }
1754
+ // Helper methods for entity/table mapping
1755
+ getEntityNameForTable(tableName) {
1756
+ const mapping = {
1757
+ [TABLE_THREADS]: "thread",
1758
+ [TABLE_MESSAGES]: "message",
1759
+ [TABLE_WORKFLOW_SNAPSHOT]: "workflow_snapshot",
1760
+ [TABLE_EVALS]: "eval",
1761
+ [TABLE_SCORERS]: "score",
1762
+ [TABLE_TRACES]: "trace",
1763
+ [TABLE_RESOURCES]: "resource"
1764
+ };
1765
+ return mapping[tableName] || null;
1766
+ }
1767
+ /**
1768
+ * Pre-processes a record to ensure Date objects are converted to ISO strings
1769
+ * This is necessary because ElectroDB validation happens before setters are applied
1770
+ */
1771
+ preprocessRecord(record) {
1772
+ const processed = { ...record };
1773
+ if (processed.createdAt instanceof Date) {
1774
+ processed.createdAt = processed.createdAt.toISOString();
1775
+ }
1776
+ if (processed.updatedAt instanceof Date) {
1777
+ processed.updatedAt = processed.updatedAt.toISOString();
1778
+ }
1779
+ if (processed.created_at instanceof Date) {
1780
+ processed.created_at = processed.created_at.toISOString();
1781
+ }
1782
+ if (processed.result && typeof processed.result === "object") {
1783
+ processed.result = JSON.stringify(processed.result);
1784
+ }
1785
+ if (processed.test_info && typeof processed.test_info === "object") {
1786
+ processed.test_info = JSON.stringify(processed.test_info);
1787
+ } else if (processed.test_info === void 0 || processed.test_info === null) {
1788
+ delete processed.test_info;
1789
+ }
1790
+ if (processed.snapshot && typeof processed.snapshot === "object") {
1791
+ processed.snapshot = JSON.stringify(processed.snapshot);
1792
+ }
1793
+ if (processed.attributes && typeof processed.attributes === "object") {
1794
+ processed.attributes = JSON.stringify(processed.attributes);
1795
+ }
1796
+ if (processed.status && typeof processed.status === "object") {
1797
+ processed.status = JSON.stringify(processed.status);
1798
+ }
1799
+ if (processed.events && typeof processed.events === "object") {
1800
+ processed.events = JSON.stringify(processed.events);
1801
+ }
1802
+ if (processed.links && typeof processed.links === "object") {
1803
+ processed.links = JSON.stringify(processed.links);
1804
+ }
1805
+ return processed;
1806
+ }
629
1807
  /**
630
1808
  * Validates that the required DynamoDB table exists and is accessible.
631
1809
  * This does not check the table structure - it assumes the table
@@ -654,20 +1832,30 @@ var DynamoDBStore = class extends MastraStorage {
654
1832
  }
655
1833
  }
656
1834
  /**
657
- * Initialize storage, validating the externally managed table is accessible.
658
- * For the single-table design, we only validate once that we can access
659
- * the table that was created via CDK/CloudFormation.
1835
+ * This method is modified for DynamoDB with ElectroDB single-table design.
1836
+ * It assumes the table is created and managed externally via CDK/CloudFormation.
1837
+ *
1838
+ * This implementation only validates that the required table exists and is accessible.
1839
+ * No table creation is attempted - we simply check if we can access the table.
660
1840
  */
661
- async init() {
662
- if (this.hasInitialized === null) {
663
- this.hasInitialized = this._performInitializationAndStore();
664
- }
1841
+ async createTable({ tableName }) {
1842
+ this.logger.debug("Validating access to externally managed table", { tableName, physicalTable: this.tableName });
665
1843
  try {
666
- await this.hasInitialized;
1844
+ const tableExists = await this.validateTableExists();
1845
+ if (!tableExists) {
1846
+ this.logger.error(
1847
+ `Table ${this.tableName} does not exist or is not accessible. It should be created via CDK/CloudFormation.`
1848
+ );
1849
+ throw new Error(
1850
+ `Table ${this.tableName} does not exist or is not accessible. Ensure it's created via CDK/CloudFormation before using this store.`
1851
+ );
1852
+ }
1853
+ this.logger.debug(`Table ${this.tableName} exists and is accessible`);
667
1854
  } catch (error) {
1855
+ this.logger.error("Error validating table access", { tableName: this.tableName, error });
668
1856
  throw new MastraError(
669
1857
  {
670
- id: "STORAGE_DYNAMODB_STORE_INIT_FAILED",
1858
+ id: "STORAGE_DYNAMODB_STORE_VALIDATE_TABLE_ACCESS_FAILED",
671
1859
  domain: ErrorDomain.STORAGE,
672
1860
  category: ErrorCategory.THIRD_PARTY,
673
1861
  details: { tableName: this.tableName }
@@ -676,39 +1864,32 @@ var DynamoDBStore = class extends MastraStorage {
676
1864
  );
677
1865
  }
678
1866
  }
679
- /**
680
- * Performs the actual table validation and stores the promise.
681
- * Handles resetting the stored promise on failure to allow retries.
682
- */
683
- _performInitializationAndStore() {
684
- return this.validateTableExists().then((exists) => {
685
- if (!exists) {
686
- throw new Error(
687
- `Table ${this.tableName} does not exist or is not accessible. Ensure it's created via CDK/CloudFormation before using this store.`
688
- );
689
- }
690
- return true;
691
- }).catch((err) => {
692
- this.hasInitialized = null;
693
- throw err;
694
- });
695
- }
696
- /**
697
- * Pre-processes a record to ensure Date objects are converted to ISO strings
698
- * This is necessary because ElectroDB validation happens before setters are applied
699
- */
700
- preprocessRecord(record) {
701
- const processed = { ...record };
702
- if (processed.createdAt instanceof Date) {
703
- processed.createdAt = processed.createdAt.toISOString();
704
- }
705
- if (processed.updatedAt instanceof Date) {
706
- processed.updatedAt = processed.updatedAt.toISOString();
1867
+ async insert({ tableName, record }) {
1868
+ this.logger.debug("DynamoDB insert called", { tableName });
1869
+ const entityName = this.getEntityNameForTable(tableName);
1870
+ if (!entityName || !this.service.entities[entityName]) {
1871
+ throw new MastraError({
1872
+ id: "STORAGE_DYNAMODB_STORE_INSERT_INVALID_ARGS",
1873
+ domain: ErrorDomain.STORAGE,
1874
+ category: ErrorCategory.USER,
1875
+ text: "No entity defined for tableName",
1876
+ details: { tableName }
1877
+ });
707
1878
  }
708
- if (processed.created_at instanceof Date) {
709
- processed.created_at = processed.created_at.toISOString();
1879
+ try {
1880
+ const dataToSave = { entity: entityName, ...this.preprocessRecord(record) };
1881
+ await this.service.entities[entityName].create(dataToSave).go();
1882
+ } catch (error) {
1883
+ throw new MastraError(
1884
+ {
1885
+ id: "STORAGE_DYNAMODB_STORE_INSERT_FAILED",
1886
+ domain: ErrorDomain.STORAGE,
1887
+ category: ErrorCategory.THIRD_PARTY,
1888
+ details: { tableName }
1889
+ },
1890
+ error
1891
+ );
710
1892
  }
711
- return processed;
712
1893
  }
713
1894
  async alterTable(_args) {
714
1895
  }
@@ -745,10 +1926,10 @@ var DynamoDBStore = class extends MastraStorage {
745
1926
  if (!item.id) throw new Error(`Missing required key 'id' for entity 'message'`);
746
1927
  key.id = item.id;
747
1928
  break;
748
- case "workflowSnapshot":
1929
+ case "workflow_snapshot":
749
1930
  if (!item.workflow_name)
750
- throw new Error(`Missing required key 'workflow_name' for entity 'workflowSnapshot'`);
751
- if (!item.run_id) throw new Error(`Missing required key 'run_id' for entity 'workflowSnapshot'`);
1931
+ throw new Error(`Missing required key 'workflow_name' for entity 'workflow_snapshot'`);
1932
+ if (!item.run_id) throw new Error(`Missing required key 'run_id' for entity 'workflow_snapshot'`);
752
1933
  key.workflow_name = item.workflow_name;
753
1934
  key.run_id = item.run_id;
754
1935
  break;
@@ -756,59 +1937,30 @@ var DynamoDBStore = class extends MastraStorage {
756
1937
  if (!item.run_id) throw new Error(`Missing required key 'run_id' for entity 'eval'`);
757
1938
  key.run_id = item.run_id;
758
1939
  break;
759
- case "trace":
760
- if (!item.id) throw new Error(`Missing required key 'id' for entity 'trace'`);
761
- key.id = item.id;
762
- break;
763
- default:
764
- this.logger.warn(`Unknown entity type encountered during clearTable: ${entityName}`);
765
- throw new Error(`Cannot construct delete key for unknown entity type: ${entityName}`);
766
- }
767
- return key;
768
- });
769
- const batchSize = 25;
770
- for (let i = 0; i < keysToDelete.length; i += batchSize) {
771
- const batchKeys = keysToDelete.slice(i, i + batchSize);
772
- await this.service.entities[entityName].delete(batchKeys).go();
773
- }
774
- this.logger.debug(`Successfully cleared all records for ${tableName}`);
775
- } catch (error) {
776
- throw new MastraError(
777
- {
778
- id: "STORAGE_DYNAMODB_STORE_CLEAR_TABLE_FAILED",
779
- domain: ErrorDomain.STORAGE,
780
- category: ErrorCategory.THIRD_PARTY,
781
- details: { tableName }
782
- },
783
- error
784
- );
785
- }
786
- }
787
- /**
788
- * Insert a record into the specified "table" (entity)
789
- */
790
- async insert({
791
- tableName,
792
- record
793
- }) {
794
- this.logger.debug("DynamoDB insert called", { tableName });
795
- const entityName = this.getEntityNameForTable(tableName);
796
- if (!entityName || !this.service.entities[entityName]) {
797
- throw new MastraError({
798
- id: "STORAGE_DYNAMODB_STORE_INSERT_INVALID_ARGS",
799
- domain: ErrorDomain.STORAGE,
800
- category: ErrorCategory.USER,
801
- text: "No entity defined for tableName",
802
- details: { tableName }
1940
+ case "trace":
1941
+ if (!item.id) throw new Error(`Missing required key 'id' for entity 'trace'`);
1942
+ key.id = item.id;
1943
+ break;
1944
+ case "score":
1945
+ if (!item.id) throw new Error(`Missing required key 'id' for entity 'score'`);
1946
+ key.id = item.id;
1947
+ break;
1948
+ default:
1949
+ this.logger.warn(`Unknown entity type encountered during clearTable: ${entityName}`);
1950
+ throw new Error(`Cannot construct delete key for unknown entity type: ${entityName}`);
1951
+ }
1952
+ return key;
803
1953
  });
804
- }
805
- try {
806
- const dataToSave = { entity: entityName, ...this.preprocessRecord(record) };
807
- await this.service.entities[entityName].create(dataToSave).go();
1954
+ const batchSize = 25;
1955
+ for (let i = 0; i < keysToDelete.length; i += batchSize) {
1956
+ const batchKeys = keysToDelete.slice(i, i + batchSize);
1957
+ await this.service.entities[entityName].delete(batchKeys).go();
1958
+ }
1959
+ this.logger.debug(`Successfully cleared all records for ${tableName}`);
808
1960
  } catch (error) {
809
1961
  throw new MastraError(
810
1962
  {
811
- id: "STORAGE_DYNAMODB_STORE_INSERT_FAILED",
1963
+ id: "STORAGE_DYNAMODB_STORE_CLEAR_TABLE_FAILED",
812
1964
  domain: ErrorDomain.STORAGE,
813
1965
  category: ErrorCategory.THIRD_PARTY,
814
1966
  details: { tableName }
@@ -820,10 +1972,7 @@ var DynamoDBStore = class extends MastraStorage {
820
1972
  /**
821
1973
  * Insert multiple records as a batch
822
1974
  */
823
- async batchInsert({
824
- tableName,
825
- records
826
- }) {
1975
+ async batchInsert({ tableName, records }) {
827
1976
  this.logger.debug("DynamoDB batchInsert called", { tableName, count: records.length });
828
1977
  const entityName = this.getEntityNameForTable(tableName);
829
1978
  if (!entityName || !this.service.entities[entityName]) {
@@ -868,10 +2017,7 @@ var DynamoDBStore = class extends MastraStorage {
868
2017
  /**
869
2018
  * Load a record by its keys
870
2019
  */
871
- async load({
872
- tableName,
873
- keys
874
- }) {
2020
+ async load({ tableName, keys }) {
875
2021
  this.logger.debug("DynamoDB load called", { tableName, keys });
876
2022
  const entityName = this.getEntityNameForTable(tableName);
877
2023
  if (!entityName || !this.service.entities[entityName]) {
@@ -903,268 +2049,227 @@ var DynamoDBStore = class extends MastraStorage {
903
2049
  );
904
2050
  }
905
2051
  }
906
- // Thread operations
907
- async getThreadById({ threadId }) {
908
- this.logger.debug("Getting thread by ID", { threadId });
2052
+ };
2053
+ var ScoresStorageDynamoDB = class extends ScoresStorage {
2054
+ service;
2055
+ constructor({ service }) {
2056
+ super();
2057
+ this.service = service;
2058
+ }
2059
+ // Helper function to parse score data (handle JSON fields)
2060
+ parseScoreData(data) {
2061
+ return {
2062
+ ...data,
2063
+ // Convert date strings back to Date objects for consistency
2064
+ createdAt: data.createdAt ? new Date(data.createdAt) : /* @__PURE__ */ new Date(),
2065
+ updatedAt: data.updatedAt ? new Date(data.updatedAt) : /* @__PURE__ */ new Date()
2066
+ // JSON fields are already transformed by the entity's getters
2067
+ };
2068
+ }
2069
+ async getScoreById({ id }) {
2070
+ this.logger.debug("Getting score by ID", { id });
909
2071
  try {
910
- const result = await this.service.entities.thread.get({ entity: "thread", id: threadId }).go();
2072
+ const result = await this.service.entities.score.get({ entity: "score", id }).go();
911
2073
  if (!result.data) {
912
2074
  return null;
913
2075
  }
914
- const data = result.data;
915
- return {
916
- ...data,
917
- // Convert date strings back to Date objects for consistency
918
- createdAt: typeof data.createdAt === "string" ? new Date(data.createdAt) : data.createdAt,
919
- updatedAt: typeof data.updatedAt === "string" ? new Date(data.updatedAt) : data.updatedAt
920
- // metadata: data.metadata ? JSON.parse(data.metadata) : undefined, // REMOVED by AI
921
- // metadata is already transformed by the entity's getter
922
- };
923
- } catch (error) {
924
- throw new MastraError(
925
- {
926
- id: "STORAGE_DYNAMODB_STORE_GET_THREAD_BY_ID_FAILED",
927
- domain: ErrorDomain.STORAGE,
928
- category: ErrorCategory.THIRD_PARTY,
929
- details: { threadId }
930
- },
931
- error
932
- );
933
- }
934
- }
935
- async getThreadsByResourceId({ resourceId }) {
936
- this.logger.debug("Getting threads by resource ID", { resourceId });
937
- try {
938
- const result = await this.service.entities.thread.query.byResource({ entity: "thread", resourceId }).go();
939
- if (!result.data.length) {
940
- return [];
941
- }
942
- return result.data.map((data) => ({
943
- ...data,
944
- // Convert date strings back to Date objects for consistency
945
- createdAt: typeof data.createdAt === "string" ? new Date(data.createdAt) : data.createdAt,
946
- updatedAt: typeof data.updatedAt === "string" ? new Date(data.updatedAt) : data.updatedAt
947
- // metadata: data.metadata ? JSON.parse(data.metadata) : undefined, // REMOVED by AI
948
- // metadata is already transformed by the entity's getter
949
- }));
2076
+ return this.parseScoreData(result.data);
950
2077
  } catch (error) {
951
2078
  throw new MastraError(
952
2079
  {
953
- id: "STORAGE_DYNAMODB_STORE_GET_THREADS_BY_RESOURCE_ID_FAILED",
2080
+ id: "STORAGE_DYNAMODB_STORE_GET_SCORE_BY_ID_FAILED",
954
2081
  domain: ErrorDomain.STORAGE,
955
2082
  category: ErrorCategory.THIRD_PARTY,
956
- details: { resourceId }
2083
+ details: { id }
957
2084
  },
958
2085
  error
959
2086
  );
960
2087
  }
961
2088
  }
962
- async saveThread({ thread }) {
963
- this.logger.debug("Saving thread", { threadId: thread.id });
2089
+ async saveScore(score) {
2090
+ this.logger.debug("Saving score", { scorerId: score.scorerId, runId: score.runId });
964
2091
  const now = /* @__PURE__ */ new Date();
965
- const threadData = {
966
- entity: "thread",
967
- id: thread.id,
968
- resourceId: thread.resourceId,
969
- title: thread.title || `Thread ${thread.id}`,
970
- createdAt: thread.createdAt?.toISOString() || now.toISOString(),
971
- updatedAt: now.toISOString(),
972
- metadata: thread.metadata ? JSON.stringify(thread.metadata) : void 0
2092
+ const scoreId = `score-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
2093
+ const scoreData = {
2094
+ entity: "score",
2095
+ id: scoreId,
2096
+ scorerId: score.scorerId,
2097
+ traceId: score.traceId || "",
2098
+ runId: score.runId,
2099
+ scorer: typeof score.scorer === "string" ? score.scorer : JSON.stringify(score.scorer),
2100
+ extractStepResult: typeof score.extractStepResult === "string" ? score.extractStepResult : JSON.stringify(score.extractStepResult),
2101
+ analyzeStepResult: typeof score.analyzeStepResult === "string" ? score.analyzeStepResult : JSON.stringify(score.analyzeStepResult),
2102
+ score: score.score,
2103
+ reason: score.reason,
2104
+ extractPrompt: score.extractPrompt,
2105
+ analyzePrompt: score.analyzePrompt,
2106
+ reasonPrompt: score.reasonPrompt,
2107
+ input: typeof score.input === "string" ? score.input : JSON.stringify(score.input),
2108
+ output: typeof score.output === "string" ? score.output : JSON.stringify(score.output),
2109
+ additionalContext: typeof score.additionalContext === "string" ? score.additionalContext : JSON.stringify(score.additionalContext),
2110
+ runtimeContext: typeof score.runtimeContext === "string" ? score.runtimeContext : JSON.stringify(score.runtimeContext),
2111
+ entityType: score.entityType,
2112
+ entityData: typeof score.entity === "string" ? score.entity : JSON.stringify(score.entity),
2113
+ entityId: score.entityId,
2114
+ source: score.source,
2115
+ resourceId: score.resourceId || "",
2116
+ threadId: score.threadId || "",
2117
+ createdAt: now.toISOString(),
2118
+ updatedAt: now.toISOString()
973
2119
  };
974
2120
  try {
975
- await this.service.entities.thread.upsert(threadData).go();
976
- return {
977
- id: thread.id,
978
- resourceId: thread.resourceId,
979
- title: threadData.title,
980
- createdAt: thread.createdAt || now,
981
- updatedAt: now,
982
- metadata: thread.metadata
2121
+ await this.service.entities.score.upsert(scoreData).go();
2122
+ const savedScore = {
2123
+ ...score,
2124
+ id: scoreId,
2125
+ createdAt: now,
2126
+ updatedAt: now
983
2127
  };
2128
+ return { score: savedScore };
984
2129
  } catch (error) {
985
2130
  throw new MastraError(
986
2131
  {
987
- id: "STORAGE_DYNAMODB_STORE_SAVE_THREAD_FAILED",
2132
+ id: "STORAGE_DYNAMODB_STORE_SAVE_SCORE_FAILED",
988
2133
  domain: ErrorDomain.STORAGE,
989
2134
  category: ErrorCategory.THIRD_PARTY,
990
- details: { threadId: thread.id }
2135
+ details: { scorerId: score.scorerId, runId: score.runId }
991
2136
  },
992
2137
  error
993
2138
  );
994
2139
  }
995
2140
  }
996
- async updateThread({
997
- id,
998
- title,
999
- metadata
2141
+ async getScoresByScorerId({
2142
+ scorerId,
2143
+ pagination,
2144
+ entityId,
2145
+ entityType
1000
2146
  }) {
1001
- this.logger.debug("Updating thread", { threadId: id });
2147
+ this.logger.debug("Getting scores by scorer ID", { scorerId, pagination, entityId, entityType });
1002
2148
  try {
1003
- const existingThread = await this.getThreadById({ threadId: id });
1004
- if (!existingThread) {
1005
- throw new Error(`Thread not found: ${id}`);
1006
- }
1007
- const now = /* @__PURE__ */ new Date();
1008
- const updateData = {
1009
- updatedAt: now.toISOString()
1010
- };
1011
- if (title) {
1012
- updateData.title = title;
2149
+ const query = this.service.entities.score.query.byScorer({ entity: "score", scorerId });
2150
+ const results = await query.go();
2151
+ let allScores = results.data.map((data) => this.parseScoreData(data));
2152
+ if (entityId) {
2153
+ allScores = allScores.filter((score) => score.entityId === entityId);
1013
2154
  }
1014
- if (metadata) {
1015
- updateData.metadata = JSON.stringify(metadata);
2155
+ if (entityType) {
2156
+ allScores = allScores.filter((score) => score.entityType === entityType);
1016
2157
  }
1017
- await this.service.entities.thread.update({ entity: "thread", id }).set(updateData).go();
2158
+ allScores.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
2159
+ const startIndex = pagination.page * pagination.perPage;
2160
+ const endIndex = startIndex + pagination.perPage;
2161
+ const paginatedScores = allScores.slice(startIndex, endIndex);
2162
+ const total = allScores.length;
2163
+ const hasMore = endIndex < total;
1018
2164
  return {
1019
- ...existingThread,
1020
- title: title || existingThread.title,
1021
- metadata: metadata || existingThread.metadata,
1022
- updatedAt: now
2165
+ scores: paginatedScores,
2166
+ pagination: {
2167
+ total,
2168
+ page: pagination.page,
2169
+ perPage: pagination.perPage,
2170
+ hasMore
2171
+ }
1023
2172
  };
1024
2173
  } catch (error) {
1025
2174
  throw new MastraError(
1026
2175
  {
1027
- id: "STORAGE_DYNAMODB_STORE_UPDATE_THREAD_FAILED",
2176
+ id: "STORAGE_DYNAMODB_STORE_GET_SCORES_BY_SCORER_ID_FAILED",
1028
2177
  domain: ErrorDomain.STORAGE,
1029
2178
  category: ErrorCategory.THIRD_PARTY,
1030
- details: { threadId: id }
2179
+ details: {
2180
+ scorerId: scorerId || "",
2181
+ entityId: entityId || "",
2182
+ entityType: entityType || "",
2183
+ page: pagination.page,
2184
+ perPage: pagination.perPage
2185
+ }
1031
2186
  },
1032
2187
  error
1033
2188
  );
1034
2189
  }
1035
2190
  }
1036
- async deleteThread({ threadId }) {
1037
- this.logger.debug("Deleting thread", { threadId });
2191
+ async getScoresByRunId({
2192
+ runId,
2193
+ pagination
2194
+ }) {
2195
+ this.logger.debug("Getting scores by run ID", { runId, pagination });
1038
2196
  try {
1039
- await this.service.entities.thread.delete({ entity: "thread", id: threadId }).go();
2197
+ const query = this.service.entities.score.query.byRun({ entity: "score", runId });
2198
+ const results = await query.go();
2199
+ const allScores = results.data.map((data) => this.parseScoreData(data));
2200
+ allScores.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
2201
+ const startIndex = pagination.page * pagination.perPage;
2202
+ const endIndex = startIndex + pagination.perPage;
2203
+ const paginatedScores = allScores.slice(startIndex, endIndex);
2204
+ const total = allScores.length;
2205
+ const hasMore = endIndex < total;
2206
+ return {
2207
+ scores: paginatedScores,
2208
+ pagination: {
2209
+ total,
2210
+ page: pagination.page,
2211
+ perPage: pagination.perPage,
2212
+ hasMore
2213
+ }
2214
+ };
1040
2215
  } catch (error) {
1041
2216
  throw new MastraError(
1042
2217
  {
1043
- id: "STORAGE_DYNAMODB_STORE_DELETE_THREAD_FAILED",
2218
+ id: "STORAGE_DYNAMODB_STORE_GET_SCORES_BY_RUN_ID_FAILED",
1044
2219
  domain: ErrorDomain.STORAGE,
1045
2220
  category: ErrorCategory.THIRD_PARTY,
1046
- details: { threadId }
2221
+ details: { runId, page: pagination.page, perPage: pagination.perPage }
1047
2222
  },
1048
2223
  error
1049
2224
  );
1050
2225
  }
1051
2226
  }
1052
- async getMessages({
1053
- threadId,
1054
- resourceId,
1055
- selectBy,
1056
- format
2227
+ async getScoresByEntityId({
2228
+ entityId,
2229
+ entityType,
2230
+ pagination
1057
2231
  }) {
1058
- this.logger.debug("Getting messages", { threadId, selectBy });
2232
+ this.logger.debug("Getting scores by entity ID", { entityId, entityType, pagination });
1059
2233
  try {
1060
- const query = this.service.entities.message.query.byThread({ entity: "message", threadId });
1061
- const limit = this.resolveMessageLimit({ last: selectBy?.last, defaultLimit: Number.MAX_SAFE_INTEGER });
1062
- if (limit !== Number.MAX_SAFE_INTEGER) {
1063
- const results2 = await query.go({ limit, order: "desc" });
1064
- const list2 = new MessageList({ threadId, resourceId }).add(
1065
- results2.data.map((data) => this.parseMessageData(data)),
1066
- "memory"
1067
- );
1068
- if (format === `v2`) return list2.get.all.v2();
1069
- return list2.get.all.v1();
1070
- }
2234
+ const query = this.service.entities.score.query.byEntityData({ entity: "score", entityId });
1071
2235
  const results = await query.go();
1072
- const list = new MessageList({ threadId, resourceId }).add(
1073
- results.data.map((data) => this.parseMessageData(data)),
1074
- "memory"
1075
- );
1076
- if (format === `v2`) return list.get.all.v2();
1077
- return list.get.all.v1();
1078
- } catch (error) {
1079
- throw new MastraError(
1080
- {
1081
- id: "STORAGE_DYNAMODB_STORE_GET_MESSAGES_FAILED",
1082
- domain: ErrorDomain.STORAGE,
1083
- category: ErrorCategory.THIRD_PARTY,
1084
- details: { threadId }
1085
- },
1086
- error
1087
- );
1088
- }
1089
- }
1090
- async saveMessages(args) {
1091
- const { messages, format = "v1" } = args;
1092
- this.logger.debug("Saving messages", { count: messages.length });
1093
- if (!messages.length) {
1094
- return [];
1095
- }
1096
- const threadId = messages[0]?.threadId;
1097
- if (!threadId) {
1098
- throw new Error("Thread ID is required");
1099
- }
1100
- const messagesToSave = messages.map((msg) => {
1101
- const now = (/* @__PURE__ */ new Date()).toISOString();
2236
+ let allScores = results.data.map((data) => this.parseScoreData(data));
2237
+ allScores = allScores.filter((score) => score.entityType === entityType);
2238
+ allScores.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
2239
+ const startIndex = pagination.page * pagination.perPage;
2240
+ const endIndex = startIndex + pagination.perPage;
2241
+ const paginatedScores = allScores.slice(startIndex, endIndex);
2242
+ const total = allScores.length;
2243
+ const hasMore = endIndex < total;
1102
2244
  return {
1103
- entity: "message",
1104
- // Add entity type
1105
- id: msg.id,
1106
- threadId: msg.threadId,
1107
- role: msg.role,
1108
- type: msg.type,
1109
- resourceId: msg.resourceId,
1110
- // Ensure complex fields are stringified if not handled by attribute setters
1111
- content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content),
1112
- toolCallArgs: `toolCallArgs` in msg && msg.toolCallArgs ? JSON.stringify(msg.toolCallArgs) : void 0,
1113
- toolCallIds: `toolCallIds` in msg && msg.toolCallIds ? JSON.stringify(msg.toolCallIds) : void 0,
1114
- toolNames: `toolNames` in msg && msg.toolNames ? JSON.stringify(msg.toolNames) : void 0,
1115
- createdAt: msg.createdAt instanceof Date ? msg.createdAt.toISOString() : msg.createdAt || now,
1116
- updatedAt: now
1117
- // Add updatedAt
2245
+ scores: paginatedScores,
2246
+ pagination: {
2247
+ total,
2248
+ page: pagination.page,
2249
+ perPage: pagination.perPage,
2250
+ hasMore
2251
+ }
1118
2252
  };
1119
- });
1120
- try {
1121
- const batchSize = 25;
1122
- const batches = [];
1123
- for (let i = 0; i < messagesToSave.length; i += batchSize) {
1124
- const batch = messagesToSave.slice(i, i + batchSize);
1125
- batches.push(batch);
1126
- }
1127
- await Promise.all([
1128
- // Process message batches
1129
- ...batches.map(async (batch) => {
1130
- for (const messageData of batch) {
1131
- if (!messageData.entity) {
1132
- this.logger.error("Missing entity property in message data for create", { messageData });
1133
- throw new Error("Internal error: Missing entity property during saveMessages");
1134
- }
1135
- await this.service.entities.message.put(messageData).go();
1136
- }
1137
- }),
1138
- // Update thread's updatedAt timestamp
1139
- this.service.entities.thread.update({ entity: "thread", id: threadId }).set({
1140
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1141
- }).go()
1142
- ]);
1143
- const list = new MessageList().add(messages, "memory");
1144
- if (format === `v1`) return list.get.all.v1();
1145
- return list.get.all.v2();
1146
2253
  } catch (error) {
1147
2254
  throw new MastraError(
1148
2255
  {
1149
- id: "STORAGE_DYNAMODB_STORE_SAVE_MESSAGES_FAILED",
2256
+ id: "STORAGE_DYNAMODB_STORE_GET_SCORES_BY_ENTITY_ID_FAILED",
1150
2257
  domain: ErrorDomain.STORAGE,
1151
2258
  category: ErrorCategory.THIRD_PARTY,
1152
- details: { count: messages.length }
2259
+ details: { entityId, entityType, page: pagination.page, perPage: pagination.perPage }
1153
2260
  },
1154
2261
  error
1155
2262
  );
1156
2263
  }
1157
2264
  }
1158
- // Helper function to parse message data (handle JSON fields)
1159
- parseMessageData(data) {
1160
- return {
1161
- ...data,
1162
- // Ensure dates are Date objects if needed (ElectroDB might return strings)
1163
- createdAt: data.createdAt ? new Date(data.createdAt) : void 0,
1164
- updatedAt: data.updatedAt ? new Date(data.updatedAt) : void 0
1165
- // Other fields like content, toolCallArgs etc. are assumed to be correctly
1166
- // transformed by the ElectroDB entity getters.
1167
- };
2265
+ };
2266
+ var TracesStorageDynamoDB = class extends TracesStorage {
2267
+ service;
2268
+ operations;
2269
+ constructor({ service, operations }) {
2270
+ super();
2271
+ this.service = service;
2272
+ this.operations = operations;
1168
2273
  }
1169
2274
  // Trace operations
1170
2275
  async getTraces(args) {
@@ -1215,7 +2320,7 @@ var DynamoDBStore = class extends MastraStorage {
1215
2320
  }
1216
2321
  try {
1217
2322
  const recordsToSave = records.map((rec) => ({ entity: "trace", ...rec }));
1218
- await this.batchInsert({
2323
+ await this.operations.batchInsert({
1219
2324
  tableName: TABLE_TRACES,
1220
2325
  records: recordsToSave
1221
2326
  // Pass records with 'entity' included
@@ -1223,15 +2328,190 @@ var DynamoDBStore = class extends MastraStorage {
1223
2328
  } catch (error) {
1224
2329
  throw new MastraError(
1225
2330
  {
1226
- id: "STORAGE_DYNAMODB_STORE_BATCH_TRACE_INSERT_FAILED",
2331
+ id: "STORAGE_DYNAMODB_STORE_BATCH_TRACE_INSERT_FAILED",
2332
+ domain: ErrorDomain.STORAGE,
2333
+ category: ErrorCategory.THIRD_PARTY,
2334
+ details: { count: records.length }
2335
+ },
2336
+ error
2337
+ );
2338
+ }
2339
+ }
2340
+ async getTracesPaginated(args) {
2341
+ const { name, scope, page = 0, perPage = 100, attributes, filters, dateRange } = args;
2342
+ this.logger.debug("Getting traces with pagination", { name, scope, page, perPage, attributes, filters, dateRange });
2343
+ try {
2344
+ let query;
2345
+ if (name) {
2346
+ query = this.service.entities.trace.query.byName({ entity: "trace", name });
2347
+ } else if (scope) {
2348
+ query = this.service.entities.trace.query.byScope({ entity: "trace", scope });
2349
+ } else {
2350
+ this.logger.warn("Performing a scan operation on traces - consider using a more specific query");
2351
+ query = this.service.entities.trace.scan;
2352
+ }
2353
+ const results = await query.go({
2354
+ order: "desc",
2355
+ pages: "all"
2356
+ // Get all pages to apply filtering and pagination
2357
+ });
2358
+ if (!results.data.length) {
2359
+ return {
2360
+ traces: [],
2361
+ total: 0,
2362
+ page,
2363
+ perPage,
2364
+ hasMore: false
2365
+ };
2366
+ }
2367
+ let filteredData = results.data;
2368
+ if (attributes) {
2369
+ filteredData = filteredData.filter((item) => {
2370
+ try {
2371
+ let itemAttributes = {};
2372
+ if (item.attributes) {
2373
+ if (typeof item.attributes === "string") {
2374
+ if (item.attributes === "[object Object]") {
2375
+ itemAttributes = {};
2376
+ } else {
2377
+ try {
2378
+ itemAttributes = JSON.parse(item.attributes);
2379
+ } catch {
2380
+ itemAttributes = {};
2381
+ }
2382
+ }
2383
+ } else if (typeof item.attributes === "object") {
2384
+ itemAttributes = item.attributes;
2385
+ }
2386
+ }
2387
+ return Object.entries(attributes).every(([key, value]) => itemAttributes[key] === value);
2388
+ } catch (e) {
2389
+ this.logger.warn("Failed to parse attributes during filtering", { item, error: e });
2390
+ return false;
2391
+ }
2392
+ });
2393
+ }
2394
+ if (dateRange?.start) {
2395
+ filteredData = filteredData.filter((item) => {
2396
+ const itemDate = new Date(item.createdAt);
2397
+ return itemDate >= dateRange.start;
2398
+ });
2399
+ }
2400
+ if (dateRange?.end) {
2401
+ filteredData = filteredData.filter((item) => {
2402
+ const itemDate = new Date(item.createdAt);
2403
+ return itemDate <= dateRange.end;
2404
+ });
2405
+ }
2406
+ const total = filteredData.length;
2407
+ const start = page * perPage;
2408
+ const end = start + perPage;
2409
+ const paginatedData = filteredData.slice(start, end);
2410
+ const traces = paginatedData.map((item) => {
2411
+ let attributes2;
2412
+ if (item.attributes) {
2413
+ if (typeof item.attributes === "string") {
2414
+ if (item.attributes === "[object Object]") {
2415
+ attributes2 = void 0;
2416
+ } else {
2417
+ try {
2418
+ attributes2 = JSON.parse(item.attributes);
2419
+ } catch {
2420
+ attributes2 = void 0;
2421
+ }
2422
+ }
2423
+ } else if (typeof item.attributes === "object") {
2424
+ attributes2 = item.attributes;
2425
+ }
2426
+ }
2427
+ let status;
2428
+ if (item.status) {
2429
+ if (typeof item.status === "string") {
2430
+ try {
2431
+ status = JSON.parse(item.status);
2432
+ } catch {
2433
+ status = void 0;
2434
+ }
2435
+ } else if (typeof item.status === "object") {
2436
+ status = item.status;
2437
+ }
2438
+ }
2439
+ let events;
2440
+ if (item.events) {
2441
+ if (typeof item.events === "string") {
2442
+ try {
2443
+ events = JSON.parse(item.events);
2444
+ } catch {
2445
+ events = void 0;
2446
+ }
2447
+ } else if (Array.isArray(item.events)) {
2448
+ events = item.events;
2449
+ }
2450
+ }
2451
+ let links;
2452
+ if (item.links) {
2453
+ if (typeof item.links === "string") {
2454
+ try {
2455
+ links = JSON.parse(item.links);
2456
+ } catch {
2457
+ links = void 0;
2458
+ }
2459
+ } else if (Array.isArray(item.links)) {
2460
+ links = item.links;
2461
+ }
2462
+ }
2463
+ return {
2464
+ id: item.id,
2465
+ parentSpanId: item.parentSpanId,
2466
+ name: item.name,
2467
+ traceId: item.traceId,
2468
+ scope: item.scope,
2469
+ kind: item.kind,
2470
+ attributes: attributes2,
2471
+ status,
2472
+ events,
2473
+ links,
2474
+ other: item.other,
2475
+ startTime: item.startTime,
2476
+ endTime: item.endTime,
2477
+ createdAt: item.createdAt
2478
+ };
2479
+ });
2480
+ return {
2481
+ traces,
2482
+ total,
2483
+ page,
2484
+ perPage,
2485
+ hasMore: end < total
2486
+ };
2487
+ } catch (error) {
2488
+ throw new MastraError(
2489
+ {
2490
+ id: "STORAGE_DYNAMODB_STORE_GET_TRACES_PAGINATED_FAILED",
1227
2491
  domain: ErrorDomain.STORAGE,
1228
- category: ErrorCategory.THIRD_PARTY,
1229
- details: { count: records.length }
2492
+ category: ErrorCategory.THIRD_PARTY
1230
2493
  },
1231
2494
  error
1232
2495
  );
1233
2496
  }
1234
2497
  }
2498
+ };
2499
+ function formatWorkflowRun(snapshotData) {
2500
+ return {
2501
+ workflowName: snapshotData.workflow_name,
2502
+ runId: snapshotData.run_id,
2503
+ snapshot: snapshotData.snapshot,
2504
+ createdAt: new Date(snapshotData.createdAt),
2505
+ updatedAt: new Date(snapshotData.updatedAt),
2506
+ resourceId: snapshotData.resourceId
2507
+ };
2508
+ }
2509
+ var WorkflowStorageDynamoDB = class extends WorkflowsStorage {
2510
+ service;
2511
+ constructor({ service }) {
2512
+ super();
2513
+ this.service = service;
2514
+ }
1235
2515
  // Workflow operations
1236
2516
  async persistWorkflowSnapshot({
1237
2517
  workflowName,
@@ -1253,7 +2533,7 @@ var DynamoDBStore = class extends MastraStorage {
1253
2533
  updatedAt: now,
1254
2534
  resourceId
1255
2535
  };
1256
- await this.service.entities.workflowSnapshot.upsert(data).go();
2536
+ await this.service.entities.workflow_snapshot.upsert(data).go();
1257
2537
  } catch (error) {
1258
2538
  throw new MastraError(
1259
2539
  {
@@ -1272,7 +2552,7 @@ var DynamoDBStore = class extends MastraStorage {
1272
2552
  }) {
1273
2553
  this.logger.debug("Loading workflow snapshot", { workflowName, runId });
1274
2554
  try {
1275
- const result = await this.service.entities.workflowSnapshot.get({
2555
+ const result = await this.service.entities.workflow_snapshot.get({
1276
2556
  entity: "workflow_snapshot",
1277
2557
  // Add entity type
1278
2558
  workflow_name: workflowName,
@@ -1301,14 +2581,14 @@ var DynamoDBStore = class extends MastraStorage {
1301
2581
  const offset = args?.offset || 0;
1302
2582
  let query;
1303
2583
  if (args?.workflowName) {
1304
- query = this.service.entities.workflowSnapshot.query.primary({
2584
+ query = this.service.entities.workflow_snapshot.query.primary({
1305
2585
  entity: "workflow_snapshot",
1306
2586
  // Add entity type
1307
2587
  workflow_name: args.workflowName
1308
2588
  });
1309
2589
  } else {
1310
2590
  this.logger.warn("Performing a scan operation on workflow snapshots - consider using a more specific query");
1311
- query = this.service.entities.workflowSnapshot.scan;
2591
+ query = this.service.entities.workflow_snapshot.scan;
1312
2592
  }
1313
2593
  const allMatchingSnapshots = [];
1314
2594
  let cursor = null;
@@ -1346,7 +2626,7 @@ var DynamoDBStore = class extends MastraStorage {
1346
2626
  }
1347
2627
  const total = allMatchingSnapshots.length;
1348
2628
  const paginatedData = allMatchingSnapshots.slice(offset, offset + limit);
1349
- const runs = paginatedData.map((snapshot) => this.formatWorkflowRun(snapshot));
2629
+ const runs = paginatedData.map((snapshot) => formatWorkflowRun(snapshot));
1350
2630
  return {
1351
2631
  runs,
1352
2632
  total
@@ -1366,15 +2646,18 @@ var DynamoDBStore = class extends MastraStorage {
1366
2646
  async getWorkflowRunById(args) {
1367
2647
  const { runId, workflowName } = args;
1368
2648
  this.logger.debug("Getting workflow run by ID", { runId, workflowName });
2649
+ console.log("workflowName", workflowName);
2650
+ console.log("runId", runId);
1369
2651
  try {
1370
2652
  if (workflowName) {
1371
2653
  this.logger.debug("WorkflowName provided, using direct GET operation.");
1372
- const result2 = await this.service.entities.workflowSnapshot.get({
2654
+ const result2 = await this.service.entities.workflow_snapshot.get({
1373
2655
  entity: "workflow_snapshot",
1374
2656
  // Entity type for PK
1375
2657
  workflow_name: workflowName,
1376
2658
  run_id: runId
1377
2659
  }).go();
2660
+ console.log("result", result2);
1378
2661
  if (!result2.data) {
1379
2662
  return null;
1380
2663
  }
@@ -1391,7 +2674,7 @@ var DynamoDBStore = class extends MastraStorage {
1391
2674
  this.logger.debug(
1392
2675
  'WorkflowName not provided. Attempting to find workflow run by runId using GSI. Ensure GSI (e.g., "byRunId") is defined on the workflowSnapshot entity with run_id as its key and provisioned in DynamoDB.'
1393
2676
  );
1394
- const result = await this.service.entities.workflowSnapshot.query.gsi2({ entity: "workflow_snapshot", run_id: runId }).go();
2677
+ const result = await this.service.entities.workflow_snapshot.query.gsi2({ entity: "workflow_snapshot", run_id: runId }).go();
1395
2678
  const matchingRunDbItem = result.data && result.data.length > 0 ? result.data[0] : null;
1396
2679
  if (!matchingRunDbItem) {
1397
2680
  return null;
@@ -1417,121 +2700,247 @@ var DynamoDBStore = class extends MastraStorage {
1417
2700
  );
1418
2701
  }
1419
2702
  }
1420
- // Helper function to format workflow run
1421
- formatWorkflowRun(snapshotData) {
1422
- return {
1423
- workflowName: snapshotData.workflow_name,
1424
- runId: snapshotData.run_id,
1425
- snapshot: snapshotData.snapshot,
1426
- createdAt: new Date(snapshotData.createdAt),
1427
- updatedAt: new Date(snapshotData.updatedAt),
1428
- resourceId: snapshotData.resourceId
1429
- };
2703
+ };
2704
+
2705
+ // src/storage/index.ts
2706
+ var DynamoDBStore = class extends MastraStorage {
2707
+ tableName;
2708
+ client;
2709
+ service;
2710
+ hasInitialized = null;
2711
+ stores;
2712
+ constructor({ name, config }) {
2713
+ super({ name });
2714
+ try {
2715
+ if (!config.tableName || typeof config.tableName !== "string" || config.tableName.trim() === "") {
2716
+ throw new Error("DynamoDBStore: config.tableName must be provided and cannot be empty.");
2717
+ }
2718
+ if (!/^[a-zA-Z0-9_.-]{3,255}$/.test(config.tableName)) {
2719
+ throw new Error(
2720
+ `DynamoDBStore: config.tableName "${config.tableName}" contains invalid characters or is not between 3 and 255 characters long.`
2721
+ );
2722
+ }
2723
+ const dynamoClient = new DynamoDBClient({
2724
+ region: config.region || "us-east-1",
2725
+ endpoint: config.endpoint,
2726
+ credentials: config.credentials
2727
+ });
2728
+ this.tableName = config.tableName;
2729
+ this.client = DynamoDBDocumentClient.from(dynamoClient);
2730
+ this.service = getElectroDbService(this.client, this.tableName);
2731
+ const operations = new StoreOperationsDynamoDB({
2732
+ service: this.service,
2733
+ tableName: this.tableName,
2734
+ client: this.client
2735
+ });
2736
+ const traces = new TracesStorageDynamoDB({ service: this.service, operations });
2737
+ const workflows = new WorkflowStorageDynamoDB({ service: this.service });
2738
+ const memory = new MemoryStorageDynamoDB({ service: this.service });
2739
+ const scores = new ScoresStorageDynamoDB({ service: this.service });
2740
+ this.stores = {
2741
+ operations,
2742
+ legacyEvals: new LegacyEvalsDynamoDB({ service: this.service, tableName: this.tableName }),
2743
+ traces,
2744
+ workflows,
2745
+ memory,
2746
+ scores
2747
+ };
2748
+ } catch (error) {
2749
+ throw new MastraError(
2750
+ {
2751
+ id: "STORAGE_DYNAMODB_STORE_CONSTRUCTOR_FAILED",
2752
+ domain: ErrorDomain.STORAGE,
2753
+ category: ErrorCategory.USER
2754
+ },
2755
+ error
2756
+ );
2757
+ }
1430
2758
  }
1431
- // Helper methods for entity/table mapping
1432
- getEntityNameForTable(tableName) {
1433
- const mapping = {
1434
- [TABLE_THREADS]: "thread",
1435
- [TABLE_MESSAGES]: "message",
1436
- [TABLE_WORKFLOW_SNAPSHOT]: "workflowSnapshot",
1437
- [TABLE_EVALS]: "eval",
1438
- [TABLE_TRACES]: "trace"
2759
+ get supports() {
2760
+ return {
2761
+ selectByIncludeResourceScope: true,
2762
+ resourceWorkingMemory: true,
2763
+ hasColumn: false,
2764
+ createTable: false
1439
2765
  };
1440
- return mapping[tableName] || null;
1441
2766
  }
1442
- // Eval operations
1443
- async getEvalsByAgentName(agentName, type) {
1444
- this.logger.debug("Getting evals for agent", { agentName, type });
2767
+ /**
2768
+ * Validates that the required DynamoDB table exists and is accessible.
2769
+ * This does not check the table structure - it assumes the table
2770
+ * was created with the correct structure via CDK/CloudFormation.
2771
+ */
2772
+ async validateTableExists() {
1445
2773
  try {
1446
- const query = this.service.entities.eval.query.byAgent({ entity: "eval", agent_name: agentName });
1447
- const results = await query.go({ order: "desc", limit: 100 });
1448
- if (!results.data.length) {
1449
- return [];
1450
- }
1451
- let filteredData = results.data;
1452
- if (type) {
1453
- filteredData = filteredData.filter((evalRecord) => {
1454
- try {
1455
- const testInfo = evalRecord.test_info && typeof evalRecord.test_info === "string" ? JSON.parse(evalRecord.test_info) : void 0;
1456
- if (type === "test" && !testInfo) {
1457
- return false;
1458
- }
1459
- if (type === "live" && testInfo) {
1460
- return false;
1461
- }
1462
- } catch (e) {
1463
- this.logger.warn("Failed to parse test_info during filtering", { record: evalRecord, error: e });
1464
- }
1465
- return true;
1466
- });
1467
- }
1468
- return filteredData.map((evalRecord) => {
1469
- try {
1470
- return {
1471
- input: evalRecord.input,
1472
- output: evalRecord.output,
1473
- // Safely parse result and test_info
1474
- result: evalRecord.result && typeof evalRecord.result === "string" ? JSON.parse(evalRecord.result) : void 0,
1475
- agentName: evalRecord.agent_name,
1476
- createdAt: evalRecord.created_at,
1477
- // Keep as string from DDB?
1478
- metricName: evalRecord.metric_name,
1479
- instructions: evalRecord.instructions,
1480
- runId: evalRecord.run_id,
1481
- globalRunId: evalRecord.global_run_id,
1482
- testInfo: evalRecord.test_info && typeof evalRecord.test_info === "string" ? JSON.parse(evalRecord.test_info) : void 0
1483
- };
1484
- } catch (parseError) {
1485
- this.logger.error("Failed to parse eval record", { record: evalRecord, error: parseError });
1486
- return {
1487
- agentName: evalRecord.agent_name,
1488
- createdAt: evalRecord.created_at,
1489
- runId: evalRecord.run_id,
1490
- globalRunId: evalRecord.global_run_id
1491
- };
1492
- }
2774
+ const command = new DescribeTableCommand({
2775
+ TableName: this.tableName
1493
2776
  });
2777
+ await this.client.send(command);
2778
+ return true;
2779
+ } catch (error) {
2780
+ if (error.name === "ResourceNotFoundException") {
2781
+ return false;
2782
+ }
2783
+ throw new MastraError(
2784
+ {
2785
+ id: "STORAGE_DYNAMODB_STORE_VALIDATE_TABLE_EXISTS_FAILED",
2786
+ domain: ErrorDomain.STORAGE,
2787
+ category: ErrorCategory.THIRD_PARTY,
2788
+ details: { tableName: this.tableName }
2789
+ },
2790
+ error
2791
+ );
2792
+ }
2793
+ }
2794
+ /**
2795
+ * Initialize storage, validating the externally managed table is accessible.
2796
+ * For the single-table design, we only validate once that we can access
2797
+ * the table that was created via CDK/CloudFormation.
2798
+ */
2799
+ async init() {
2800
+ if (this.hasInitialized === null) {
2801
+ this.hasInitialized = this._performInitializationAndStore();
2802
+ }
2803
+ try {
2804
+ await this.hasInitialized;
1494
2805
  } catch (error) {
1495
2806
  throw new MastraError(
1496
2807
  {
1497
- id: "STORAGE_DYNAMODB_STORE_GET_EVALS_BY_AGENT_NAME_FAILED",
2808
+ id: "STORAGE_DYNAMODB_STORE_INIT_FAILED",
1498
2809
  domain: ErrorDomain.STORAGE,
1499
2810
  category: ErrorCategory.THIRD_PARTY,
1500
- details: { agentName }
2811
+ details: { tableName: this.tableName }
1501
2812
  },
1502
2813
  error
1503
2814
  );
1504
2815
  }
1505
2816
  }
2817
+ /**
2818
+ * Performs the actual table validation and stores the promise.
2819
+ * Handles resetting the stored promise on failure to allow retries.
2820
+ */
2821
+ _performInitializationAndStore() {
2822
+ return this.validateTableExists().then((exists) => {
2823
+ if (!exists) {
2824
+ throw new Error(
2825
+ `Table ${this.tableName} does not exist or is not accessible. Ensure it's created via CDK/CloudFormation before using this store.`
2826
+ );
2827
+ }
2828
+ return true;
2829
+ }).catch((err) => {
2830
+ this.hasInitialized = null;
2831
+ throw err;
2832
+ });
2833
+ }
2834
+ async createTable({ tableName, schema }) {
2835
+ return this.stores.operations.createTable({ tableName, schema });
2836
+ }
2837
+ async alterTable(_args) {
2838
+ return this.stores.operations.alterTable(_args);
2839
+ }
2840
+ async clearTable({ tableName }) {
2841
+ return this.stores.operations.clearTable({ tableName });
2842
+ }
2843
+ async dropTable({ tableName }) {
2844
+ return this.stores.operations.dropTable({ tableName });
2845
+ }
2846
+ async insert({ tableName, record }) {
2847
+ return this.stores.operations.insert({ tableName, record });
2848
+ }
2849
+ async batchInsert({ tableName, records }) {
2850
+ return this.stores.operations.batchInsert({ tableName, records });
2851
+ }
2852
+ async load({ tableName, keys }) {
2853
+ return this.stores.operations.load({ tableName, keys });
2854
+ }
2855
+ // Thread operations
2856
+ async getThreadById({ threadId }) {
2857
+ return this.stores.memory.getThreadById({ threadId });
2858
+ }
2859
+ async getThreadsByResourceId({ resourceId }) {
2860
+ return this.stores.memory.getThreadsByResourceId({ resourceId });
2861
+ }
2862
+ async saveThread({ thread }) {
2863
+ return this.stores.memory.saveThread({ thread });
2864
+ }
2865
+ async updateThread({
2866
+ id,
2867
+ title,
2868
+ metadata
2869
+ }) {
2870
+ return this.stores.memory.updateThread({ id, title, metadata });
2871
+ }
2872
+ async deleteThread({ threadId }) {
2873
+ return this.stores.memory.deleteThread({ threadId });
2874
+ }
2875
+ async getMessages({
2876
+ threadId,
2877
+ resourceId,
2878
+ selectBy,
2879
+ format
2880
+ }) {
2881
+ return this.stores.memory.getMessages({ threadId, resourceId, selectBy, format });
2882
+ }
2883
+ async saveMessages(args) {
2884
+ return this.stores.memory.saveMessages(args);
2885
+ }
2886
+ async getThreadsByResourceIdPaginated(args) {
2887
+ return this.stores.memory.getThreadsByResourceIdPaginated(args);
2888
+ }
2889
+ async getMessagesPaginated(args) {
2890
+ return this.stores.memory.getMessagesPaginated(args);
2891
+ }
2892
+ async updateMessages(_args) {
2893
+ return this.stores.memory.updateMessages(_args);
2894
+ }
2895
+ // Trace operations
2896
+ async getTraces(args) {
2897
+ return this.stores.traces.getTraces(args);
2898
+ }
2899
+ async batchTraceInsert({ records }) {
2900
+ return this.stores.traces.batchTraceInsert({ records });
2901
+ }
1506
2902
  async getTracesPaginated(_args) {
1507
- throw new MastraError(
1508
- {
1509
- id: "STORAGE_DYNAMODB_STORE_GET_TRACES_PAGINATED_FAILED",
1510
- domain: ErrorDomain.STORAGE,
1511
- category: ErrorCategory.THIRD_PARTY
1512
- },
1513
- new Error("Method not implemented.")
1514
- );
2903
+ return this.stores.traces.getTracesPaginated(_args);
1515
2904
  }
1516
- async getThreadsByResourceIdPaginated(_args) {
1517
- throw new MastraError(
1518
- {
1519
- id: "STORAGE_DYNAMODB_STORE_GET_THREADS_BY_RESOURCE_ID_PAGINATED_FAILED",
1520
- domain: ErrorDomain.STORAGE,
1521
- category: ErrorCategory.THIRD_PARTY
1522
- },
1523
- new Error("Method not implemented.")
1524
- );
2905
+ // Workflow operations
2906
+ async persistWorkflowSnapshot({
2907
+ workflowName,
2908
+ runId,
2909
+ snapshot
2910
+ }) {
2911
+ return this.stores.workflows.persistWorkflowSnapshot({ workflowName, runId, snapshot });
1525
2912
  }
1526
- async getMessagesPaginated(_args) {
1527
- throw new MastraError(
1528
- {
1529
- id: "STORAGE_DYNAMODB_STORE_GET_MESSAGES_PAGINATED_FAILED",
1530
- domain: ErrorDomain.STORAGE,
1531
- category: ErrorCategory.THIRD_PARTY
1532
- },
1533
- new Error("Method not implemented.")
1534
- );
2913
+ async loadWorkflowSnapshot({
2914
+ workflowName,
2915
+ runId
2916
+ }) {
2917
+ return this.stores.workflows.loadWorkflowSnapshot({ workflowName, runId });
2918
+ }
2919
+ async getWorkflowRuns(args) {
2920
+ return this.stores.workflows.getWorkflowRuns(args);
2921
+ }
2922
+ async getWorkflowRunById(args) {
2923
+ return this.stores.workflows.getWorkflowRunById(args);
2924
+ }
2925
+ async getResourceById({ resourceId }) {
2926
+ return this.stores.memory.getResourceById({ resourceId });
2927
+ }
2928
+ async saveResource({ resource }) {
2929
+ return this.stores.memory.saveResource({ resource });
2930
+ }
2931
+ async updateResource({
2932
+ resourceId,
2933
+ workingMemory,
2934
+ metadata
2935
+ }) {
2936
+ return this.stores.memory.updateResource({ resourceId, workingMemory, metadata });
2937
+ }
2938
+ // Eval operations
2939
+ async getEvalsByAgentName(agentName, type) {
2940
+ return this.stores.legacyEvals.getEvalsByAgentName(agentName, type);
2941
+ }
2942
+ async getEvals(options) {
2943
+ return this.stores.legacyEvals.getEvals(options);
1535
2944
  }
1536
2945
  /**
1537
2946
  * Closes the DynamoDB client connection and cleans up resources.
@@ -1553,9 +2962,37 @@ var DynamoDBStore = class extends MastraStorage {
1553
2962
  );
1554
2963
  }
1555
2964
  }
1556
- async updateMessages(_args) {
1557
- this.logger.error("updateMessages is not yet implemented in DynamoDBStore");
1558
- throw new Error("Method not implemented");
2965
+ /**
2966
+ * SCORERS - Not implemented
2967
+ */
2968
+ async getScoreById({ id: _id }) {
2969
+ return this.stores.scores.getScoreById({ id: _id });
2970
+ }
2971
+ async saveScore(_score) {
2972
+ return this.stores.scores.saveScore(_score);
2973
+ }
2974
+ async getScoresByRunId({
2975
+ runId: _runId,
2976
+ pagination: _pagination
2977
+ }) {
2978
+ return this.stores.scores.getScoresByRunId({ runId: _runId, pagination: _pagination });
2979
+ }
2980
+ async getScoresByEntityId({
2981
+ entityId: _entityId,
2982
+ entityType: _entityType,
2983
+ pagination: _pagination
2984
+ }) {
2985
+ return this.stores.scores.getScoresByEntityId({
2986
+ entityId: _entityId,
2987
+ entityType: _entityType,
2988
+ pagination: _pagination
2989
+ });
2990
+ }
2991
+ async getScoresByScorerId({
2992
+ scorerId: _scorerId,
2993
+ pagination: _pagination
2994
+ }) {
2995
+ return this.stores.scores.getScoresByScorerId({ scorerId: _scorerId, pagination: _pagination });
1559
2996
  }
1560
2997
  };
1561
2998