@mastra/mongodb 1.13.0 → 1.14.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 CHANGED
@@ -1,5 +1,53 @@
1
1
  # @mastra/mongodb
2
2
 
3
+ ## 1.14.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Added a `filterFields` option to `MongoDBVector.createIndex()`. Declaring the metadata fields you filter on lets Mastra register them as native filter fields in the Atlas vectorSearch index, so filtered queries are pushed straight into `$vectorSearch` instead of first materialising matching document `_id`s. This removes the 16 MB BSON ceiling that previously capped metadata-filtered queries at roughly 342,000 matching documents. ([#19006](https://github.com/mastra-ai/mastra/pull/19006))
8
+
9
+ ```ts
10
+ await vectorStore.createIndex({
11
+ indexName: 'my-index',
12
+ dimension: 1536,
13
+ metric: 'cosine',
14
+ filterFields: ['category', 'tenant_id'],
15
+ });
16
+ ```
17
+
18
+ Queries that filter only on declared fields (using operators Atlas Vector Search supports) take the fast path automatically. Filters that reference an undeclared field, or use an unsupported operator, keep working through the existing pre-filter.
19
+
20
+ ### Patch Changes
21
+
22
+ - Fixed `MongoDBVector.createIndex()` leaving index setup incomplete. Previously, when the vector search index already existed, the call silently skipped creating the companion full-text search index. Repeated or interrupted `createIndex()` calls now finish creating the remaining search index instead of leaving setup incomplete. ([#19006](https://github.com/mastra-ai/mastra/pull/19006))
23
+
24
+ - Updated dependencies [[`ec857fc`](https://github.com/mastra-ai/mastra/commit/ec857fc79c264b53b38e16478c789b7177f2ad59), [`d7385ad`](https://github.com/mastra-ai/mastra/commit/d7385ad9e88f9e4f33d15c0ec0bfebedde0cbc2e), [`41a5392`](https://github.com/mastra-ai/mastra/commit/41a5392d9f6c5e18d6b227f0fc0ddf49c50774e9), [`3d6e539`](https://github.com/mastra-ai/mastra/commit/3d6e539272eb2ea0407034605ee1906b3be06b39), [`1426af2`](https://github.com/mastra-ai/mastra/commit/1426af24975879c000d13ac75673f630fcc970c1), [`a40adeb`](https://github.com/mastra-ai/mastra/commit/a40adeb222b961a56a58af56a106106525721b74), [`8a0d145`](https://github.com/mastra-ai/mastra/commit/8a0d145aadbdf7278665aceaaec364b35dd9bd94), [`bd2f1d2`](https://github.com/mastra-ai/mastra/commit/bd2f1d274d05e60e2366f005ea0d94d5cea0d5ff), [`e1f2fae`](https://github.com/mastra-ai/mastra/commit/e1f2faebaf048c3d4c2e2c01d293767c195d5794), [`63aa799`](https://github.com/mastra-ai/mastra/commit/63aa799c6b44eacc7806cda6846b7c5bbee06b37), [`b7e79c3`](https://github.com/mastra-ai/mastra/commit/b7e79c3c02ac5cd415db34ba0975ceafc1464333), [`675fbff`](https://github.com/mastra-ai/mastra/commit/675fbff84d3274391b33e852f76083c38a5514e5), [`da009e1`](https://github.com/mastra-ai/mastra/commit/da009e1aacd89ed94b8d1b2af09c9d4fe7c4db49), [`3b77e77`](https://github.com/mastra-ai/mastra/commit/3b77e7704936522e4769d29de1b5ea6901f302bd), [`c7d30cd`](https://github.com/mastra-ai/mastra/commit/c7d30cd86009c407df91105591f03cd6e3d2854d), [`21a0eb8`](https://github.com/mastra-ai/mastra/commit/21a0eb86746ba0b703acea360d4f84c6a5a493f2), [`8b20926`](https://github.com/mastra-ai/mastra/commit/8b20926cd59e2ba3d66458e062fa0e6e2ada3e68), [`975295d`](https://github.com/mastra-ai/mastra/commit/975295d418552f0d46a59edfef4c3ee555f9930a), [`73db8db`](https://github.com/mastra-ai/mastra/commit/73db8db90d69ab6153c7942749f624db0d96952d), [`6b1bf3b`](https://github.com/mastra-ai/mastra/commit/6b1bf3b9494bd51aa8f654c68c9355d6046fa2a1), [`35c2181`](https://github.com/mastra-ai/mastra/commit/35c2181e6a50e47c90ba36260db7c9723d54696f), [`0a2c22c`](https://github.com/mastra-ai/mastra/commit/0a2c22c902604439ec490319e14c17f331e0c84c), [`4cfdd64`](https://github.com/mastra-ai/mastra/commit/4cfdd645794feaea0c4ea711e70ecdfbef0c5b8e), [`b75d749`](https://github.com/mastra-ai/mastra/commit/b75d749621ff5d17e86bcb4ee809d301fb4f7cf3), [`821648b`](https://github.com/mastra-ai/mastra/commit/821648bf2871ef840100c7bacbecf676010bd12a), [`de86fd7`](https://github.com/mastra-ai/mastra/commit/de86fd7119f0438381d1a642e3d258143c0b9c29), [`2745031`](https://github.com/mastra-ai/mastra/commit/2745031d1d4a4978f037092da371428c32e2842a), [`b4b7ea8`](https://github.com/mastra-ai/mastra/commit/b4b7ea8733f033fc441ea47ed03f6afb17ec2248), [`3a8024c`](https://github.com/mastra-ai/mastra/commit/3a8024ce615f8aa89479c0d71fe61d10bb0040be), [`35865a5`](https://github.com/mastra-ai/mastra/commit/35865a53e194aa9634d6a70a97010e7a6b9d58b1), [`74faf8b`](https://github.com/mastra-ai/mastra/commit/74faf8bd9c1018f2492653c06b1e25fc8300e9e6), [`ef03fbc`](https://github.com/mastra-ai/mastra/commit/ef03fbcc556bcbc04c9b3d06fab88771ecaa043c), [`675fbff`](https://github.com/mastra-ai/mastra/commit/675fbff84d3274391b33e852f76083c38a5514e5), [`70687f7`](https://github.com/mastra-ai/mastra/commit/70687f7e495a322a02070b4a67cb0c77a5ca91ec), [`1fadac4`](https://github.com/mastra-ai/mastra/commit/1fadac44537caeefe81f9f775ae2f2f3d94e9069), [`73db8db`](https://github.com/mastra-ai/mastra/commit/73db8db90d69ab6153c7942749f624db0d96952d), [`76b7181`](https://github.com/mastra-ai/mastra/commit/76b71810366e6d90b9d3973149d1c7ba3659ffb9), [`792ec9a`](https://github.com/mastra-ai/mastra/commit/792ec9a0869bab8274cf5e0ed2840738737a1607), [`712b864`](https://github.com/mastra-ai/mastra/commit/712b864aa1ed12b14c54390ec17b69de163c37f7), [`85e4fb5`](https://github.com/mastra-ai/mastra/commit/85e4fb50087a81c74df3a762f53b56373db0b912), [`0c0e8d7`](https://github.com/mastra-ai/mastra/commit/0c0e8d7becd4d1445c656b78d5d845f606c1ff9d), [`a7bbe77`](https://github.com/mastra-ai/mastra/commit/a7bbe773577f60bc4761b534ef7ec6b476332dad), [`72e437c`](https://github.com/mastra-ai/mastra/commit/72e437c515942c80b9def5b026e0bdee61b469d9), [`8f7a5de`](https://github.com/mastra-ai/mastra/commit/8f7a5dedc246cdc938bb65516703cf9b27b03756), [`a7bbe77`](https://github.com/mastra-ai/mastra/commit/a7bbe773577f60bc4761b534ef7ec6b476332dad), [`11f6cd9`](https://github.com/mastra-ai/mastra/commit/11f6cd96fe42582403416608beb212cc1a2cc79e), [`ef03c0c`](https://github.com/mastra-ai/mastra/commit/ef03c0cfc62367a458e4cc56462e2148b35681c5), [`4fb4d88`](https://github.com/mastra-ai/mastra/commit/4fb4d881bc107acee13890ad4d78661016c510ed), [`4e68363`](https://github.com/mastra-ai/mastra/commit/4e683634f94ebd062d26a3bb6093a8dfc7263d37), [`c328769`](https://github.com/mastra-ai/mastra/commit/c3287698ff8ef98dba86d415faa566fa3e5f4d56), [`9f7c67a`](https://github.com/mastra-ai/mastra/commit/9f7c67abeeb52c41c51a9b5edee60b62afe7cd8d), [`3b65e68`](https://github.com/mastra-ai/mastra/commit/3b65e68d7f1c771c7a70eea42d83fefdd28cad88), [`4eba27a`](https://github.com/mastra-ai/mastra/commit/4eba27adcf60f991df0e62f94b3e75b4e67f3b4b), [`c701be3`](https://github.com/mastra-ai/mastra/commit/c701be32d7d9aa94a66da8c6cc38dcac6856f464), [`db650ce`](https://github.com/mastra-ai/mastra/commit/db650ce490348914e85b93651d83acdf8f2a4c31), [`232fcbc`](https://github.com/mastra-ai/mastra/commit/232fcbc14fce625dd672ba043329c0b732c62be2), [`6354eeb`](https://github.com/mastra-ai/mastra/commit/6354eeb32efa9f5f68f51dda394e90e2ee76f1fb), [`a8799bb`](https://github.com/mastra-ai/mastra/commit/a8799bb8e44f4a60d01e4e2acd3448ff80bf14f8), [`3d6e539`](https://github.com/mastra-ai/mastra/commit/3d6e539272eb2ea0407034605ee1906b3be06b39), [`e3868e2`](https://github.com/mastra-ai/mastra/commit/e3868e22babfffd0133771669ca724501c2dd58e), [`9251370`](https://github.com/mastra-ai/mastra/commit/9251370ad413af464aa22d7566338bec5613e8de), [`3491666`](https://github.com/mastra-ai/mastra/commit/34916663c4fdd43b48c21f4ab2d5fb6dcccc94f9), [`c0bec73`](https://github.com/mastra-ai/mastra/commit/c0bec732c93d1a22ae5e51ed66cf8cacca8bd6a6)]:
25
+ - @mastra/core@1.52.0
26
+
27
+ ## 1.14.0-alpha.0
28
+
29
+ ### Minor Changes
30
+
31
+ - Added a `filterFields` option to `MongoDBVector.createIndex()`. Declaring the metadata fields you filter on lets Mastra register them as native filter fields in the Atlas vectorSearch index, so filtered queries are pushed straight into `$vectorSearch` instead of first materialising matching document `_id`s. This removes the 16 MB BSON ceiling that previously capped metadata-filtered queries at roughly 342,000 matching documents. ([#19006](https://github.com/mastra-ai/mastra/pull/19006))
32
+
33
+ ```ts
34
+ await vectorStore.createIndex({
35
+ indexName: 'my-index',
36
+ dimension: 1536,
37
+ metric: 'cosine',
38
+ filterFields: ['category', 'tenant_id'],
39
+ });
40
+ ```
41
+
42
+ Queries that filter only on declared fields (using operators Atlas Vector Search supports) take the fast path automatically. Filters that reference an undeclared field, or use an unsupported operator, keep working through the existing pre-filter.
43
+
44
+ ### Patch Changes
45
+
46
+ - Fixed `MongoDBVector.createIndex()` leaving index setup incomplete. Previously, when the vector search index already existed, the call silently skipped creating the companion full-text search index. Repeated or interrupted `createIndex()` calls now finish creating the remaining search index instead of leaving setup incomplete. ([#19006](https://github.com/mastra-ai/mastra/pull/19006))
47
+
48
+ - Updated dependencies [[`ec857fc`](https://github.com/mastra-ai/mastra/commit/ec857fc79c264b53b38e16478c789b7177f2ad59), [`e1f2fae`](https://github.com/mastra-ai/mastra/commit/e1f2faebaf048c3d4c2e2c01d293767c195d5794), [`63aa799`](https://github.com/mastra-ai/mastra/commit/63aa799c6b44eacc7806cda6846b7c5bbee06b37), [`73db8db`](https://github.com/mastra-ai/mastra/commit/73db8db90d69ab6153c7942749f624db0d96952d), [`73db8db`](https://github.com/mastra-ai/mastra/commit/73db8db90d69ab6153c7942749f624db0d96952d), [`76b7181`](https://github.com/mastra-ai/mastra/commit/76b71810366e6d90b9d3973149d1c7ba3659ffb9), [`0c0e8d7`](https://github.com/mastra-ai/mastra/commit/0c0e8d7becd4d1445c656b78d5d845f606c1ff9d), [`9f7c67a`](https://github.com/mastra-ai/mastra/commit/9f7c67abeeb52c41c51a9b5edee60b62afe7cd8d), [`3b65e68`](https://github.com/mastra-ai/mastra/commit/3b65e68d7f1c771c7a70eea42d83fefdd28cad88), [`e3868e2`](https://github.com/mastra-ai/mastra/commit/e3868e22babfffd0133771669ca724501c2dd58e)]:
49
+ - @mastra/core@1.52.0-alpha.5
50
+
3
51
  ## 1.13.0
4
52
 
5
53
  ### Minor Changes
@@ -686,9 +734,7 @@
686
734
  **Example**
687
735
 
688
736
  ```ts
689
- const storage = new LibSQLStore({
690
- /* config */
691
- });
737
+ const storage = new LibSQLStore({/* config */});
692
738
  const favorites = await storage.getStore('favorites');
693
739
 
694
740
  await favorites?.favorite({
@@ -714,9 +760,7 @@
714
760
  **Example**
715
761
 
716
762
  ```ts
717
- const storage = new LibSQLStore({
718
- /* config */
719
- });
763
+ const storage = new LibSQLStore({/* config */});
720
764
  const favorites = await storage.getStore('favorites');
721
765
 
722
766
  await favorites?.favorite({
@@ -3,7 +3,7 @@ name: mastra-mongodb
3
3
  description: Documentation for @mastra/mongodb. Use when working with @mastra/mongodb APIs, configuration, or implementation.
4
4
  metadata:
5
5
  package: "@mastra/mongodb"
6
- version: "1.13.0"
6
+ version: "1.14.0"
7
7
  ---
8
8
 
9
9
  ## When to use
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.13.0",
2
+ "version": "1.14.0",
3
3
  "package": "@mastra/mongodb",
4
4
  "exports": {},
5
5
  "modules": {}
@@ -403,7 +403,7 @@ By default, working memory reaches the model as part of the system message. You
403
403
 
404
404
  ```typescript
405
405
  const memory = new Memory({
406
- storage: new LibSQLStore({ url: 'file:./mastra.db' }),
406
+ storage: new LibSQLStore({ id: 'mastra-storage', url: 'file:./mastra.db' }),
407
407
  options: {
408
408
  workingMemory: {
409
409
  enabled: true,
@@ -89,6 +89,8 @@ Creates a new vector index (collection) in MongoDB.
89
89
 
90
90
  **metric** (`'cosine' | 'euclidean' | 'dotproduct'`): Distance metric for similarity search (Default: `cosine`)
91
91
 
92
+ **filterFields** (`string[]`): Metadata field names to declare as filter fields in the Atlas vectorSearch index (registered as metadata.\<field>). Queries that filter only on declared fields are pushed directly into $vectorSearch instead of pre-filtering candidate \_ids, avoiding the 16 MB BSON limit on large result sets. Filters that reference an undeclared field, or use an operator $vectorSearch does not support, fall back to the pre-filter automatically.
93
+
92
94
  ### `waitForIndexReady()`
93
95
 
94
96
  Waits for an index to become ready after creation. Useful when you need to ensure an index is ready before performing operations.
package/dist/index.cjs CHANGED
@@ -15,7 +15,7 @@ var skills = require('@mastra/core/storage/domains/skills');
15
15
 
16
16
  // package.json
17
17
  var package_default = {
18
- version: "1.13.0"};
18
+ version: "1.14.0"};
19
19
  var MongoDBFilterTranslator = class extends filter.BaseFilterTranslator {
20
20
  getSupportedOperators() {
21
21
  return {
@@ -105,13 +105,38 @@ var MongoDBFilterTranslator = class extends filter.BaseFilterTranslator {
105
105
  };
106
106
 
107
107
  // src/vector/index.ts
108
- var MongoDBVector = class extends vector.MastraVector {
108
+ var MongoDBVector = class _MongoDBVector extends vector.MastraVector {
109
109
  client;
110
110
  db;
111
111
  collections;
112
112
  embeddingFieldName;
113
113
  metadataFieldName = "metadata";
114
114
  documentFieldName = "document";
115
+ /**
116
+ * Per-index cache of the filter paths declared in the Atlas vectorSearch index
117
+ * (e.g. `document`, `metadata.category`). Populated when an index is created in
118
+ * this process and lazily hydrated from the live index definition otherwise.
119
+ * `_id` is excluded because it is materialised separately by the fallback path.
120
+ */
121
+ declaredFilterPaths = /* @__PURE__ */ new Map();
122
+ /**
123
+ * MongoDB query operators supported inside `$vectorSearch.filter`. Intentionally
124
+ * conservative: filters using any operator outside this set fall back to the
125
+ * `$match` pre-filter, which supports the full query language. Widening this set
126
+ * is safe only for operators Atlas Vector Search actually accepts in `filter`.
127
+ */
128
+ static PUSHDOWN_OPERATORS = /* @__PURE__ */ new Set([
129
+ "$and",
130
+ "$or",
131
+ "$eq",
132
+ "$ne",
133
+ "$gt",
134
+ "$gte",
135
+ "$lt",
136
+ "$lte",
137
+ "$in",
138
+ "$nin"
139
+ ]);
115
140
  mongoMetricMap = {
116
141
  cosine: "cosine",
117
142
  euclidean: "euclidean",
@@ -174,7 +199,12 @@ var MongoDBVector = class extends vector.MastraVector {
174
199
  * queryable. Skipping that step on a real Atlas cluster may cause
175
200
  * "index not found" or "index not ready" errors on subsequent operations.
176
201
  */
177
- async createIndex({ indexName, dimension, metric = "cosine" }) {
202
+ async createIndex({
203
+ indexName,
204
+ dimension,
205
+ metric = "cosine",
206
+ filterFields
207
+ }) {
178
208
  let mongoMetric;
179
209
  try {
180
210
  if (!Number.isInteger(dimension) || dimension <= 0) {
@@ -209,29 +239,33 @@ var MongoDBVector = class extends vector.MastraVector {
209
239
  const indexNameInternal = `${indexName}_vector_index`;
210
240
  const embeddingField = this.embeddingFieldName;
211
241
  const numDimensions = dimension;
212
- await collection.createSearchIndex({
213
- definition: {
214
- fields: [
215
- {
216
- type: "vector",
217
- path: embeddingField,
218
- numDimensions,
219
- similarity: mongoMetric
220
- },
221
- {
222
- type: "filter",
223
- path: "_id"
224
- },
225
- {
226
- type: "filter",
227
- path: "document"
228
- }
229
- ]
242
+ const declaredMetadataPaths = this.buildDeclaredMetadataPaths(filterFields);
243
+ const fields = [
244
+ {
245
+ type: "vector",
246
+ path: embeddingField,
247
+ numDimensions,
248
+ similarity: mongoMetric
249
+ },
250
+ {
251
+ type: "filter",
252
+ path: "_id"
230
253
  },
254
+ {
255
+ type: "filter",
256
+ path: this.documentFieldName
257
+ },
258
+ ...declaredMetadataPaths.map((path) => ({ type: "filter", path }))
259
+ ];
260
+ const vectorIndexCreated = await this.createSearchIndexIgnoringExisting(collection, {
261
+ definition: { fields },
231
262
  name: indexNameInternal,
232
263
  type: "vectorSearch"
233
264
  });
234
- await collection.createSearchIndex({
265
+ if (vectorIndexCreated) {
266
+ this.declaredFilterPaths.set(indexName, /* @__PURE__ */ new Set([this.documentFieldName, ...declaredMetadataPaths]));
267
+ }
268
+ await this.createSearchIndexIgnoringExisting(collection, {
235
269
  definition: {
236
270
  mappings: {
237
271
  dynamic: true
@@ -241,16 +275,30 @@ var MongoDBVector = class extends vector.MastraVector {
241
275
  type: "search"
242
276
  });
243
277
  } catch (error$1) {
244
- if (error$1.codeName !== "IndexAlreadyExists") {
245
- throw new error.MastraError(
246
- {
247
- id: storage.createVectorErrorId("MONGODB", "CREATE_INDEX", "FAILED"),
248
- domain: error.ErrorDomain.STORAGE,
249
- category: error.ErrorCategory.THIRD_PARTY
250
- },
251
- error$1
252
- );
278
+ throw new error.MastraError(
279
+ {
280
+ id: storage.createVectorErrorId("MONGODB", "CREATE_INDEX", "FAILED"),
281
+ domain: error.ErrorDomain.STORAGE,
282
+ category: error.ErrorCategory.THIRD_PARTY
283
+ },
284
+ error$1
285
+ );
286
+ }
287
+ }
288
+ /**
289
+ * Creates an Atlas Search index, treating IndexAlreadyExists as a no-op so
290
+ * `createIndex` stays idempotent per index. Returns true when the index was
291
+ * actually created by this call.
292
+ */
293
+ async createSearchIndexIgnoringExisting(collection, description) {
294
+ try {
295
+ await collection.createSearchIndex(description);
296
+ return true;
297
+ } catch (error) {
298
+ if (error?.codeName === "IndexAlreadyExists") {
299
+ return false;
253
300
  }
301
+ throw error;
254
302
  }
255
303
  }
256
304
  /**
@@ -392,9 +440,19 @@ var MongoDBVector = class extends vector.MastraVector {
392
440
  limit: Math.min(1e4, topK)
393
441
  };
394
442
  if (hasMetadataFilter) {
395
- const candidateIds = await collection.aggregate([{ $match: metadataFilter }, { $project: { _id: 1 } }]).map((doc) => doc._id).toArray();
396
- if (candidateIds.length === 0) return [];
397
- vectorSearch.filter = documentFilter ? { $and: [{ _id: { $in: candidateIds } }, { [this.documentFieldName]: documentFilter }] } : { _id: { $in: candidateIds } };
443
+ let declaredPaths;
444
+ try {
445
+ declaredPaths = await this.getDeclaredFilterPaths(indexName);
446
+ } catch {
447
+ declaredPaths = /* @__PURE__ */ new Set();
448
+ }
449
+ if (this.canPushDownFilter(metadataFilter, declaredPaths)) {
450
+ vectorSearch.filter = documentFilter ? { $and: [metadataFilter, { [this.documentFieldName]: documentFilter }] } : metadataFilter;
451
+ } else {
452
+ const candidateIds = await collection.aggregate([{ $match: metadataFilter }, { $project: { _id: 1 } }]).map((doc) => doc._id).toArray();
453
+ if (candidateIds.length === 0) return [];
454
+ vectorSearch.filter = documentFilter ? { $and: [{ _id: { $in: candidateIds } }, { [this.documentFieldName]: documentFilter }] } : { _id: { $in: candidateIds } };
455
+ }
398
456
  } else if (documentFilter) {
399
457
  vectorSearch.filter = { [this.documentFieldName]: documentFilter };
400
458
  }
@@ -511,6 +569,7 @@ var MongoDBVector = class extends vector.MastraVector {
511
569
  if (collection) {
512
570
  await collection.drop();
513
571
  this.collections.delete(indexName);
572
+ this.declaredFilterPaths.delete(indexName);
514
573
  } else {
515
574
  throw new Error(`Index (Collection) "${indexName}" does not exist`);
516
575
  }
@@ -777,6 +836,66 @@ var MongoDBVector = class extends vector.MastraVector {
777
836
  }
778
837
  }
779
838
  }
839
+ /**
840
+ * Maps user-facing `filterFields` (bare metadata field names) to the fully
841
+ * qualified filter paths stored in the index, e.g. `category` → `metadata.category`.
842
+ * Blank entries are dropped and duplicates collapsed. A name already carrying the
843
+ * `metadata.` prefix is left untouched so callers can pass either form.
844
+ */
845
+ buildDeclaredMetadataPaths(filterFields) {
846
+ if (!filterFields?.length) return [];
847
+ const paths = /* @__PURE__ */ new Set();
848
+ for (const field of filterFields) {
849
+ const trimmed = field?.trim();
850
+ if (!trimmed) continue;
851
+ paths.add(trimmed.startsWith(`${this.metadataFieldName}.`) ? trimmed : `${this.metadataFieldName}.${trimmed}`);
852
+ }
853
+ return [...paths];
854
+ }
855
+ /**
856
+ * Returns the set of filter paths declared in the vectorSearch index (e.g.
857
+ * `document`, `metadata.category`), excluding `_id`. Reads the live index
858
+ * definition once per index and caches the result. Used to decide whether a
859
+ * metadata filter can be pushed down into `$vectorSearch.filter`.
860
+ */
861
+ async getDeclaredFilterPaths(indexName) {
862
+ const cached = this.declaredFilterPaths.get(indexName);
863
+ if (cached) return cached;
864
+ const collection = await this.getCollection(indexName, true);
865
+ const indexNameInternal = `${indexName}_vector_index`;
866
+ const indexInfo = await collection.listSearchIndexes().toArray();
867
+ const indexData = indexInfo.find((idx) => idx.name === indexNameInternal);
868
+ const paths = /* @__PURE__ */ new Set();
869
+ if (indexData?.status !== "READY") {
870
+ return paths;
871
+ }
872
+ for (const field of indexData.latestDefinition?.fields ?? []) {
873
+ if (field?.type === "filter" && typeof field.path === "string" && field.path !== "_id") {
874
+ paths.add(field.path);
875
+ }
876
+ }
877
+ this.declaredFilterPaths.set(indexName, paths);
878
+ return paths;
879
+ }
880
+ /**
881
+ * True when `filter` can be passed directly to `$vectorSearch.filter`: every
882
+ * field path it references is a declared filter field and every operator is in
883
+ * {@link MongoDBVector.PUSHDOWN_OPERATORS}. Conservative by design — anything it
884
+ * can't prove safe returns false so the caller uses the `$match` pre-filter.
885
+ */
886
+ canPushDownFilter(filter, declaredPaths) {
887
+ if (filter === null || typeof filter !== "object") return true;
888
+ if (Array.isArray(filter)) return filter.every((item) => this.canPushDownFilter(item, declaredPaths));
889
+ for (const [key, value] of Object.entries(filter)) {
890
+ if (key.startsWith("$")) {
891
+ if (!_MongoDBVector.PUSHDOWN_OPERATORS.has(key)) return false;
892
+ } else if (!declaredPaths.has(key)) {
893
+ return false;
894
+ }
895
+ if (!this.canPushDownFilter(value, declaredPaths)) return false;
896
+ }
897
+ return true;
898
+ }
780
899
  transformFilter(filter) {
781
900
  const translator = new MongoDBFilterTranslator();
782
901
  if (!filter) return {};