@mastra/mongodb 0.11.1-alpha.1 → 0.12.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,23 +1,23 @@
1
1
 
2
- > @mastra/mongodb@0.11.1-alpha.1 build /home/runner/work/mastra/mastra/stores/mongodb
2
+ > @mastra/mongodb@0.12.0-alpha.3 build /home/runner/work/mastra/mastra/stores/mongodb
3
3
  > tsup src/index.ts --format esm,cjs --experimental-dts --clean --treeshake=smallest --splitting
4
4
 
5
5
  CLI Building entry: src/index.ts
6
6
  CLI Using tsconfig: tsconfig.json
7
7
  CLI tsup v8.5.0
8
8
  TSC Build start
9
- TSC ⚡️ Build success in 8941ms
9
+ TSC ⚡️ Build success in 11566ms
10
10
  DTS Build start
11
11
  CLI Target: es2022
12
12
  Analysis will use the bundled TypeScript version 5.8.3
13
13
  Writing package typings: /home/runner/work/mastra/mastra/stores/mongodb/dist/_tsup-dts-rollup.d.ts
14
14
  Analysis will use the bundled TypeScript version 5.8.3
15
15
  Writing package typings: /home/runner/work/mastra/mastra/stores/mongodb/dist/_tsup-dts-rollup.d.cts
16
- DTS ⚡️ Build success in 11876ms
16
+ DTS ⚡️ Build success in 12520ms
17
17
  CLI Cleaning output folder
18
18
  ESM Build start
19
19
  CJS Build start
20
- CJS dist/index.cjs 42.74 KB
21
- CJS ⚡️ Build success in 976ms
22
- ESM dist/index.js 41.92 KB
23
- ESM ⚡️ Build success in 976ms
20
+ CJS dist/index.cjs 43.36 KB
21
+ CJS ⚡️ Build success in 1128ms
22
+ ESM dist/index.js 42.52 KB
23
+ ESM ⚡️ Build success in 1130ms
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # @mastra/mongodb
2
2
 
3
+ ## 0.12.0-alpha.3
4
+
5
+ ### Minor Changes
6
+
7
+ - 8a3bfd2: Update peerdeps to latest core
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [792c4c0]
12
+ - Updated dependencies [502fe05]
13
+ - Updated dependencies [4efcfa0]
14
+ - @mastra/core@0.10.7-alpha.3
15
+
16
+ ## 0.11.1-alpha.2
17
+
18
+ ### Patch Changes
19
+
20
+ - a2eceb2: fix: mongo storage trace query
21
+ - 0fb9d64: [MASTRA-4018] Update saveMessages in storage adapters to upsert messages
22
+ - 870f156: Parse eval result
23
+ - 144eb0b: [MASTRA-3669] Metadata Filter Types
24
+ - Updated dependencies [15e9d26]
25
+ - Updated dependencies [07d6d88]
26
+ - Updated dependencies [5d74aab]
27
+ - Updated dependencies [144eb0b]
28
+ - @mastra/core@0.10.7-alpha.2
29
+
3
30
  ## 0.11.1-alpha.1
4
31
 
5
32
  ### Patch Changes
@@ -1,10 +1,12 @@
1
1
  import { BaseFilterTranslator } from '@mastra/core/vector/filter';
2
+ import type { BlacklistedRootOperators } from '@mastra/core/vector/filter';
2
3
  import type { CreateIndexParams } from '@mastra/core/vector';
3
4
  import type { DeleteIndexParams } from '@mastra/core/vector';
4
5
  import type { DeleteVectorParams } from '@mastra/core/vector';
5
6
  import type { DescribeIndexParams } from '@mastra/core/vector';
6
7
  import type { EvalRow } from '@mastra/core/storage';
7
8
  import type { IndexStats } from '@mastra/core/vector';
9
+ import type { LogicalOperatorValueMap } from '@mastra/core/vector/filter';
8
10
  import type { MastraMessageContentV2 } from '@mastra/core/agent';
9
11
  import type { MastraMessageV1 } from '@mastra/core/memory';
10
12
  import type { MastraMessageV2 } from '@mastra/core/memory';
@@ -12,6 +14,7 @@ import { MastraStorage } from '@mastra/core/storage';
12
14
  import { MastraVector } from '@mastra/core/vector';
13
15
  import type { MongoClientOptions } from 'mongodb';
14
16
  import type { OperatorSupport } from '@mastra/core/vector/filter';
17
+ import type { OperatorValueMap } from '@mastra/core/vector/filter';
15
18
  import type { PaginationInfo } from '@mastra/core/storage';
16
19
  import type { QueryResult } from '@mastra/core/vector';
17
20
  import type { QueryVectorParams } from '@mastra/core/vector';
@@ -23,6 +26,7 @@ import type { TABLE_NAMES } from '@mastra/core/storage';
23
26
  import type { Trace } from '@mastra/core/telemetry';
24
27
  import type { UpdateVectorParams } from '@mastra/core/vector';
25
28
  import type { UpsertVectorParams } from '@mastra/core/vector';
29
+ import type { VectorFieldValue } from '@mastra/core/vector/filter';
26
30
  import type { VectorFilter } from '@mastra/core/vector/filter';
27
31
  import type { WorkflowRun } from '@mastra/core/storage';
28
32
  import type { WorkflowRunState } from '@mastra/core/workflows';
@@ -35,6 +39,8 @@ declare const MONGODB_PROMPT = "When querying MongoDB Vector, you can ONLY use t
35
39
  export { MONGODB_PROMPT }
36
40
  export { MONGODB_PROMPT as MONGODB_PROMPT_alias_1 }
37
41
 
