@mastra/mongodb 1.0.0-beta.1 → 1.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { MastraVector } from '@mastra/core/vector';
3
3
  import { MongoClient } from 'mongodb';
4
4
  import { v4 } from 'uuid';
5
5
  import { BaseFilterTranslator } from '@mastra/core/vector/filter';
6
- import { MastraStorage, StoreOperations, TABLE_SCHEMAS, safelyParseJSON, MemoryStorage, TABLE_MESSAGES, normalizePerPage, calculatePagination, TABLE_THREADS, TABLE_RESOURCES, ScoresStorage, TABLE_SCORERS, WorkflowsStorage, TABLE_WORKFLOW_SNAPSHOT, ObservabilityStorage, TABLE_SPANS } from '@mastra/core/storage';
6
+ import { MastraStorage, StoreOperations, TABLE_SCHEMAS, safelyParseJSON, MemoryStorage, TABLE_MESSAGES, normalizePerPage, calculatePagination, TABLE_THREADS, TABLE_RESOURCES, ScoresStorage, TABLE_SCORERS, WorkflowsStorage, TABLE_WORKFLOW_SNAPSHOT, ObservabilityStorage, TABLE_SPANS, transformScoreRow as transformScoreRow$1 } from '@mastra/core/storage';
7
7
  import { MessageList } from '@mastra/core/agent';
8
8
  import { saveScorePayloadSchema } from '@mastra/core/evals';
9
9
 
@@ -11,7 +11,7 @@ import { saveScorePayloadSchema } from '@mastra/core/evals';
11
11
 
12
12
  // package.json
