@mastra/mongodb 1.13.0 → 1.14.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -6
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/docs-memory-working-memory.md +1 -1
- package/dist/docs/references/reference-vectors-mongodb.md +2 -0
- package/dist/index.cjs +153 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +153 -34
- package/dist/index.js.map +1 -1
- package/dist/vector/index.d.ts +55 -1
- package/dist/vector/index.d.ts.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import { skillSnapshotFieldValuesEqual } from '@mastra/core/storage/domains/skil
|
|
|
13
13
|
|
|
14
14
|
// package.json
|
|
15
15
|
var package_default = {
|
|
16
|
-
version: "1.
|
|
16
|
+
version: "1.14.0-alpha.0"};
|
|
17
17
|
var MongoDBFilterTranslator = class extends BaseFilterTranslator {
|
|
18
18
|
getSupportedOperators() {
|
|
19
19
|
return {
|
|
@@ -103,13 +103,38 @@ var MongoDBFilterTranslator = class extends BaseFilterTranslator {
|
|
|
103
103
|
};
|
|
104
104
|
|
|
105
105
|
// src/vector/index.ts
|
|
106
|
-
var MongoDBVector = class extends MastraVector {
|
|
106
|
+
var MongoDBVector = class _MongoDBVector extends MastraVector {
|
|
107
107
|
client;
|
|
108
108
|
db;
|
|
109
109
|
collections;
|
|
110
110
|
embeddingFieldName;
|
|
111
111
|
metadataFieldName = "metadata";
|
|
112
112
|
documentFieldName = "document";
|
|
113
|
+
/**
|
|
114
|
+
* Per-index cache of the filter paths declared in the Atlas vectorSearch index
|
|
115
|
+
* (e.g. `document`, `metadata.category`). Populated when an index is created in
|
|
116
|
+
* this process and lazily hydrated from the live index definition otherwise.
|
|
117
|
+
* `_id` is excluded because it is materialised separately by the fallback path.
|
|
118
|
+
*/
|
|
119
|
+
declaredFilterPaths = /* @__PURE__ */ new Map();
|
|
120
|
+
/**
|
|
121
|
+
* MongoDB query operators supported inside `$vectorSearch.filter`. Intentionally
|
|
122
|
+
* conservative: filters using any operator outside this set fall back to the
|
|
123
|
+
* `$match` pre-filter, which supports the full query language. Widening this set
|
|
124
|
+
* is safe only for operators Atlas Vector Search actually accepts in `filter`.
|
|
125
|
+
*/
|
|
126
|
+
static PUSHDOWN_OPERATORS = /* @__PURE__ */ new Set([
|
|
127
|
+
"$and",
|
|
128
|
+
"$or",
|
|
129
|
+
"$eq",
|
|
130
|
+
"$ne",
|
|
131
|
+
"$gt",
|
|
132
|
+
"$gte",
|
|
133
|
+
"$lt",
|
|
134
|
+
"$lte",
|
|
135
|
+
"$in",
|
|
136
|
+
"$nin"
|
|
137
|
+
]);
|
|
113
138
|
mongoMetricMap = {
|
|
114
139
|
cosine: "cosine",
|
|
115
140
|
euclidean: "euclidean",
|
|
@@ -172,7 +197,12 @@ var MongoDBVector = class extends MastraVector {
|
|
|
172
197
|
* queryable. Skipping that step on a real Atlas cluster may cause
|
|
173
198
|
* "index not found" or "index not ready" errors on subsequent operations.
|
|
174
199
|
*/
|
|
175
|
-
async createIndex({
|
|
200
|
+
async createIndex({
|
|
201
|
+
indexName,
|
|
202
|
+
dimension,
|
|
203
|
+
metric = "cosine",
|
|
204
|
+
filterFields
|
|
205
|
+
}) {
|
|
176
206
|
let mongoMetric;
|
|
177
207
|
try {
|
|
178
208
|
if (!Number.isInteger(dimension) || dimension <= 0) {
|
|
@@ -207,29 +237,33 @@ var MongoDBVector = class extends MastraVector {
|
|
|
207
237
|
const indexNameInternal = `${indexName}_vector_index`;
|
|
208
238
|
const embeddingField = this.embeddingFieldName;
|
|
209
239
|
const numDimensions = dimension;
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
path: "_id"
|
|
222
|
-
},
|
|
223
|
-
{
|
|
224
|
-
type: "filter",
|
|
225
|
-
path: "document"
|
|
226
|
-
}
|
|
227
|
-
]
|
|
240
|
+
const declaredMetadataPaths = this.buildDeclaredMetadataPaths(filterFields);
|
|
241
|
+
const fields = [
|
|
242
|
+
{
|
|
243
|
+
type: "vector",
|
|
244
|
+
path: embeddingField,
|
|
245
|
+
numDimensions,
|
|
246
|
+
similarity: mongoMetric
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
type: "filter",
|
|
250
|
+
path: "_id"
|
|
228
251
|
},
|
|
252
|
+
{
|
|
253
|
+
type: "filter",
|
|
254
|
+
path: this.documentFieldName
|
|
255
|
+
},
|
|
256
|
+
...declaredMetadataPaths.map((path) => ({ type: "filter", path }))
|
|
257
|
+
];
|
|
258
|
+
const vectorIndexCreated = await this.createSearchIndexIgnoringExisting(collection, {
|
|
259
|
+
definition: { fields },
|
|
229
260
|
name: indexNameInternal,
|
|
230
261
|
type: "vectorSearch"
|
|
231
262
|
});
|
|
232
|
-
|
|
263
|
+
if (vectorIndexCreated) {
|
|
264
|
+
this.declaredFilterPaths.set(indexName, /* @__PURE__ */ new Set([this.documentFieldName, ...declaredMetadataPaths]));
|
|
265
|
+
}
|
|
266
|
+
await this.createSearchIndexIgnoringExisting(collection, {
|
|
233
267
|
definition: {
|
|
234
268
|
mappings: {
|
|
235
269
|
dynamic: true
|
|
@@ -239,16 +273,30 @@ var MongoDBVector = class extends MastraVector {
|
|
|
239
273
|
type: "search"
|
|
240
274
|
});
|
|
241
275
|
} catch (error) {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
276
|
+
throw new MastraError(
|
|
277
|
+
{
|
|
278
|
+
id: createVectorErrorId("MONGODB", "CREATE_INDEX", "FAILED"),
|
|
279
|
+
domain: ErrorDomain.STORAGE,
|
|
280
|
+
category: ErrorCategory.THIRD_PARTY
|
|
281
|
+
},
|
|
282
|
+
error
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Creates an Atlas Search index, treating IndexAlreadyExists as a no-op so
|
|
288
|
+
* `createIndex` stays idempotent per index. Returns true when the index was
|
|
289
|
+
* actually created by this call.
|
|
290
|
+
*/
|
|
291
|
+
async createSearchIndexIgnoringExisting(collection, description) {
|
|
292
|
+
try {
|
|
293
|
+
await collection.createSearchIndex(description);
|
|
294
|
+
return true;
|
|
295
|
+
} catch (error) {
|
|
296
|
+
if (error?.codeName === "IndexAlreadyExists") {
|
|
297
|
+
return false;
|
|
251
298
|
}
|
|
299
|
+
throw error;
|
|
252
300
|
}
|
|
253
301
|
}
|
|
254
302
|
/**
|
|
@@ -390,9 +438,19 @@ var MongoDBVector = class extends MastraVector {
|
|
|
390
438
|
limit: Math.min(1e4, topK)
|
|
391
439
|
};
|
|
392
440
|
if (hasMetadataFilter) {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
441
|
+
let declaredPaths;
|
|
442
|
+
try {
|
|
443
|
+
declaredPaths = await this.getDeclaredFilterPaths(indexName);
|
|
444
|
+
} catch {
|
|
445
|
+
declaredPaths = /* @__PURE__ */ new Set();
|
|
446
|
+
}
|
|
447
|
+
if (this.canPushDownFilter(metadataFilter, declaredPaths)) {
|
|
448
|
+
vectorSearch.filter = documentFilter ? { $and: [metadataFilter, { [this.documentFieldName]: documentFilter }] } : metadataFilter;
|
|
449
|
+
} else {
|
|
450
|
+
const candidateIds = await collection.aggregate([{ $match: metadataFilter }, { $project: { _id: 1 } }]).map((doc) => doc._id).toArray();
|
|
451
|
+
if (candidateIds.length === 0) return [];
|
|
452
|
+
vectorSearch.filter = documentFilter ? { $and: [{ _id: { $in: candidateIds } }, { [this.documentFieldName]: documentFilter }] } : { _id: { $in: candidateIds } };
|
|
453
|
+
}
|
|
396
454
|
} else if (documentFilter) {
|
|
397
455
|
vectorSearch.filter = { [this.documentFieldName]: documentFilter };
|
|
398
456
|
}
|
|
@@ -509,6 +567,7 @@ var MongoDBVector = class extends MastraVector {
|
|
|
509
567
|
if (collection) {
|
|
510
568
|
await collection.drop();
|
|
511
569
|
this.collections.delete(indexName);
|
|
570
|
+
this.declaredFilterPaths.delete(indexName);
|
|
512
571
|
} else {
|
|
513
572
|
throw new Error(`Index (Collection) "${indexName}" does not exist`);
|
|
514
573
|
}
|
|
@@ -775,6 +834,66 @@ var MongoDBVector = class extends MastraVector {
|
|
|
775
834
|
}
|
|
776
835
|
}
|
|
777
836
|
}
|
|
837
|
+
/**
|
|
838
|
+
* Maps user-facing `filterFields` (bare metadata field names) to the fully
|
|
839
|
+
* qualified filter paths stored in the index, e.g. `category` → `metadata.category`.
|
|
840
|
+
* Blank entries are dropped and duplicates collapsed. A name already carrying the
|
|
841
|
+
* `metadata.` prefix is left untouched so callers can pass either form.
|
|
842
|
+
*/
|
|
843
|
+
buildDeclaredMetadataPaths(filterFields) {
|
|
844
|
+
if (!filterFields?.length) return [];
|
|
845
|
+
const paths = /* @__PURE__ */ new Set();
|
|
846
|
+
for (const field of filterFields) {
|
|
847
|
+
const trimmed = field?.trim();
|
|
848
|
+
if (!trimmed) continue;
|
|
849
|
+
paths.add(trimmed.startsWith(`${this.metadataFieldName}.`) ? trimmed : `${this.metadataFieldName}.${trimmed}`);
|
|
850
|
+
}
|
|
851
|
+
return [...paths];
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* Returns the set of filter paths declared in the vectorSearch index (e.g.
|
|
855
|
+
* `document`, `metadata.category`), excluding `_id`. Reads the live index
|
|
856
|
+
* definition once per index and caches the result. Used to decide whether a
|
|
857
|
+
* metadata filter can be pushed down into `$vectorSearch.filter`.
|
|
858
|
+
*/
|
|
859
|
+
async getDeclaredFilterPaths(indexName) {
|
|
860
|
+
const cached = this.declaredFilterPaths.get(indexName);
|
|
861
|
+
if (cached) return cached;
|
|
862
|
+
const collection = await this.getCollection(indexName, true);
|
|
863
|
+
const indexNameInternal = `${indexName}_vector_index`;
|
|
864
|
+
const indexInfo = await collection.listSearchIndexes().toArray();
|
|
865
|
+
const indexData = indexInfo.find((idx) => idx.name === indexNameInternal);
|
|
866
|
+
const paths = /* @__PURE__ */ new Set();
|
|
867
|
+
if (indexData?.status !== "READY") {
|
|
868
|
+
return paths;
|
|
869
|
+
}
|
|
870
|
+
for (const field of indexData.latestDefinition?.fields ?? []) {
|
|
871
|
+
if (field?.type === "filter" && typeof field.path === "string" && field.path !== "_id") {
|
|
872
|
+
paths.add(field.path);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
this.declaredFilterPaths.set(indexName, paths);
|
|
876
|
+
return paths;
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* True when `filter` can be passed directly to `$vectorSearch.filter`: every
|
|
880
|
+
* field path it references is a declared filter field and every operator is in
|
|
881
|
+
* {@link MongoDBVector.PUSHDOWN_OPERATORS}. Conservative by design — anything it
|
|
882
|
+
* can't prove safe returns false so the caller uses the `$match` pre-filter.
|
|
883
|
+
*/
|
|
884
|
+
canPushDownFilter(filter, declaredPaths) {
|
|
885
|
+
if (filter === null || typeof filter !== "object") return true;
|
|
886
|
+
if (Array.isArray(filter)) return filter.every((item) => this.canPushDownFilter(item, declaredPaths));
|
|
887
|
+
for (const [key, value] of Object.entries(filter)) {
|
|
888
|
+
if (key.startsWith("$")) {
|
|
889
|
+
if (!_MongoDBVector.PUSHDOWN_OPERATORS.has(key)) return false;
|
|
890
|
+
} else if (!declaredPaths.has(key)) {
|
|
891
|
+
return false;
|
|
892
|
+
}
|
|
893
|
+
if (!this.canPushDownFilter(value, declaredPaths)) return false;
|
|
894
|
+
}
|
|
895
|
+
return true;
|
|
896
|
+
}
|
|
778
897
|
transformFilter(filter) {
|
|
779
898
|
const translator = new MongoDBFilterTranslator();
|
|
780
899
|
if (!filter) return {};
|