42
+ declare type MongoDBBlacklisted = BlacklistedRootOperators | '$size';
43
+
38
44
  declare interface MongoDBConfig {
39
45
  url: string;
40
46
  dbName: string;
@@ -48,9 +54,9 @@ export { MongoDBConfig as MongoDBConfig_alias_1 }
48
54
  * Maintains MongoDB-compatible syntax while ensuring proper validation
49
55
  * and normalization of values.
50
56
  */
51
- export declare class MongoDBFilterTranslator extends BaseFilterTranslator {
57
+ export declare class MongoDBFilterTranslator extends BaseFilterTranslator<MongoDBVectorFilter> {
52
58
  protected getSupportedOperators(): OperatorSupport;
53
- translate(filter?: VectorFilter): any;
59
+ translate(filter?: MongoDBVectorFilter): any;
54
60
  private translateNode;
55
61
  private translateOperatorValue;
56
62
  isEmpty(filter: any): boolean;
@@ -64,11 +70,13 @@ declare interface MongoDBIndexReadyParams {
64
70
  export { MongoDBIndexReadyParams }
65
71
  export { MongoDBIndexReadyParams as MongoDBIndexReadyParams_alias_1 }
66
72
 
67
- declare interface MongoDBQueryVectorParams extends QueryVectorParams {
68
- documentFilter?: VectorFilter;
73
+ declare type MongoDBOperatorValueMap = Omit<OperatorValueMap, '$options'> & {
74
+ $size: number;
75
+ };
76
+
77
+ declare interface MongoDBQueryVectorParams extends QueryVectorParams<MongoDBVectorFilter> {
78
+ documentFilter?: MongoDBVectorFilter;
69
79
  }
70
- export { MongoDBQueryVectorParams }
71
- export { MongoDBQueryVectorParams as MongoDBQueryVectorParams_alias_1 }
72
80
 
73
81
  declare class MongoDBStore extends MastraStorage {
74
82
  #private;
@@ -207,7 +215,7 @@ declare interface MongoDBUpsertVectorParams extends UpsertVectorParams {
207
215
  export { MongoDBUpsertVectorParams }
208
216
  export { MongoDBUpsertVectorParams as MongoDBUpsertVectorParams_alias_1 }
209
217
 
210
- declare class MongoDBVector extends MastraVector {
218
+ declare class MongoDBVector extends MastraVector<MongoDBVectorFilter> {
211
219
  private client;
212
220
  private db;
213
221
  private collections;
@@ -271,4 +279,6 @@ declare class MongoDBVector extends MastraVector {
271
279
  export { MongoDBVector }
272
280
  export { MongoDBVector as MongoDBVector_alias_1 }
273
281
 
282
+ export declare type MongoDBVectorFilter = VectorFilter<keyof MongoDBOperatorValueMap, MongoDBOperatorValueMap, LogicalOperatorValueMap, MongoDBBlacklisted, VectorFieldValue | RegExp>;
283
+
274
284
  export { }
@@ -1,10 +1,12 @@
1
1
  import { BaseFilterTranslator } from '@mastra/core/vector/filter';
2
+ import type { BlacklistedRootOperators } from '@mastra/core/vector/filter';
2
3
  import type { CreateIndexParams } from '@mastra/core/vector';
3
4
  import type { DeleteIndexParams } from '@mastra/core/vector';
4
5
  import type { DeleteVectorParams } from '@mastra/core/vector';
5
6
  import type { DescribeIndexParams } from '@mastra/core/vector';
6
7
  import type { EvalRow } from '@mastra/core/storage';
7
8
  import type { IndexStats } from '@mastra/core/vector';
9
+ import type { LogicalOperatorValueMap } from '@mastra/core/vector/filter';
8
10
  import type { MastraMessageContentV2 } from '@mastra/core/agent';
9
11
  import type { MastraMessageV1 } from '@mastra/core/memory';
10
12
  import type { MastraMessageV2 } from '@mastra/core/memory';
@@ -12,6 +14,7 @@ import { MastraStorage } from '@mastra/core/storage';
12
14
  import { MastraVector } from '@mastra/core/vector';
13
15
  import type { MongoClientOptions } from 'mongodb';
14
16
  import type { OperatorSupport } from '@mastra/core/vector/filter';
17
+ import type { OperatorValueMap } from '@mastra/core/vector/filter';
15
18
  import type { PaginationInfo } from '@mastra/core/storage';
16
19
  import type { QueryResult } from '@mastra/core/vector';
17
20
  import type { QueryVectorParams } from '@mastra/core/vector';
@@ -23,6 +26,7 @@ import type { TABLE_NAMES } from '@mastra/core/storage';
23
26
  import type { Trace } from '@mastra/core/telemetry';
24
27
  import type { UpdateVectorParams } from '@mastra/core/vector';
25
28
  import type { UpsertVectorParams } from '@mastra/core/vector';
29
+ import type { VectorFieldValue } from '@mastra/core/vector/filter';
26
30
  import type { VectorFilter } from '@mastra/core/vector/filter';
27
31
  import type { WorkflowRun } from '@mastra/core/storage';
28
32
  import type { WorkflowRunState } from '@mastra/core/workflows';
@@ -35,6 +39,8 @@ declare const MONGODB_PROMPT = "When querying MongoDB Vector, you can ONLY use t
35
39
  export { MONGODB_PROMPT }
36
40
  export { MONGODB_PROMPT as MONGODB_PROMPT_alias_1 }
37
41
 
42
+ declare type MongoDBBlacklisted = BlacklistedRootOperators | '$size';
43
+
38
44
  declare interface MongoDBConfig {
39
45
  url: string;
40
46
  dbName: string;
@@ -48,9 +54,9 @@ export { MongoDBConfig as MongoDBConfig_alias_1 }
48
54
  * Maintains MongoDB-compatible syntax while ensuring proper validation
49
55
  * and normalization of values.
50
56
  */
51
- export declare class MongoDBFilterTranslator extends BaseFilterTranslator {
57
+ export declare class MongoDBFilterTranslator extends BaseFilterTranslator<MongoDBVectorFilter> {
52
58
  protected getSupportedOperators(): OperatorSupport;
53
- translate(filter?: VectorFilter): any;
59
+ translate(filter?: MongoDBVectorFilter): any;
54
60
  private translateNode;
55
61
  private translateOperatorValue;
56
62
  isEmpty(filter: any): boolean;
@@ -64,11 +70,13 @@ declare interface MongoDBIndexReadyParams {
64
70
  export { MongoDBIndexReadyParams }
65
71
  export { MongoDBIndexReadyParams as MongoDBIndexReadyParams_alias_1 }
66
72
 
67
- declare interface MongoDBQueryVectorParams extends QueryVectorParams {
68
- documentFilter?: VectorFilter;
73
+ declare type MongoDBOperatorValueMap = Omit<OperatorValueMap, '$options'> & {
74
+ $size: number;
75
+ };
76
+
77
+ declare interface MongoDBQueryVectorParams extends QueryVectorParams<MongoDBVectorFilter> {
78
+ documentFilter?: MongoDBVectorFilter;
69
79
  }
70
- export { MongoDBQueryVectorParams }
71
- export { MongoDBQueryVectorParams as MongoDBQueryVectorParams_alias_1 }
72
80
 
73
81
  declare class MongoDBStore extends MastraStorage {
74
82
  #private;
@@ -207,7 +215,7 @@ declare interface MongoDBUpsertVectorParams extends UpsertVectorParams {
207
215
  export { MongoDBUpsertVectorParams }
208
216
  export { MongoDBUpsertVectorParams as MongoDBUpsertVectorParams_alias_1 }
209
217
 
210
- declare class MongoDBVector extends MastraVector {
218
+ declare class MongoDBVector extends MastraVector<MongoDBVectorFilter> {
211
219
  private client;
212
220
  private db;
213
221
  private collections;
@@ -271,4 +279,6 @@ declare class MongoDBVector extends MastraVector {
271
279
  export { MongoDBVector }
272
280
  export { MongoDBVector as MongoDBVector_alias_1 }
273
281
 
282
+ export declare type MongoDBVectorFilter = VectorFilter<keyof MongoDBOperatorValueMap, MongoDBOperatorValueMap, LogicalOperatorValueMap, MongoDBBlacklisted, VectorFieldValue | RegExp>;
283
+
274
284
  export { }
package/dist/index.cjs CHANGED
@@ -912,7 +912,15 @@ var MongoDBStore = class extends storage.MastraStorage {
912
912
  const collection = await this.getCollection(storage.TABLE_MESSAGES);
913
913
  const threadsCollection = await this.getCollection(storage.TABLE_THREADS);
914
914
  await Promise.all([
915
- collection.insertMany(messagesToInsert),
915
+ collection.bulkWrite(
916
+ messagesToInsert.map((msg) => ({
917
+ updateOne: {
918
+ filter: { id: msg.id },
919
+ update: { $set: msg },
920
+ upsert: true
921
+ }
922
+ }))
923
+ ),
916
924
  threadsCollection.updateOne({ id: threadId }, { $set: { updatedAt: /* @__PURE__ */ new Date() } })
917
925
  ]);
918
926
  const list = new agent.MessageList().add(messages, "memory");
@@ -938,15 +946,15 @@ var MongoDBStore = class extends storage.MastraStorage {
938
946
  const offset = page * perPage;
939
947
  const query = {};
940
948
  if (name) {
941
- query["name"] = `%${name}%`;
949
+ query["name"] = new RegExp(name);
942
950
  }
943
951
  if (scope) {
944
952
  query["scope"] = scope;
945
953
  }
946
954
  if (attributes) {
947
- Object.keys(attributes).forEach((key) => {
948
- query[`attributes.${key}`] = attributes[key];
949
- });
955
+ query["$and"] = Object.entries(attributes).map(([key, value]) => ({
956
+ attributes: new RegExp(`"${key}":"${value}"`)
957
+ }));
950
958
  }
951
959
  if (filters) {
952
960
  Object.entries(filters).forEach(([key, value]) => {
@@ -1220,10 +1228,19 @@ var MongoDBStore = class extends storage.MastraStorage {
1220
1228
  console.warn("Failed to parse test_info:", e);
1221
1229
  }
1222
1230
  }
1231
+ const resultValue = JSON.parse(row.result);
1232
+ if (!resultValue || typeof resultValue !== "object" || !("score" in resultValue)) {
1233
+ throw new error.MastraError({
1234
+ id: "STORAGE_MONGODB_STORE_INVALID_METRIC_FORMAT",
1235
+ text: `Invalid MetricResult format: ${JSON.stringify(resultValue)}`,
1236
+ domain: error.ErrorDomain.STORAGE,
1237
+ category: error.ErrorCategory.USER
1238
+ });
1239
+ }
1223
1240
  return {
1224
1241
  input: row.input,
1225
1242
  output: row.output,
1226
- result: row.result,
1243
+ result: resultValue,
1227
1244
  agentName: row.agent_name,
1228
1245
  metricName: row.metric_name,
1229
1246
  instructions: row.instructions,
package/dist/index.d.cts CHANGED
@@ -1,6 +1,5 @@
1
1
  export { MONGODB_PROMPT } from './_tsup-dts-rollup.cjs';
2
2
  export { MongoDBUpsertVectorParams } from './_tsup-dts-rollup.cjs';
3
- export { MongoDBQueryVectorParams } from './_tsup-dts-rollup.cjs';
4
3
  export { MongoDBIndexReadyParams } from './_tsup-dts-rollup.cjs';
5
4
  export { MongoDBVector } from './_tsup-dts-rollup.cjs';
6
5
  export { MongoDBConfig } from './_tsup-dts-rollup.cjs';
package/dist/index.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  export { MONGODB_PROMPT } from './_tsup-dts-rollup.js';
2
2
  export { MongoDBUpsertVectorParams } from './_tsup-dts-rollup.js';
3
- export { MongoDBQueryVectorParams } from './_tsup-dts-rollup.js';
4
3
  export { MongoDBIndexReadyParams } from './_tsup-dts-rollup.js';
5
4
  export { MongoDBVector } from './_tsup-dts-rollup.js';
6
5
  export { MongoDBConfig } from './_tsup-dts-rollup.js';
package/dist/index.js CHANGED
@@ -910,7 +910,15 @@ var MongoDBStore = class extends MastraStorage {
910
910
  const collection = await this.getCollection(TABLE_MESSAGES);
911
911
  const threadsCollection = await this.getCollection(TABLE_THREADS);
912
912
  await Promise.all([
913
- collection.insertMany(messagesToInsert),
913
+ collection.bulkWrite(
914
+ messagesToInsert.map((msg) => ({
915
+ updateOne: {
916
+ filter: { id: msg.id },
917
+ update: { $set: msg },
918
+ upsert: true
919
+ }
920
+ }))
921
+ ),
914
922
  threadsCollection.updateOne({ id: threadId }, { $set: { updatedAt: /* @__PURE__ */ new Date() } })
915
923
  ]);
916
924
  const list = new MessageList().add(messages, "memory");
@@ -936,15 +944,15 @@ var MongoDBStore = class extends MastraStorage {
936
944
  const offset = page * perPage;
937
945
  const query = {};
938
946
  if (name) {
939
- query["name"] = `%${name}%`;
947
+ query["name"] = new RegExp(name);
940
948
  }
941
949
  if (scope) {
942
950
  query["scope"] = scope;
943
951
  }
944
952
  if (attributes) {
945
- Object.keys(attributes).forEach((key) => {
946
- query[`attributes.${key}`] = attributes[key];
947
- });
953
+ query["$and"] = Object.entries(attributes).map(([key, value]) => ({
954
+ attributes: new RegExp(`"${key}":"${value}"`)
955
+ }));
948
956
  }
949
957
  if (filters) {
950
958
  Object.entries(filters).forEach(([key, value]) => {
@@ -1218,10 +1226,19 @@ var MongoDBStore = class extends MastraStorage {
1218
1226
  console.warn("Failed to parse test_info:", e);
1219
1227
  }
1220
1228
  }
1229
+ const resultValue = JSON.parse(row.result);
1230
+ if (!resultValue || typeof resultValue !== "object" || !("score" in resultValue)) {
1231
+ throw new MastraError({
1232
+ id: "STORAGE_MONGODB_STORE_INVALID_METRIC_FORMAT",
1233
+ text: `Invalid MetricResult format: ${JSON.stringify(resultValue)}`,
1234
+ domain: ErrorDomain.STORAGE,
1235
+ category: ErrorCategory.USER
1236
+ });
1237
+ }
1221
1238
  return {
1222
1239
  input: row.input,
1223
1240
  output: row.output,
1224
- result: row.result,
1241
+ result: resultValue,
1225
1242
  agentName: row.agent_name,
1226
1243
  metricName: row.metric_name,
1227
1244
  instructions: row.instructions,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/mongodb",
3
- "version": "0.11.1-alpha.1",
3
+ "version": "0.12.0-alpha.3",
4
4
  "description": "MongoDB provider for Mastra - includes vector store capabilities",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -27,16 +27,16 @@
27
27
  "devDependencies": {
28
28
  "@microsoft/api-extractor": "^7.52.8",
29
29
  "@types/node": "^20.19.0",
30
- "eslint": "^9.28.0",
30
+ "eslint": "^9.29.0",
31
31
  "tsup": "^8.5.0",
32
32
  "typescript": "^5.8.3",
33
33
  "vitest": "^3.2.3",
34
- "@internal/storage-test-utils": "0.0.9",
35
- "@mastra/core": "0.10.7-alpha.1",
36
- "@internal/lint": "0.0.13"
34
+ "@internal/lint": "0.0.13",
35
+ "@mastra/core": "0.10.7-alpha.3",
36
+ "@internal/storage-test-utils": "0.0.9"
37
37
  },
38
38
  "peerDependencies": {
39
- "@mastra/core": ">=0.10.4-0 <0.11.0"
39
+ "@mastra/core": ">=0.10.7-0 <0.11.0-0"
40
40
  },
41
41
  "scripts": {
42
42
  "build": "tsup src/index.ts --format esm,cjs --experimental-dts --clean --treeshake=smallest --splitting",
@@ -1,7 +1,13 @@
1
1
  import { randomUUID } from 'crypto';
2
2
  import type { MastraMessageV1, MastraMessageV2, MetricResult, WorkflowRunState } from '@mastra/core';
3
3
  import type { TABLE_NAMES } from '@mastra/core/storage';
4
- import { TABLE_EVALS, TABLE_MESSAGES, TABLE_THREADS, TABLE_WORKFLOW_SNAPSHOT } from '@mastra/core/storage';
4
+ import {
5
+ TABLE_EVALS,
6
+ TABLE_MESSAGES,
7
+ TABLE_THREADS,
8
+ TABLE_TRACES,
9
+ TABLE_WORKFLOW_SNAPSHOT,
10
+ } from '@mastra/core/storage';
5
11
  import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest';
6
12
  import type { MongoDBConfig } from './index';
7
13
  import { MongoDBStore } from './index';
@@ -23,6 +29,7 @@ class Test {
23
29
  await this.store.clearTable({ tableName: TABLE_MESSAGES });
24
30
  await this.store.clearTable({ tableName: TABLE_THREADS });
25
31
  await this.store.clearTable({ tableName: TABLE_EVALS });
32
+ await this.store.clearTable({ tableName: TABLE_TRACES });
26
33
  } catch (error) {
27
34
  // Ignore errors during table clearing
28
35
  console.warn('Error clearing tables:', error);
@@ -78,6 +85,7 @@ class Test {
78
85
  content: {
79
86
  format: 2,
80
87
  parts: [{ type: 'text', text: content }],
88
+ content: content,
81
89
  },
82
90
  createdAt: new Date(),
83
91
  resourceId,
@@ -354,6 +362,83 @@ describe('MongoDBStore', () => {
354
362
  expect((msg as any).content.parts).toEqual(messages[idx]!.content.parts);
355
363
  });
356
364
  });
365
+ it('should upsert messages: duplicate id+threadId results in update, not duplicate row', async () => {
366
+ const test = new Test(store).build();
367
+ await test.clearTables();
368
+ const thread = test.generateSampleThread();
369
+ await store.saveThread({ thread });
370
+ const baseMessage = test.generateSampleMessageV2({
371
+ threadId: thread.id,
372
+ content: 'Original',
373
+ resourceId: thread.resourceId,
374
+ });
375
+
376
+ // Insert the message for the first time
377
+ await store.saveMessages({ messages: [baseMessage], format: 'v2' });
378
+
379
+ // Insert again with the same id and threadId but different content
380
+ const updatedMessage = {
381
+ ...test.generateSampleMessageV2({
382
+ threadId: thread.id,
383
+ content: 'Updated',
384
+ resourceId: thread.resourceId,
385
+ }),
386
+ createdAt: baseMessage.createdAt,
387
+ id: baseMessage.id,
388
+ };
389
+
390
+ await store.saveMessages({ messages: [updatedMessage], format: 'v2' });
391
+
392
+ // Retrieve messages for the thread
393
+ const retrievedMessages = await store.getMessages({ threadId: thread.id, format: 'v2' });
394
+
395
+ // Only one message should exist for that id+threadId
396
+ expect(retrievedMessages.filter(m => m.id === baseMessage.id)).toHaveLength(1);
397
+
398
+ // The content should be the updated one
399
+ expect(retrievedMessages.find(m => m.id === baseMessage.id)?.content.content).toBe('Updated');
400
+ });
401
+
402
+ it('should upsert messages: duplicate id and different threadid', async () => {
403
+ const test = new Test(store).build();
404
+ const thread1 = test.generateSampleThread();
405
+ const thread2 = test.generateSampleThread();
406
+ await store.saveThread({ thread: thread1 });
407
+ await store.saveThread({ thread: thread2 });
408
+
409
+ const message = test.generateSampleMessageV2({
410
+ threadId: thread1.id,
411
+ content: 'Thread1 Content',
412
+ resourceId: thread1.resourceId,
413
+ });
414
+
415
+ // Insert message into thread1
416
+ await store.saveMessages({ messages: [message], format: 'v2' });
417
+
418
+ // Attempt to insert a message with the same id but different threadId
419
+ const conflictingMessage = {
420
+ ...test.generateSampleMessageV2({
421
+ threadId: thread2.id, // different thread
422
+ content: 'Thread2 Content',
423
+ resourceId: thread2.resourceId,
424
+ }),
425
+ createdAt: message.createdAt,
426
+ id: message.id,
427
+ };
428
+
429
+ // Save should move the message to the new thread
430
+ await store.saveMessages({ messages: [conflictingMessage], format: 'v2' });
431
+
432
+ // Retrieve messages for both threads
433
+ const thread1Messages = await store.getMessages({ threadId: thread1.id, format: 'v2' });
434
+ const thread2Messages = await store.getMessages({ threadId: thread2.id, format: 'v2' });
435
+
436
+ // Thread 1 should NOT have the message with that id
437
+ expect(thread1Messages.find(m => m.id === message.id)).toBeUndefined();
438
+
439
+ // Thread 2 should have the message with that id
440
+ expect(thread2Messages.find(m => m.id === message.id)?.content.content).toBe('Thread2 Content');
441
+ });
357
442
 
358
443
  // it('should retrieve messages w/ next/prev messages by message id + resource id', async () => {
359
444
  // const test = new Test(store).build();
@@ -846,6 +931,152 @@ describe('MongoDBStore', () => {
846
931
  });
847
932
  });
848
933
 
934
+ describe('Trace Operations', () => {
935
+ const sampleTrace = (
936
+ name: string,
937
+ scope: string,
938
+ startTime = Date.now(),
939
+ attributes: Record<string, string> = {},
940
+ ) => ({
941
+ id: `trace-${randomUUID()}`,
942
+ parentSpanId: `span-${randomUUID()}`,
943
+ traceId: `traceid-${randomUUID()}`,
944
+ name,
945
+ scope,
946
+ kind: 1,
947
+ startTime: startTime,
948
+ endTime: startTime + 100,
949
+ status: JSON.stringify({ code: 0 }),
950
+ attributes: JSON.stringify({ key: 'value', scopeAttr: scope, ...attributes }),
951
+ events: JSON.stringify([{ name: 'event1', timestamp: startTime + 50 }]),
952
+ links: JSON.stringify([]),
953
+ createdAt: new Date(startTime).toISOString(),
954
+ updatedAt: new Date(startTime).toISOString(),
955
+ });
956
+
957
+ beforeEach(async () => {
958
+ const test = new Test(store).build();
959
+ await test.clearTables();
960
+ });
961
+
962
+ it('should batch insert and retrieve traces', async () => {
963
+ const trace1 = sampleTrace('trace-op-1', 'scope-A');
964
+ const trace2 = sampleTrace('trace-op-2', 'scope-A', Date.now() + 10);
965
+ const trace3 = sampleTrace('trace-op-3', 'scope-B', Date.now() + 20);
966
+ const records = [trace1, trace2, trace3];
967
+
968
+ await store.batchInsert({ tableName: TABLE_TRACES, records });
969
+
970
+ const allTraces = await store.getTraces();
971
+ expect(allTraces.length).toBe(3);
972
+ });
973
+
974
+ it('should handle Date objects for createdAt/updatedAt fields in batchInsert', async () => {
975
+ const now = new Date();
976
+ const traceWithDateObjects = {
977
+ id: `trace-${randomUUID()}`,
978
+ parentSpanId: `span-${randomUUID()}`,
979
+ traceId: `traceid-${randomUUID()}`,
980
+ name: 'test-trace-with-dates',
981
+ scope: 'default-tracer',
982
+ kind: 1,
983
+ startTime: now.getTime(),
984
+ endTime: now.getTime() + 100,
985
+ status: JSON.stringify({ code: 0 }),
986
+ attributes: JSON.stringify({ key: 'value' }),
987
+ events: JSON.stringify([]),
988
+ links: JSON.stringify([]),
989
+ createdAt: now,
990
+ updatedAt: now,
991
+ };
992
+
993
+ await store.batchInsert({ tableName: TABLE_TRACES, records: [traceWithDateObjects] });
994
+
995
+ const allTraces = await store.getTraces({ name: 'test-trace-with-dates', page: 0, perPage: 10 });
996
+ expect(allTraces.length).toBe(1);
997
+ expect(allTraces[0].name).toBe('test-trace-with-dates');
998
+ });
999
+
1000
+ it('should retrieve traces filtered by name', async () => {
1001
+ const now = Date.now();
1002
+ const trace1 = sampleTrace('trace-filter-name', 'scope-X', now);
1003
+ const trace2 = sampleTrace('trace-filter-name', 'scope-Y', now + 10);
1004
+ const trace3 = sampleTrace('other-name', 'scope-X', now + 20);
1005
+ await store.batchInsert({ tableName: TABLE_TRACES, records: [trace1, trace2, trace3] });
1006
+
1007
+ const filteredTraces = await store.getTraces({ name: 'trace-filter-name', page: 0, perPage: 10 });
1008
+ expect(filteredTraces.length).toBe(2);
1009
+ expect(filteredTraces.every(t => t.name === 'trace-filter-name')).toBe(true);
1010
+ expect(filteredTraces[0].scope).toBe('scope-Y');
1011
+ expect(filteredTraces[1].scope).toBe('scope-X');
1012
+ });
1013
+
1014
+ it('should retrieve traces filtered by attributes', async () => {
1015
+ const now = Date.now();
1016
+ const trace1 = sampleTrace('trace-filter-attribute-A', 'scope-X', now, { componentName: 'component-TARGET' });
1017
+ const trace2 = sampleTrace('trace-filter-attribute-B', 'scope-Y', now + 10, { componentName: 'component-OTHER' });
1018
+ const trace3 = sampleTrace('trace-filter-attribute-C', 'scope-Z', now + 20, {
1019
+ componentName: 'component-TARGET',
1020
+ andFilterTest: 'TARGET',
1021
+ });
1022
+ await store.batchInsert({ tableName: TABLE_TRACES, records: [trace1, trace2, trace3] });
1023
+
1024
+ const filteredTraces = await store.getTraces({
1025
+ attributes: { componentName: 'component-TARGET' },
1026
+ page: 0,
1027
+ perPage: 10,
1028
+ });
1029
+ expect(filteredTraces.length).toBe(2);
1030
+ expect(filteredTraces[0].name).toBe('trace-filter-attribute-C');
1031
+ expect(filteredTraces[1].name).toBe('trace-filter-attribute-A');
1032
+
1033
+ const filteredTraces2 = await store.getTraces({
1034
+ attributes: { componentName: 'component-TARGET', andFilterTest: 'TARGET' },
1035
+ page: 0,
1036
+ perPage: 10,
1037
+ });
1038
+ expect(filteredTraces2.length).toBe(1);
1039
+ expect(filteredTraces2[0].name).toBe('trace-filter-attribute-C');
1040
+ });
1041
+
1042
+ it('should retrieve traces filtered by scope', async () => {
1043
+ const now = Date.now();
1044
+ const trace1 = sampleTrace('trace-filter-scope-A', 'scope-TARGET', now);
1045
+ const trace2 = sampleTrace('trace-filter-scope-B', 'scope-OTHER', now + 10);
1046
+ const trace3 = sampleTrace('trace-filter-scope-C', 'scope-TARGET', now + 20);
1047
+ await store.batchInsert({ tableName: TABLE_TRACES, records: [trace1, trace2, trace3] });
1048
+
1049
+ const filteredTraces = await store.getTraces({ scope: 'scope-TARGET', page: 0, perPage: 10 });
1050
+ expect(filteredTraces.length).toBe(2);
1051
+ expect(filteredTraces.every(t => t.scope === 'scope-TARGET')).toBe(true);
1052
+ expect(filteredTraces[0].name).toBe('trace-filter-scope-C');
1053
+ expect(filteredTraces[1].name).toBe('trace-filter-scope-A');
1054
+ });
1055
+
1056
+ it('should handle pagination for getTraces', async () => {
1057
+ const now = Date.now();
1058
+ const traceData = Array.from({ length: 5 }, (_, i) => sampleTrace('trace-page', `scope-page`, now + i * 10));
1059
+ await store.batchInsert({ tableName: TABLE_TRACES, records: traceData });
1060
+
1061
+ const page1 = await store.getTraces({ name: 'trace-page', page: 0, perPage: 2 });
1062
+ expect(page1.length).toBe(2);
1063
+ expect(page1[0]!.startTime).toBe(traceData[4]!.startTime);
1064
+ expect(page1[1]!.startTime).toBe(traceData[3]!.startTime);
1065
+
1066
+ const page2 = await store.getTraces({ name: 'trace-page', page: 1, perPage: 2 });
1067
+ expect(page2.length).toBe(2);
1068
+ expect(page2[0]!.startTime).toBe(traceData[2]!.startTime);
1069
+ expect(page2[1]!.startTime).toBe(traceData[1]!.startTime);
1070
+
1071
+ const page3 = await store.getTraces({ name: 'trace-page', page: 2, perPage: 2 });
1072
+ expect(page3.length).toBe(1);
1073
+ expect(page3[0]!.startTime).toBe(traceData[0]!.startTime);
1074
+
1075
+ const page4 = await store.getTraces({ name: 'trace-page', page: 3, perPage: 2 });
1076
+ expect(page4.length).toBe(0);
1077
+ });
1078
+ });
1079
+
849
1080
  describe('Eval Operations', () => {
850
1081
  it('should retrieve evals by agent name', async () => {
851
1082
  const test = new Test(store).build();
@@ -864,7 +1095,7 @@ describe('MongoDBStore', () => {
864
1095
  agent_name: liveEval.agentName,
865
1096
  input: liveEval.input,
866
1097
  output: liveEval.output,
867
- result: liveEval.result,
1098
+ result: JSON.stringify(liveEval.result),
868
1099
  metric_name: liveEval.metricName,
869
1100
  instructions: liveEval.instructions,
870
1101
  test_info: null,
@@ -881,7 +1112,7 @@ describe('MongoDBStore', () => {
881
1112
  agent_name: testEval.agentName,
882
1113
  input: testEval.input,
883
1114
  output: testEval.output,
884
- result: testEval.result,
1115
+ result: JSON.stringify(testEval.result),
885
1116
  metric_name: testEval.metricName,
886
1117
  instructions: testEval.instructions,
887
1118
  test_info: JSON.stringify(testEval.testInfo),
@@ -898,7 +1129,7 @@ describe('MongoDBStore', () => {
898
1129
  agent_name: otherAgentEval.agentName,
899
1130
  input: otherAgentEval.input,
900
1131
  output: otherAgentEval.output,
901
- result: otherAgentEval.result,
1132
+ result: JSON.stringify(otherAgentEval.result),
902
1133
  metric_name: otherAgentEval.metricName,
903
1134
  instructions: otherAgentEval.instructions,
904
1135
  test_info: null,
@@ -913,16 +1144,20 @@ describe('MongoDBStore', () => {
913
1144
  const allEvals = await store.getEvalsByAgentName(agentName);
914
1145
  expect(allEvals).toHaveLength(2);
915
1146
  expect(allEvals.map(e => e.runId)).toEqual(expect.arrayContaining([liveEval.runId, testEval.runId]));
1147
+ expect(allEvals[0]!.result.score).toEqual(liveEval.result.score);
1148
+ expect(allEvals[1]!.result.score).toEqual(testEval.result.score);
916
1149
 
917
1150
  // Test getting only live evals
918
1151
  const liveEvals = await store.getEvalsByAgentName(agentName, 'live');
919
1152
  expect(liveEvals).toHaveLength(1);
920
1153
  expect(liveEvals[0]!.runId).toBe(liveEval.runId);
1154
+ expect(liveEvals[0]!.result.score).toEqual(liveEval.result.score);
921
1155
 
922
1156
  // Test getting only test evals
923
1157
  const testEvals = await store.getEvalsByAgentName(agentName, 'test');
924
1158
  expect(testEvals).toHaveLength(1);
925
1159
  expect(testEvals[0]!.runId).toBe(testEval.runId);
1160
+ expect(testEvals[0]!.result.score).toEqual(testEval.result.score);
926
1161
  expect(testEvals[0]!.testInfo).toEqual(testEval.testInfo);
927
1162
 
928
1163
  // Test getting evals for non-existent agent
@@ -462,7 +462,15 @@ export class MongoDBStore extends MastraStorage {
462
462
  const threadsCollection = await this.getCollection(TABLE_THREADS);
463
463
 
464
464
  await Promise.all([
465
- collection.insertMany(messagesToInsert),
465
+ collection.bulkWrite(
466
+ messagesToInsert.map(msg => ({
467
+ updateOne: {
468
+ filter: { id: msg.id },
469
+ update: { $set: msg },
470
+ upsert: true,
471
+ },
472
+ })),
473
+ ),
466
474
  threadsCollection.updateOne({ id: threadId }, { $set: { updatedAt: new Date() } }),
467
475
  ]);
468
476
 
@@ -500,7 +508,7 @@ export class MongoDBStore extends MastraStorage {
500
508
 
501
509
  const query: any = {};
502
510
  if (name) {
503
- query['name'] = `%${name}%`;
511
+ query['name'] = new RegExp(name);
504
512
  }
505
513
 
506
514
  if (scope) {
@@ -508,9 +516,9 @@ export class MongoDBStore extends MastraStorage {
508
516
  }
509
517
 
510
518
  if (attributes) {
511
- Object.keys(attributes).forEach(key => {
512
- query[`attributes.${key}`] = attributes[key];
513
- });
519
+ query['$and'] = Object.entries(attributes).map(([key, value]) => ({
520
+ attributes: new RegExp(`\"${key}\":\"${value}\"`),
521
+ }));
514
522
  }
515
523
 
516
524
  if (filters) {
@@ -852,11 +860,20 @@ export class MongoDBStore extends MastraStorage {
852
860
  console.warn('Failed to parse test_info:', e);
853
861
  }
854
862
  }
863
+ const resultValue = JSON.parse(row.result as string);
864
+ if (!resultValue || typeof resultValue !== 'object' || !('score' in resultValue)) {
865
+ throw new MastraError({
866
+ id: 'STORAGE_MONGODB_STORE_INVALID_METRIC_FORMAT',
867
+ text: `Invalid MetricResult format: ${JSON.stringify(resultValue)}`,
868
+ domain: ErrorDomain.STORAGE,
869
+ category: ErrorCategory.USER,
870
+ });
871
+ }
855
872
 
856
873
  return {
857
874
  input: row.input as string,
858
875
  output: row.output as string,
859
- result: row.result as MetricResult,
876
+ result: resultValue as MetricResult,
860
877
  agentName: row.agent_name as string,
861
878
  metricName: row.metric_name as string,
862
879
  instructions: row.instructions as string,
@@ -1,5 +1,6 @@
1
1
  import { describe, it, expect, beforeEach } from 'vitest';
2
2
 
3
+ import type { MongoDBVectorFilter } from './filter';
3
4
  import { MongoDBFilterTranslator } from './filter';
4
5
 
5
6
  describe('MongoDBFilterTranslator', () => {
@@ -12,12 +13,12 @@ describe('MongoDBFilterTranslator', () => {
12
13
  // Basic Filter Operations
13
14
  describe('basic operations', () => {
14
15
  it('handles simple equality', () => {
15
- const filter = { field: 'value' };
16
+ const filter: MongoDBVectorFilter = { field: 'value' };
16
17
  expect(translator.translate(filter)).toEqual(filter);
17
18
  });
18
19
 
19
20
  it('handles comparison operators', () => {
20
- const filter = {
21
+ const filter: MongoDBVectorFilter = {
21
22
  age: { $gt: 25 },
22
23
  score: { $lte: 100 },
23
24
  };
@@ -25,7 +26,7 @@ describe('MongoDBFilterTranslator', () => {
25
26
  });
26
27
 
27
28
  it('handles valid multiple operators on same field', () => {
28
- const filter = {
29
+ const filter: MongoDBVectorFilter = {
29
30
  price: { $gt: 100, $lt: 200 },
30
31
  quantity: { $gte: 10, $lte: 20 },
31
32
  };
@@ -33,7 +34,7 @@ describe('MongoDBFilterTranslator', () => {
33
34
  });
34
35
 
35
36
  it('handles null values correctly', () => {
36
- const filter = {
37
+ const filter: MongoDBVectorFilter = {
37
38
  field: null,
38
39
  other: { $eq: null },
39
40
  };
@@ -41,7 +42,7 @@ describe('MongoDBFilterTranslator', () => {
41
42
  });
42
43
 
43
44
  it('handles boolean values correctly', () => {
44
- const filter = {
45
+ const filter: MongoDBVectorFilter = {
45
46
  active: true,
46
47
  deleted: false,
47
48
  status: { $eq: true },
@@ -53,7 +54,7 @@ describe('MongoDBFilterTranslator', () => {
53
54
  // Array Operations
54
55
  describe('array operations', () => {
55
56
  it('handles array operators', () => {
56
- const filter = {
57
+ const filter: MongoDBVectorFilter = {
57
58
  tags: { $all: ['tag1', 'tag2'] },
58
59
  categories: { $in: ['A', 'B'] },
59
60
  items: { $nin: ['item1', 'item2'] },
@@ -63,7 +64,7 @@ describe('MongoDBFilterTranslator', () => {
63
64
  });
64
65
 
65
66
  it('handles empty array values', () => {
66
- const filter = {
67
+ const filter: MongoDBVectorFilter = {
67
68
  tags: { $in: [] },
68
69
  categories: { $all: [] },
69
70
  };
@@ -71,14 +72,14 @@ describe('MongoDBFilterTranslator', () => {
71
72
  });
72
73
 
73
74
  it('handles nested array operators', () => {
74
- const filter = {
75
+ const filter: MongoDBVectorFilter = {
75
76
  $and: [{ tags: { $all: ['tag1', 'tag2'] } }, { 'nested.array': { $in: [1, 2, 3] } }],
76
77
  };
77
78
  expect(translator.translate(filter)).toEqual(filter);
78
79
  });
79
80
 
80
81
  it('handles $size operator', () => {
81
- const filter = {
82
+ const filter: MongoDBVectorFilter = {
82
83
  tags: { $size: 3 },
83
84
  };
84
85
  expect(translator.translate(filter)).toEqual(filter);
@@ -88,14 +89,14 @@ describe('MongoDBFilterTranslator', () => {
88
89
  // Logical Operators
89
90
  describe('logical operators', () => {
90
91
  it('handles logical operators', () => {
91
- const filter = {
92
+ const filter: MongoDBVectorFilter = {
92
93
  $or: [{ status: 'active' }, { age: { $gt: 25 } }],
93
94
  };
94
95
  expect(translator.translate(filter)).toEqual(filter);
95
96
  });
96
97
 
97
98
  it('handles $not operator', () => {
98
- const filter = {
99
+ const filter: MongoDBVectorFilter = {
99
100
  field: { $not: { $eq: 'value' } },
100
101
  $not: { field: 'value' },
101
102
  };
@@ -103,14 +104,14 @@ describe('MongoDBFilterTranslator', () => {
103
104
  });
104
105
 
105
106
  it('handles $nor operator', () => {
106
- const filter = {
107
+ const filter: MongoDBVectorFilter = {
107
108
  $nor: [{ status: 'deleted' }, { active: false }],
108
109
  };
109
110
  expect(translator.translate(filter)).toEqual(filter);
110
111
  });
111
112
 
112
113
  it('handles nested logical operators', () => {
113
- const filter = {
114
+ const filter: MongoDBVectorFilter = {
114
115
  $and: [
115
116
  { status: 'active' },
116
117
  { $or: [{ category: { $in: ['A', 'B'] } }, { $and: [{ price: { $gt: 100 } }, { stock: { $lt: 50 } }] }] },
@@ -120,7 +121,7 @@ describe('MongoDBFilterTranslator', () => {
120
121
  });
121
122
 
122
123
  it('handles empty conditions in logical operators', () => {
123
- const filter = {
124
+ const filter: MongoDBVectorFilter = {
124
125
  $and: [],
125
126
  $or: [{}],
126
127
  field: 'value',
@@ -175,7 +176,7 @@ describe('MongoDBFilterTranslator', () => {
175
176
  expect(() =>
176
177
  translator.translate({
177
178
  $or: [{ $in: ['value1', 'value2'] }],
178
- }),
179
+ } as any),
179
180
  ).toThrow(/Logical operators must contain field conditions/);
180
181
  });
181
182
 
@@ -187,7 +188,7 @@ describe('MongoDBFilterTranslator', () => {
187
188
  $or: [{ subfield: 'value1' }, { subfield: 'value2' }],
188
189
  },
189
190
  },
190
- }),
191
+ } as any),
191
192
  ).toThrow();
192
193
 
193
194
  expect(() =>
@@ -196,7 +197,7 @@ describe('MongoDBFilterTranslator', () => {
196
197
  $in: [
197
198
  {
198
199
  $and: [{ subfield: 'value1' }, { subfield: 'value2' }],
199
- },
200
+ } as any,
200
201
  ],
201
202
  },
202
203
  }),
@@ -211,7 +212,7 @@ describe('MongoDBFilterTranslator', () => {
211
212
  $or: [{ subfield: 'value1' }, { subfield: 'value2' }],
212
213
  },
213
214
  },
214
- }),
215
+ } as any),
215
216
  ).toThrow();
216
217
 
217
218
  expect(() =>
@@ -227,7 +228,7 @@ describe('MongoDBFilterTranslator', () => {
227
228
 
228
229
  it('throws error for $not if not an object', () => {
229
230
  expect(() => translator.translate({ $not: 'value' })).toThrow();
230
- expect(() => translator.translate({ $not: [{ field: 'value' }] })).toThrow();
231
+ expect(() => translator.translate({ $not: [{ field: 'value' }] } as any)).toThrow();
231
232
  });
232
233
 
233
234
  it('throws error for $not if empty', () => {
@@ -238,7 +239,7 @@ describe('MongoDBFilterTranslator', () => {
238
239
  // Nested Objects and Fields
239
240
  describe('nested objects and fields', () => {
240
241
  it('handles nested objects', () => {
241
- const filter = {
242
+ const filter: MongoDBVectorFilter = {
242
243
  'user.profile.age': { $gt: 25 },
243
244
  'user.status': 'active',
244
245
  };
@@ -246,7 +247,7 @@ describe('MongoDBFilterTranslator', () => {
246
247
  });
247
248
 
248
249
  it('handles deeply nested field paths', () => {
249
- const filter = {
250
+ const filter: MongoDBVectorFilter = {
250
251
  'user.profile.address.city': { $eq: 'New York' },
251
252
  'deep.nested.field': { $gt: 100 },
252
253
  };
@@ -295,13 +296,13 @@ describe('MongoDBFilterTranslator', () => {
295
296
  describe('special cases', () => {
296
297
  it('handles empty filters', () => {
297
298
  expect(translator.translate({})).toEqual({});
298
- expect(translator.translate(null as any)).toEqual(null);
299
- expect(translator.translate(undefined as any)).toEqual(undefined);
299
+ expect(translator.translate(null)).toEqual(null);
300
+ expect(translator.translate(undefined)).toEqual(undefined);
300
301
  });
301
302
 
302
303
  it('normalizes dates', () => {
303
304
  const date = new Date('2024-01-01');
304
- const filter = { timestamp: { $gt: date } };
305
+ const filter: MongoDBVectorFilter = { timestamp: { $gt: date } };
305
306
  expect(translator.translate(filter)).toEqual({
306
307
  timestamp: { $gt: date.toISOString() },
307
308
  });
@@ -321,14 +322,14 @@ describe('MongoDBFilterTranslator', () => {
321
322
  // Regex Support
322
323
  describe('regex support', () => {
323
324
  it('handles $regex operator', () => {
324
- const filter = {
325
+ const filter: MongoDBVectorFilter = {
325
326
  name: { $regex: '^test' },
326
327
  };
327
328
  expect(translator.translate(filter)).toEqual(filter);
328
329
  });
329
330
 
330
331
  it('handles RegExp objects', () => {
331
- const filter = {
332
+ const filter: MongoDBVectorFilter = {
332
333
  name: /^test/i,
333
334
  };
334
335
  // RegExp objects should be preserved
@@ -338,7 +339,7 @@ describe('MongoDBFilterTranslator', () => {
338
339
 
339
340
  describe('operator validation', () => {
340
341
  it('ensures all supported operator filters are accepted', () => {
341
- const supportedFilters = [
342
+ const supportedFilters: MongoDBVectorFilter[] = [
342
343
  // Basic comparison operators
343
344
  { field: { $eq: 'value' } },
344
345
  { field: { $ne: 'value' } },
@@ -382,12 +383,21 @@ describe('MongoDBFilterTranslator', () => {
382
383
  });
383
384
 
384
385
  it('throws on unsupported operators', () => {
385
- expect(() => translator.translate({ field: { $unknown: 'value' } })).toThrow('Unsupported operator: $unknown');
386
- expect(() => translator.translate({ $unknown: [{ field: 'value' }] })).toThrow('Unsupported operator: $unknown');
386
+ expect(() => translator.translate({ field: { $unknown: 'value' } } as any)).toThrow(
387
+ 'Unsupported operator: $unknown',
388
+ );
389
+ expect(() => translator.translate({ $unknown: [{ field: 'value' }] } as any)).toThrow(
390
+ 'Unsupported operator: $unknown',
391
+ );
387
392
  });
388
393
 
389
394
  it('throws error for non-logical operators at top level', () => {
390
- const invalidFilters = [{ $gt: 100 }, { $in: ['value1', 'value2'] }, { $exists: true }, { $regex: 'pattern' }];
395
+ const invalidFilters: any = [
396
+ { $gt: 100 },
397
+ { $in: ['value1', 'value2'] },
398
+ { $exists: true },
399
+ { $regex: 'pattern' },
400
+ ];
391
401
 
392
402
  invalidFilters.forEach(filter => {
393
403
  expect(() => translator.translate(filter)).toThrow(/Invalid top-level operator/);
@@ -1,12 +1,33 @@
1
1
  import { BaseFilterTranslator } from '@mastra/core/vector/filter';
2
- import type { FieldCondition, VectorFilter, OperatorSupport, QueryOperator } from '@mastra/core/vector/filter';
2
+ import type {
3
+ VectorFilter,
4
+ OperatorSupport,
5
+ QueryOperator,
6
+ OperatorValueMap,
7
+ LogicalOperatorValueMap,
8
+ BlacklistedRootOperators,
9
+ VectorFieldValue,
10
+ } from '@mastra/core/vector/filter';
11
+
12
+ type MongoDBOperatorValueMap = Omit<OperatorValueMap, '$options'> & {
13
+ $size: number;
14
+ };
15
+ type MongoDBBlacklisted = BlacklistedRootOperators | '$size';
16
+
17
+ export type MongoDBVectorFilter = VectorFilter<
18
+ keyof MongoDBOperatorValueMap,
19
+ MongoDBOperatorValueMap,
20
+ LogicalOperatorValueMap,
21
+ MongoDBBlacklisted,
22
+ VectorFieldValue | RegExp
23
+ >;
3
24
 
4
25
  /**
5
26
  * Translator for MongoDB filter queries.
6
27
  * Maintains MongoDB-compatible syntax while ensuring proper validation
7
28
  * and normalization of values.
8
29
  */
9
- export class MongoDBFilterTranslator extends BaseFilterTranslator {
30
+ export class MongoDBFilterTranslator extends BaseFilterTranslator<MongoDBVectorFilter> {
10
31
  protected override getSupportedOperators(): OperatorSupport {
11
32
  return {
12
33
  ...BaseFilterTranslator.DEFAULT_OPERATORS,
@@ -15,14 +36,14 @@ export class MongoDBFilterTranslator extends BaseFilterTranslator {
15
36
  };
16
37
  }
17
38
 
18
- translate(filter?: VectorFilter): any {
39
+ translate(filter?: MongoDBVectorFilter): any {
19
40
  if (this.isEmpty(filter)) return filter;
20
41
  this.validateFilter(filter);
21
42
 
22
43
  return this.translateNode(filter);
23
44
  }
24
45
 
25
- private translateNode(node: VectorFilter | FieldCondition): any {
46
+ private translateNode(node: MongoDBVectorFilter): any {
26
47
  // Handle primitive values and arrays
27
48
  if (this.isRegex(node)) {
28
49
  return node; // Return regex values as-is
@@ -1,4 +1,3 @@
1
- import type { VectorFilter } from '@mastra/core/vector/filter';
2
1
  import { vi, describe, it, expect, beforeAll, afterAll, test } from 'vitest';
3
2
  import { MongoDBVector } from './';
4
3
 
@@ -329,7 +328,7 @@ describe('MongoDBVector Integration Tests', () => {
329
328
  const results = await retryQuery({
330
329
  indexName: testIndexName2,
331
330
  queryVector: [1, 0, 0, 0],
332
- filter: null as unknown as VectorFilter,
331
+ filter: null,
333
332
  });
334
333
  const results2 = await retryQuery({
335
334
  indexName: testIndexName2,
@@ -11,20 +11,20 @@ import type {
11
11
  DeleteVectorParams,
12
12
  UpdateVectorParams,
13
13
  } from '@mastra/core/vector';
14
- import type { VectorFilter } from '@mastra/core/vector/filter';
15
14
  import { MongoClient } from 'mongodb';
16
15
  import type { MongoClientOptions, Document, Db, Collection } from 'mongodb';
17
16
  import { v4 as uuidv4 } from 'uuid';
18
17
 
19
18
  import { MongoDBFilterTranslator } from './filter';
19
+ import type { MongoDBVectorFilter } from './filter';
20
20
 
21
21
  // Define necessary types and interfaces
22
22
  export interface MongoDBUpsertVectorParams extends UpsertVectorParams {
23
23
  documents?: string[];
24
24
  }
25
25
 
26
- export interface MongoDBQueryVectorParams extends QueryVectorParams {
27
- documentFilter?: VectorFilter;
26
+ interface MongoDBQueryVectorParams extends QueryVectorParams<MongoDBVectorFilter> {
27
+ documentFilter?: MongoDBVectorFilter;
28
28
  }
29
29
 
30
30
  export interface MongoDBIndexReadyParams {
@@ -42,7 +42,7 @@ interface MongoDBDocument extends Document {
42
42
  [key: string]: any; // Index signature for additional properties
43
43
  }
44
44
  // The MongoDBVector class
45
- export class MongoDBVector extends MastraVector {
45
+ export class MongoDBVector extends MastraVector<MongoDBVectorFilter> {
46
46
  private client: MongoClient;
47
47
  private db: Db;
48
48
  private collections: Map<string, Collection<MongoDBDocument>>;
@@ -558,7 +558,7 @@ export class MongoDBVector extends MastraVector {
558
558
  await collection.updateOne({ _id: '__index_metadata__' }, { $set: { dimension } }, { upsert: true });
559
559
  }
560
560
 
561
- private transformFilter(filter?: VectorFilter): any {
561
+ private transformFilter(filter?: MongoDBVectorFilter) {
562
562
  const translator = new MongoDBFilterTranslator();
563
563
  if (!filter) return {};
564
564
  return translator.translate(filter);