13
13
  var package_default = {
14
- version: "1.0.0-beta.1"};
14
+ version: "1.0.0-beta.3"};
15
15
  var MongoDBFilterTranslator = class extends BaseFilterTranslator {
16
16
  getSupportedOperators() {
17
17
  return {
@@ -353,12 +353,17 @@ var MongoDBVector = class extends MastraVector {
353
353
  limit: topK
354
354
  };
355
355
  if (Object.keys(combinedFilter).length > 0) {
356
- const candidateIds = await collection.aggregate([{ $match: combinedFilter }, { $project: { _id: 1 } }]).map((doc) => doc._id).toArray();
356
+ const filterWithExclusion = {
357
+ $and: [{ _id: { $ne: "__index_metadata__" } }, combinedFilter]
358
+ };
359
+ const candidateIds = await collection.aggregate([{ $match: filterWithExclusion }, { $project: { _id: 1 } }]).map((doc) => doc._id).toArray();
357
360
  if (candidateIds.length > 0) {
358
361
  vectorSearch.filter = { _id: { $in: candidateIds } };
359
362
  } else {
360
363
  return [];
361
364
  }
365
+ } else {
366
+ vectorSearch.filter = { _id: { $ne: "__index_metadata__" } };
362
367
  }
363
368
  const pipeline = [
364
369
  {
@@ -479,7 +484,26 @@ var MongoDBVector = class extends MastraVector {
479
484
  * @returns A promise that resolves when the update is complete.
480
485
  * @throws Will throw an error if no updates are provided or if the update operation fails.
481
486
  */
482
- async updateVector({ indexName, id, update }) {
487
+ async updateVector(params) {
488
+ const { indexName, update } = params;
489
+ if ("id" in params && params.id && "filter" in params && params.filter) {
490
+ throw new MastraError({
491
+ id: "STORAGE_MONGODB_VECTOR_UPDATE_MUTUALLY_EXCLUSIVE_PARAMS",
492
+ domain: ErrorDomain.STORAGE,
493
+ category: ErrorCategory.USER,
494
+ details: { indexName },
495
+ text: "id and filter are mutually exclusive - provide only one"
496
+ });
497
+ }
498
+ if (!("id" in params || "filter" in params) || !params.id && !params.filter) {
499
+ throw new MastraError({
500
+ id: "STORAGE_MONGODB_VECTOR_UPDATE_MISSING_PARAMS",
501
+ domain: ErrorDomain.STORAGE,
502
+ category: ErrorCategory.USER,
503
+ text: "Either id or filter must be provided",
504
+ details: { indexName }
505
+ });
506
+ }
483
507
  try {
484
508
  if (!update.vector && !update.metadata) {
485
509
  throw new Error("No updates provided");
@@ -501,17 +525,49 @@ var MongoDBVector = class extends MastraVector {
501
525
  );
502
526
  updateDoc[this.metadataFieldName] = normalizedMeta;
503
527
  }
504
- await collection.findOneAndUpdate({ _id: id }, { $set: updateDoc });
528
+ if ("id" in params && params.id) {
529
+ await collection.findOneAndUpdate({ _id: params.id }, { $set: updateDoc });
530
+ } else if ("filter" in params && params.filter) {
531
+ const filter = params.filter;
532
+ if (!filter || Object.keys(filter).length === 0) {
533
+ throw new MastraError({
534
+ id: "STORAGE_MONGODB_VECTOR_UPDATE_EMPTY_FILTER",
535
+ domain: ErrorDomain.STORAGE,
536
+ category: ErrorCategory.USER,
537
+ details: { indexName },
538
+ text: "Cannot update with empty filter"
539
+ });
540
+ }
541
+ const mongoFilter = this.transformFilter(filter);
542
+ const transformedFilter = this.transformMetadataFilter(mongoFilter);
543
+ if (!transformedFilter || Object.keys(transformedFilter).length === 0) {
544
+ throw new MastraError({
545
+ id: "STORAGE_MONGODB_VECTOR_UPDATE_INVALID_FILTER",
546
+ domain: ErrorDomain.STORAGE,
547
+ category: ErrorCategory.USER,
548
+ details: { indexName },
549
+ text: "Filter produced empty query"
550
+ });
551
+ }
552
+ await collection.updateMany(transformedFilter, { $set: updateDoc });
553
+ }
505
554
  } catch (error) {
555
+ if (error instanceof MastraError) {
556
+ throw error;
557
+ }
558
+ const errorDetails = { indexName };
559
+ if ("id" in params && params.id) {
560
+ errorDetails.id = params.id;
561
+ }
562
+ if ("filter" in params && params.filter) {
563
+ errorDetails.filter = JSON.stringify(params.filter);
564
+ }
506
565
  throw new MastraError(
507
566
  {
508
567
  id: "STORAGE_MONGODB_VECTOR_UPDATE_VECTOR_FAILED",
509
568
  domain: ErrorDomain.STORAGE,
510
569
  category: ErrorCategory.THIRD_PARTY,
511
- details: {
512
- indexName,
513
- id
514
- }
570
+ details: errorDetails
515
571
  },
516
572
  error
517
573
  );
@@ -543,6 +599,83 @@ var MongoDBVector = class extends MastraVector {
543
599
  );
544
600
  }
545
601
  }
602
+ async deleteVectors({ indexName, filter, ids }) {
603
+ if (!filter && !ids) {
604
+ throw new MastraError({
605
+ id: "STORAGE_MONGODB_VECTOR_DELETE_MISSING_PARAMS",
606
+ domain: ErrorDomain.STORAGE,
607
+ category: ErrorCategory.USER,
608
+ details: { indexName },
609
+ text: "Either filter or ids must be provided"
610
+ });
611
+ }
612
+ if (filter && ids) {
613
+ throw new MastraError({
614
+ id: "STORAGE_MONGODB_VECTOR_DELETE_CONFLICTING_PARAMS",
615
+ domain: ErrorDomain.STORAGE,
616
+ category: ErrorCategory.USER,
617
+ details: { indexName },
618
+ text: "Cannot provide both filter and ids - they are mutually exclusive"
619
+ });
620
+ }
621
+ try {
622
+ const collection = await this.getCollection(indexName, true);
623
+ if (ids) {
624
+ if (ids.length === 0) {
625
+ throw new MastraError({
626
+ id: "STORAGE_MONGODB_VECTOR_DELETE_EMPTY_IDS",
627
+ domain: ErrorDomain.STORAGE,
628
+ category: ErrorCategory.USER,
629
+ details: { indexName },
630
+ text: "Cannot delete with empty ids array"
631
+ });
632
+ }
633
+ await collection.deleteMany({ _id: { $in: ids } });
634
+ } else {
635
+ if (!filter || Object.keys(filter).length === 0) {
636
+ throw new MastraError({
637
+ id: "STORAGE_MONGODB_VECTOR_DELETE_EMPTY_FILTER",
638
+ domain: ErrorDomain.STORAGE,
639
+ category: ErrorCategory.USER,
640
+ details: { indexName },
641
+ text: "Cannot delete with empty filter"
642
+ });
643
+ }
644
+ const mongoFilter = this.transformFilter(filter);
645
+ const transformedFilter = this.transformMetadataFilter(mongoFilter);
646
+ if (!transformedFilter || Object.keys(transformedFilter).length === 0) {
647
+ throw new MastraError({
648
+ id: "STORAGE_MONGODB_VECTOR_DELETE_INVALID_FILTER",
649
+ domain: ErrorDomain.STORAGE,
650
+ category: ErrorCategory.USER,
651
+ details: { indexName },
652
+ text: "Filter produced empty query"
653
+ });
654
+ }
655
+ const finalFilter = {
656
+ $and: [{ _id: { $ne: "__index_metadata__" } }, transformedFilter]
657
+ };
658
+ await collection.deleteMany(finalFilter);
659
+ }
660
+ } catch (error) {
661
+ if (error instanceof MastraError) {
662
+ throw error;
663
+ }
664
+ throw new MastraError(
665
+ {
666
+ id: "STORAGE_MONGODB_VECTOR_DELETE_VECTORS_FAILED",
667
+ domain: ErrorDomain.STORAGE,
668
+ category: ErrorCategory.THIRD_PARTY,
669
+ details: {
670
+ indexName,
671
+ ...filter && { filter: JSON.stringify(filter) },
672
+ ...ids && { idsCount: ids.length }
673
+ }
674
+ },
675
+ error
676
+ );
677
+ }
678
+ }
546
679
  // Private methods
547
680
  async getCollection(indexName, throwIfNotExists = true) {
548
681
  if (this.collections.has(indexName)) {
@@ -590,16 +723,26 @@ var MongoDBVector = class extends MastraVector {
590
723
  */
591
724
  transformMetadataFilter(filter) {
592
725
  if (!filter || typeof filter !== "object") return filter;
726
+ if (Array.isArray(filter)) {
727
+ return filter.map((item) => this.transformMetadataFilter(item));
728
+ }
593
729
  const transformed = {};
594
730
  for (const [key, value] of Object.entries(filter)) {
595
731
  if (key.startsWith("$")) {
596
732
  if (Array.isArray(value)) {
597
733
  transformed[key] = value.map((item) => this.transformMetadataFilter(item));
598
- } else {
734
+ } else if (typeof value === "object" && value !== null) {
599
735
  transformed[key] = this.transformMetadataFilter(value);
736
+ } else {
737
+ transformed[key] = value;
600
738
  }
601
739
  } else if (key.startsWith("metadata.")) {
602
- transformed[key] = value;
740
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
741
+ const hasOperator = Object.keys(value).some((k) => k.startsWith("$"));
742
+ transformed[key] = hasOperator ? value : value;
743
+ } else {
744
+ transformed[key] = value;
745
+ }
603
746
  } else if (this.isMetadataField(key)) {
604
747
  transformed[`metadata.${key}`] = value;
605
748
  } else {
@@ -693,8 +836,6 @@ var MongoDBConnector = class _MongoDBConnector {
693
836
  }
694
837
  }
695
838
  };
696
-
697
- // src/storage/domains/utils.ts
698
839
  function formatDateForMongoDB(date) {
699
840
  return typeof date === "string" ? new Date(date) : date;
700
841
  }
@@ -725,18 +866,16 @@ var MemoryStorageMongoDB = class extends MemoryStorage {
725
866
  if (row.type && row.type !== "v2") result.type = row.type;
726
867
  return result;
727
868
  }
728
- async _getIncludedMessages({
729
- threadId,
730
- include
731
- }) {
732
- if (!threadId.trim()) throw new Error("threadId must be a non-empty string");
733
- if (!include) return null;
869
+ async _getIncludedMessages({ include }) {
870
+ if (!include || include.length === 0) return null;
734
871
  const collection = await this.operations.getCollection(TABLE_MESSAGES);
735
872
  const includedMessages = [];
736
873
  for (const inc of include) {
737
874
  const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
738
- const searchThreadId = inc.threadId || threadId;
739
- const allMessages = await collection.find({ thread_id: searchThreadId }).sort({ createdAt: 1 }).toArray();
875
+ const targetMessage = await collection.findOne({ id });
876
+ if (!targetMessage) continue;
877
+ const messageThreadId = targetMessage.thread_id;
878
+ const allMessages = await collection.find({ thread_id: messageThreadId }).sort({ createdAt: 1 }).toArray();
740
879
  const targetIndex = allMessages.findIndex((msg) => msg.id === id);
741
880
  if (targetIndex === -1) continue;
742
881
  const startIndex = Math.max(0, targetIndex - withPreviousMessages);
@@ -777,15 +916,16 @@ var MemoryStorageMongoDB = class extends MemoryStorage {
777
916
  }
778
917
  async listMessages(args) {
779
918
  const { threadId, resourceId, include, filter, perPage: perPageInput, page = 0, orderBy } = args;
780
- if (!threadId.trim()) {
919
+ const threadIds = Array.isArray(threadId) ? threadId : [threadId];
920
+ if (threadIds.length === 0 || threadIds.some((id) => !id.trim())) {
781
921
  throw new MastraError(
782
922
  {
783
923
  id: "STORAGE_MONGODB_LIST_MESSAGES_INVALID_THREAD_ID",
784
924
  domain: ErrorDomain.STORAGE,
785
925
  category: ErrorCategory.THIRD_PARTY,
786
- details: { threadId }
926
+ details: { threadId: Array.isArray(threadId) ? threadId.join(",") : threadId }
787
927
  },
788
- new Error("threadId must be a non-empty string")
928
+ new Error("threadId must be a non-empty string or array of non-empty strings")
789
929
  );
790
930
  }
791
931
  if (page < 0) {
@@ -805,7 +945,7 @@ var MemoryStorageMongoDB = class extends MemoryStorage {
805
945
  const { field, direction } = this.parseOrderBy(orderBy, "ASC");
806
946
  const sortOrder = direction === "ASC" ? 1 : -1;
807
947
  const collection = await this.operations.getCollection(TABLE_MESSAGES);
808
- const query = { thread_id: threadId };
948
+ const query = { thread_id: threadIds.length === 1 ? threadIds[0] : { $in: threadIds } };
809
949
  if (resourceId) {
810
950
  query.resourceId = resourceId;
811
951
  }
@@ -837,7 +977,7 @@ var MemoryStorageMongoDB = class extends MemoryStorage {
837
977
  }
838
978
  const messageIds = new Set(messages.map((m) => m.id));
839
979
  if (include && include.length > 0) {
840
- const includeMessages = await this._getIncludedMessages({ threadId, include });
980
+ const includeMessages = await this._getIncludedMessages({ include });
841
981
  if (includeMessages) {
842
982
  for (const includeMsg of includeMessages) {
843
983
  if (!messageIds.has(includeMsg.id)) {
@@ -858,7 +998,10 @@ var MemoryStorageMongoDB = class extends MemoryStorage {
858
998
  }
859
999
  return direction === "ASC" ? String(aValue).localeCompare(String(bValue)) : String(bValue).localeCompare(String(aValue));
860
1000
  });
861
- const returnedThreadMessageIds = new Set(finalMessages.filter((m) => m.threadId === threadId).map((m) => m.id));
1001
+ const threadIdSet = new Set(threadIds);
1002
+ const returnedThreadMessageIds = new Set(
1003
+ finalMessages.filter((m) => m.threadId && threadIdSet.has(m.threadId)).map((m) => m.id)
1004
+ );
862
1005
  const allThreadMessagesReturned = returnedThreadMessageIds.size >= total;
863
1006
  const hasMore = perPageInput !== false && !allThreadMessagesReturned && offset + perPage < total;
864
1007
  return {
@@ -875,7 +1018,7 @@ var MemoryStorageMongoDB = class extends MemoryStorage {
875
1018
  domain: ErrorDomain.STORAGE,
876
1019
  category: ErrorCategory.THIRD_PARTY,
877
1020
  details: {
878
- threadId,
1021
+ threadId: Array.isArray(threadId) ? threadId.join(",") : threadId,
879
1022
  resourceId: resourceId ?? ""
880
1023
  }
881
1024
  },
@@ -1755,98 +1898,9 @@ var StoreOperationsMongoDB = class extends StoreOperations {
1755
1898
  }
1756
1899
  };
1757
1900
  function transformScoreRow(row) {
1758
- let scorerValue = null;
1759
- if (row.scorer) {
1760
- try {
1761
- scorerValue = typeof row.scorer === "string" ? safelyParseJSON(row.scorer) : row.scorer;
1762
- } catch (e) {
1763
- console.warn("Failed to parse scorer:", e);
1764
- }
1765
- }
1766
- let preprocessStepResultValue = null;
1767
- if (row.preprocessStepResult) {
1768
- try {
1769
- preprocessStepResultValue = typeof row.preprocessStepResult === "string" ? safelyParseJSON(row.preprocessStepResult) : row.preprocessStepResult;
1770
- } catch (e) {
1771
- console.warn("Failed to parse preprocessStepResult:", e);
1772
- }
1773
- }
1774
- let analyzeStepResultValue = null;
1775
- if (row.analyzeStepResult) {
1776
- try {
1777
- analyzeStepResultValue = typeof row.analyzeStepResult === "string" ? safelyParseJSON(row.analyzeStepResult) : row.analyzeStepResult;
1778
- } catch (e) {
1779
- console.warn("Failed to parse analyzeStepResult:", e);
1780
- }
1781
- }
1782
- let inputValue = null;
1783
- if (row.input) {
1784
- try {
1785
- inputValue = typeof row.input === "string" ? safelyParseJSON(row.input) : row.input;
1786
- } catch (e) {
1787
- console.warn("Failed to parse input:", e);
1788
- }
1789
- }
1790
- let outputValue = null;
1791
- if (row.output) {
1792
- try {
1793
- outputValue = typeof row.output === "string" ? safelyParseJSON(row.output) : row.output;
1794
- } catch (e) {
1795
- console.warn("Failed to parse output:", e);
1796
- }
1797
- }
1798
- let entityValue = null;
1799
- if (row.entity) {
1800
- try {
1801
- entityValue = typeof row.entity === "string" ? safelyParseJSON(row.entity) : row.entity;
1802
- } catch (e) {
1803
- console.warn("Failed to parse entity:", e);
1804
- }
1805
- }
1806
- let requestContextValue = null;
1807
- if (row.requestContext) {
1808
- try {
1809
- requestContextValue = typeof row.requestContext === "string" ? safelyParseJSON(row.requestContext) : row.requestContext;
1810
- } catch (e) {
1811
- console.warn("Failed to parse requestContext:", e);
1812
- }
1813
- }
1814
- let metadataValue = null;
1815
- if (row.metadata) {
1816
- try {
1817
- metadataValue = typeof row.metadata === "string" ? safelyParseJSON(row.metadata) : row.metadata;
1818
- } catch (e) {
1819
- console.warn("Failed to parse metadata:", e);
1820
- }
1821
- }
1822
- return {
1823
- id: row.id,
1824
- entityId: row.entityId,
1825
- entityType: row.entityType,
1826
- scorerId: row.scorerId,
1827
- traceId: row.traceId,
1828
- spanId: row.spanId,
1829
- runId: row.runId,
1830
- scorer: scorerValue,
1831
- preprocessStepResult: preprocessStepResultValue,
1832
- preprocessPrompt: row.preprocessPrompt,
1833
- analyzeStepResult: analyzeStepResultValue,
1834
- generateScorePrompt: row.generateScorePrompt,
1835
- score: row.score,
1836
- analyzePrompt: row.analyzePrompt,
1837
- reasonPrompt: row.reasonPrompt,
1838
- metadata: metadataValue,
1839
- input: inputValue,
1840
- output: outputValue,
1841
- additionalContext: row.additionalContext,
1842
- requestContext: requestContextValue,
1843
- entity: entityValue,
1844
- source: row.source,
1845
- resourceId: row.resourceId,
1846
- threadId: row.threadId,
1847
- createdAt: new Date(row.createdAt),
1848
- updatedAt: new Date(row.updatedAt)
1849
- };
1901
+ return transformScoreRow$1(row, {
1902
+ convertTimestamps: true
1903
+ });
1850
1904
  }
1851
1905
  var ScoresStorageMongoDB = class extends ScoresStorage {
1852
1906
  operations;
@@ -1891,36 +1945,29 @@ var ScoresStorageMongoDB = class extends ScoresStorage {
1891
1945
  try {
1892
1946
  const now = /* @__PURE__ */ new Date();
1893
1947
  const scoreId = `score-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
1894
- const scoreData = {
1895
- id: scoreId,
1896
- entityId: validatedScore.entityId,
1897
- entityType: validatedScore.entityType,
1898
- scorerId: validatedScore.scorerId,
1899
- traceId: validatedScore.traceId || "",
1900
- spanId: validatedScore.spanId || "",
1901
- runId: validatedScore.runId,
1902
- scorer: typeof validatedScore.scorer === "string" ? safelyParseJSON(validatedScore.scorer) : validatedScore.scorer,
1903
- preprocessStepResult: typeof validatedScore.preprocessStepResult === "string" ? safelyParseJSON(validatedScore.preprocessStepResult) : validatedScore.preprocessStepResult,
1904
- analyzeStepResult: typeof validatedScore.analyzeStepResult === "string" ? safelyParseJSON(validatedScore.analyzeStepResult) : validatedScore.analyzeStepResult,
1905
- score: validatedScore.score,
1906
- reason: validatedScore.reason,
1907
- preprocessPrompt: validatedScore.preprocessPrompt,
1908
- generateScorePrompt: validatedScore.generateScorePrompt,
1909
- generateReasonPrompt: validatedScore.generateReasonPrompt,
1910
- analyzePrompt: validatedScore.analyzePrompt,
1911
- input: typeof validatedScore.input === "string" ? safelyParseJSON(validatedScore.input) : validatedScore.input,
1912
- output: typeof validatedScore.output === "string" ? safelyParseJSON(validatedScore.output) : validatedScore.output,
1913
- additionalContext: validatedScore.additionalContext,
1914
- requestContext: typeof validatedScore.requestContext === "string" ? safelyParseJSON(validatedScore.requestContext) : validatedScore.requestContext,
1915
- entity: typeof validatedScore.entity === "string" ? safelyParseJSON(validatedScore.entity) : validatedScore.entity,
1916
- source: validatedScore.source,
1917
- resourceId: validatedScore.resourceId || "",
1918
- threadId: validatedScore.threadId || "",
1919
- createdAt: now,
1920
- updatedAt: now
1948
+ const scorer = typeof validatedScore.scorer === "string" ? safelyParseJSON(validatedScore.scorer) : validatedScore.scorer;
1949
+ const preprocessStepResult = typeof validatedScore.preprocessStepResult === "string" ? safelyParseJSON(validatedScore.preprocessStepResult) : validatedScore.preprocessStepResult;
1950
+ const analyzeStepResult = typeof validatedScore.analyzeStepResult === "string" ? safelyParseJSON(validatedScore.analyzeStepResult) : validatedScore.analyzeStepResult;
1951
+ const input = typeof validatedScore.input === "string" ? safelyParseJSON(validatedScore.input) : validatedScore.input;
1952
+ const output = typeof validatedScore.output === "string" ? safelyParseJSON(validatedScore.output) : validatedScore.output;
1953
+ const requestContext = typeof validatedScore.requestContext === "string" ? safelyParseJSON(validatedScore.requestContext) : validatedScore.requestContext;
1954
+ const entity = typeof validatedScore.entity === "string" ? safelyParseJSON(validatedScore.entity) : validatedScore.entity;
1955
+ const createdAt = now;
1956
+ const updatedAt = now;
1957
+ const dataToSave = {
1958
+ ...validatedScore,
1959
+ scorer,
1960
+ preprocessStepResult,
1961
+ analyzeStepResult,
1962
+ input,
1963
+ output,
1964
+ requestContext,
1965
+ entity,
1966
+ createdAt,
1967
+ updatedAt
1921
1968
  };
1922
1969
  const collection = await this.operations.getCollection(TABLE_SCORERS);
1923
- await collection.insertOne(scoreData);
1970
+ await collection.insertOne(dataToSave);
1924
1971
  const savedScore = {
1925
1972
  ...score,
1926
1973
  id: scoreId,