@mastra/mongodb 0.11.1-alpha.1 → 0.11.1-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +14 -0
- package/dist/_tsup-dts-rollup.d.cts +17 -7
- package/dist/_tsup-dts-rollup.d.ts +17 -7
- package/dist/index.cjs +23 -6
- package/dist/index.d.cts +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +23 -6
- package/package.json +4 -4
- package/src/storage/index.test.ts +239 -4
- package/src/storage/index.ts +23 -6
- package/src/vector/filter.test.ts +40 -30
- package/src/vector/filter.ts +25 -4
- package/src/vector/index.test.ts +1 -2
- package/src/vector/index.ts +5 -5
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
|
|
2
|
-
> @mastra/mongodb@0.11.1-alpha.
|
|
2
|
+
> @mastra/mongodb@0.11.1-alpha.2 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
|
[34mCLI[39m Building entry: src/index.ts
|
|
6
6
|
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
7
7
|
[34mCLI[39m tsup v8.5.0
|
|
8
8
|
[34mTSC[39m Build start
|
|
9
|
-
[32mTSC[39m ⚡️ Build success in
|
|
9
|
+
[32mTSC[39m ⚡️ Build success in 10005ms
|
|
10
10
|
[34mDTS[39m Build start
|
|
11
11
|
[34mCLI[39m Target: es2022
|
|
12
12
|
Analysis will use the bundled TypeScript version 5.8.3
|
|
13
13
|
[36mWriting package typings: /home/runner/work/mastra/mastra/stores/mongodb/dist/_tsup-dts-rollup.d.ts[39m
|
|
14
14
|
Analysis will use the bundled TypeScript version 5.8.3
|
|
15
15
|
[36mWriting package typings: /home/runner/work/mastra/mastra/stores/mongodb/dist/_tsup-dts-rollup.d.cts[39m
|
|
16
|
-
[32mDTS[39m ⚡️ Build success in
|
|
16
|
+
[32mDTS[39m ⚡️ Build success in 11238ms
|
|
17
17
|
[34mCLI[39m Cleaning output folder
|
|
18
18
|
[34mESM[39m Build start
|
|
19
19
|
[34mCJS[39m Build start
|
|
20
|
-
[
|
|
21
|
-
[
|
|
22
|
-
[
|
|
23
|
-
[
|
|
20
|
+
[32mESM[39m [1mdist/index.js [22m[32m42.52 KB[39m
|
|
21
|
+
[32mESM[39m ⚡️ Build success in 1306ms
|
|
22
|
+
[32mCJS[39m [1mdist/index.cjs [22m[32m43.36 KB[39m
|
|
23
|
+
[32mCJS[39m ⚡️ Build success in 1315ms
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @mastra/mongodb
|
|
2
2
|
|
|
3
|
+
## 0.11.1-alpha.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- a2eceb2: fix: mongo storage trace query
|
|
8
|
+
- 0fb9d64: [MASTRA-4018] Update saveMessages in storage adapters to upsert messages
|
|
9
|
+
- 870f156: Parse eval result
|
|
10
|
+
- 144eb0b: [MASTRA-3669] Metadata Filter Types
|
|
11
|
+
- Updated dependencies [15e9d26]
|
|
12
|
+
- Updated dependencies [07d6d88]
|
|
13
|
+
- Updated dependencies [5d74aab]
|
|
14
|
+
- Updated dependencies [144eb0b]
|
|
15
|
+
- @mastra/core@0.10.7-alpha.2
|
|
16
|
+
|
|
3
17
|
## 0.11.1-alpha.1
|
|
4
18
|
|
|
5
19
|
### 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?:
|
|
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
|
|
68
|
-
|
|
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?:
|
|
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
|
|
68
|
-
|
|
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.
|
|
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"] =
|
|
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.
|
|
948
|
-
|
|
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:
|
|
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.
|
|
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"] =
|
|
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.
|
|
946
|
-
|
|
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:
|
|
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.
|
|
3
|
+
"version": "0.11.1-alpha.2",
|
|
4
4
|
"description": "MongoDB provider for Mastra - includes vector store capabilities",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@microsoft/api-extractor": "^7.52.8",
|
|
29
29
|
"@types/node": "^20.19.0",
|
|
30
|
-
"eslint": "^9.
|
|
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/lint": "0.0.13",
|
|
34
35
|
"@internal/storage-test-utils": "0.0.9",
|
|
35
|
-
"@mastra/core": "0.10.7-alpha.
|
|
36
|
-
"@internal/lint": "0.0.13"
|
|
36
|
+
"@mastra/core": "0.10.7-alpha.2"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@mastra/core": ">=0.10.4-0 <0.11.0"
|
|
@@ -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 {
|
|
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
|
package/src/storage/index.ts
CHANGED
|
@@ -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.
|
|
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'] =
|
|
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.
|
|
512
|
-
|
|
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:
|
|
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
|
|
299
|
-
expect(translator.translate(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(
|
|
386
|
-
|
|
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
|
|
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/);
|
package/src/vector/filter.ts
CHANGED
|
@@ -1,12 +1,33 @@
|
|
|
1
1
|
import { BaseFilterTranslator } from '@mastra/core/vector/filter';
|
|
2
|
-
import type {
|
|
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?:
|
|
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:
|
|
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
|
package/src/vector/index.test.ts
CHANGED
|
@@ -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
|
|
331
|
+
filter: null,
|
|
333
332
|
});
|
|
334
333
|
const results2 = await retryQuery({
|
|
335
334
|
indexName: testIndexName2,
|
package/src/vector/index.ts
CHANGED
|
@@ -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
|
-
|
|
27
|
-
documentFilter?:
|
|
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?:
|
|
561
|
+
private transformFilter(filter?: MongoDBVectorFilter) {
|
|
562
562
|
const translator = new MongoDBFilterTranslator();
|
|
563
563
|
if (!filter) return {};
|
|
564
564
|
return translator.translate(filter);
|