@mastra/mongodb 1.18.10 → 1.19.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/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import { MessageList } from "@mastra/core/agent";
9
9
  import { saveScorePayloadSchema } from "@mastra/core/evals";
10
10
  import { skillSnapshotFieldValuesEqual } from "@mastra/core/storage/domains/skills";
11
11
  //#region package.json
12
- var version = "1.18.10";
12
+ var version = "1.19.0";
13
13
  //#endregion
14
14
  //#region src/vector/filter.ts
15
15
  /**
@@ -81,6 +81,10 @@ var MongoDBFilterTranslator = class extends BaseFilterTranslator {
81
81
  };
82
82
  //#endregion
83
83
  //#region src/vector/index.ts
84
+ /** Name an index's embedding mode for a conflict message. */
85
+ function describeEmbedding(config) {
86
+ return config ? `autoEmbed (path "${config.path}", model "${config.model}")` : "client-side vectors";
87
+ }
84
88
  var MongoDBVector = class MongoDBVector extends MastraVector {
85
89
  client;
86
90
  db;
@@ -185,7 +189,8 @@ var MongoDBVector = class MongoDBVector extends MastraVector {
185
189
  searchIndexName: persisted.searchIndexName ?? `${indexName}_vector_index`,
186
190
  isByo,
187
191
  allowWrites: isByo ? persisted.allowWrites ?? false : true,
188
- textSearchIndexName: persisted.textSearchIndexName
192
+ textSearchIndexName: persisted.textSearchIndexName,
193
+ autoEmbed: persisted.autoEmbed
189
194
  };
190
195
  this.indexTargets.set(indexName, target);
191
196
  return {
@@ -288,9 +293,17 @@ var MongoDBVector = class MongoDBVector extends MastraVector {
288
293
  * Persist (upsert) a logical-index target so it survives a process restart. Idempotent
289
294
  * createIndex refreshes it; a partial update (e.g. only the text-search-index name) merges
290
295
  * into the existing entry.
296
+ *
297
+ * `claim` narrows the upsert to entries the caller considers compatible. Because the filter
298
+ * still pins `_id`, an incompatible entry matches nothing, the upsert attempts an insert, and
299
+ * the duplicate `_id` surfaces as a duplicate-key error (code 11000) instead of overwriting
300
+ * the entry someone else registered. Callers that only refresh part of an entry omit it.
291
301
  */
292
- async writeRegistryEntry(indexName, entry) {
293
- await this.db.collection(MongoDBVector.REGISTRY_COLLECTION).updateOne({ _id: indexName }, { $set: {
302
+ async writeRegistryEntry(indexName, entry, claim) {
303
+ await this.db.collection(MongoDBVector.REGISTRY_COLLECTION).updateOne({
304
+ _id: indexName,
305
+ ...claim
306
+ }, { $set: {
294
307
  indexName,
295
308
  ...entry
296
309
  } }, { upsert: true });
@@ -324,6 +337,31 @@ var MongoDBVector = class MongoDBVector extends MastraVector {
324
337
  text: `hybridQuery uses $rankFusion, which requires MongoDB >= 8.0 (found ${this.rankFusionSupport.version}). On 8.0.x it may need a MongoDB support case to enable; upgrade to >= 8.1, or run query() and textQuery() separately and fuse client-side.`
325
338
  });
326
339
  }
340
+ /** Validates a text upsert: `documents` is present, and `metadata`/`ids` line up with it. */
341
+ validateTextUpsertInput(indexName, documents, vectors, metadata, ids) {
342
+ const fail = (errorType, message, details = {}) => {
343
+ throw new MastraError({
344
+ id: createVectorErrorId("MONGODB", "UPSERT", errorType),
345
+ domain: ErrorDomain.STORAGE,
346
+ category: ErrorCategory.USER,
347
+ text: message,
348
+ details: {
349
+ indexName,
350
+ ...details
351
+ }
352
+ });
353
+ };
354
+ if (vectors?.length) fail("INVALID_ARGS", `Index "${indexName}" was created with autoEmbed, so MongoDB generates the embeddings: pass documents only. The supplied vectors would be stored in a field no index reads.`);
355
+ if (!documents || documents.length === 0) fail("EMPTY_DOCUMENTS", "documents array cannot be empty when upserting into an autoEmbed index");
356
+ if (metadata && metadata.length > 0 && metadata.length !== documents.length) fail("METADATA_LENGTH_MISMATCH", "Metadata array length must match documents array length", {
357
+ documentsLength: documents.length,
358
+ metadataLength: metadata.length
359
+ });
360
+ if (ids && ids.length !== documents.length) fail("IDS_LENGTH_MISMATCH", "IDs array length must match documents array length", {
361
+ documentsLength: documents.length,
362
+ idsLength: ids.length
363
+ });
364
+ }
327
365
  /**
328
366
  * Builds the projection pipeline stage(s) for query results based on metadata mode and
329
367
  * whether to include vectors. Shared across query, textQuery, and hybridQuery to keep
@@ -350,7 +388,7 @@ var MongoDBVector = class MongoDBVector extends MastraVector {
350
388
  * @param scoreMeta - metadata field holding the relevance score for the search stage in use
351
389
  * (`vectorSearchScore` for $vectorSearch, `searchScore` for $search, `score` for $rankFusion).
352
390
  */
353
- buildProjection(metadataMode, includeVector, scoreMeta) {
391
+ buildProjection(metadataMode, includeVector, scoreMeta, documentPath = this.documentFieldName) {
354
392
  const score = { $meta: scoreMeta };
355
393
  if (metadataMode === "document") {
356
394
  const project = {
@@ -366,7 +404,7 @@ var MongoDBVector = class MongoDBVector extends MastraVector {
366
404
  _id: 1,
367
405
  score,
368
406
  metadata: `$${this.metadataFieldName}`,
369
- document: `$${this.documentFieldName}`,
407
+ document: `$${documentPath}`,
370
408
  ...includeVector && { vector: `$${this.embeddingFieldName}` }
371
409
  } }];
372
410
  }
@@ -403,10 +441,14 @@ var MongoDBVector = class MongoDBVector extends MastraVector {
403
441
  * queryable. Skipping that step on a real Atlas cluster may cause
404
442
  * "index not found" or "index not ready" errors on subsequent operations.
405
443
  */
406
- async createIndex({ indexName, dimension, metric = "cosine", filterFields, collectionName, searchIndexName, allowWrites }) {
444
+ async createIndex(params) {
445
+ const { indexName, dimension, metric = "cosine", filterFields, collectionName, searchIndexName, allowWrites, autoEmbed } = params;
407
446
  let mongoMetric;
408
447
  try {
409
- if (!Number.isInteger(dimension) || dimension <= 0) throw new Error("Dimension must be a positive integer");
448
+ if (autoEmbed) {
449
+ if (dimension !== void 0) throw new Error("dimension cannot be combined with autoEmbed. The embedding model determines the vector size; use autoEmbed.numDimensions to override it.");
450
+ if (params.metric !== void 0) throw new Error("metric cannot be combined with autoEmbed. metric configures a client-side vector field; use autoEmbed.similarity instead.");
451
+ } else if (dimension === void 0 || !Number.isInteger(dimension) || dimension <= 0) throw new Error("Dimension must be a positive integer");
410
452
  mongoMetric = this.mongoMetricMap[metric];
411
453
  if (!mongoMetric) throw new Error(`Invalid metric: "${metric}". Must be one of: cosine, euclidean, dotproduct`);
412
454
  } catch (error) {
@@ -416,7 +458,7 @@ var MongoDBVector = class MongoDBVector extends MastraVector {
416
458
  category: ErrorCategory.USER,
417
459
  details: {
418
460
  indexName,
419
- dimension,
461
+ ...dimension !== void 0 ? { dimension } : {},
420
462
  metric
421
463
  }
422
464
  }, error);
@@ -456,40 +498,87 @@ var MongoDBVector = class MongoDBVector extends MastraVector {
456
498
  text: `searchIndexName "${targetSearchIndex}" collides with the full-text search index name for collection "${targetCollection}". Choose a different vector searchIndexName.`
457
499
  });
458
500
  const textSearchIndexName = effectiveIsByo ? existingEntry?.textSearchIndexName : existingEntry?.textSearchIndexName ?? defaultTextSearchIndex;
459
- await this.writeRegistryEntry(indexName, {
460
- collectionName: targetCollection,
461
- searchIndexName: targetSearchIndex,
462
- isByo: effectiveIsByo,
463
- allowWrites: effectiveWritable,
464
- textSearchIndexName,
465
- dimension,
466
- metric
501
+ const resolvedAutoEmbed = autoEmbed ? {
502
+ ...autoEmbed,
503
+ path: autoEmbed.path ?? this.documentFieldName
504
+ } : void 0;
505
+ const existingAutoEmbed = existingEntry?.autoEmbed;
506
+ const embeddingChanged = Boolean(existingAutoEmbed) !== Boolean(resolvedAutoEmbed) || existingAutoEmbed && resolvedAutoEmbed && (existingAutoEmbed.path !== resolvedAutoEmbed.path || existingAutoEmbed.model !== resolvedAutoEmbed.model);
507
+ if (existingEntry && embeddingChanged) throw new MastraError({
508
+ id: createVectorErrorId("MONGODB", "CREATE_INDEX", "CONFLICT"),
509
+ domain: ErrorDomain.STORAGE,
510
+ category: ErrorCategory.USER,
511
+ details: { indexName },
512
+ text: `Index "${indexName}" is already registered with ${describeEmbedding(existingAutoEmbed)}, but this createIndex call resolves to ${describeEmbedding(resolvedAutoEmbed)}. Call deleteIndex({ indexName: "${indexName}" }) first to rebuild it.`
467
513
  });
514
+ const registryClaim = {
515
+ collectionName: targetCollection,
516
+ ...resolvedAutoEmbed ? {
517
+ "autoEmbed.path": resolvedAutoEmbed.path,
518
+ "autoEmbed.model": resolvedAutoEmbed.model
519
+ } : { autoEmbed: null }
520
+ };
521
+ try {
522
+ await this.writeRegistryEntry(indexName, {
523
+ collectionName: targetCollection,
524
+ searchIndexName: targetSearchIndex,
525
+ isByo: effectiveIsByo,
526
+ allowWrites: effectiveWritable,
527
+ textSearchIndexName,
528
+ dimension,
529
+ ...resolvedAutoEmbed ? {} : { metric },
530
+ autoEmbed: resolvedAutoEmbed
531
+ }, registryClaim);
532
+ } catch (error) {
533
+ if (error?.code !== 11e3) throw error;
534
+ const winner = await this.readRegistryEntry(indexName);
535
+ throw new MastraError({
536
+ id: createVectorErrorId("MONGODB", "CREATE_INDEX", "CONFLICT"),
537
+ domain: ErrorDomain.STORAGE,
538
+ category: ErrorCategory.USER,
539
+ details: { indexName },
540
+ text: winner ? `Index "${indexName}" was registered concurrently against collection "${winner.collectionName}" with ${describeEmbedding(winner.autoEmbed)}, but this createIndex call resolves to collection "${targetCollection}" with ${describeEmbedding(resolvedAutoEmbed)}. Call deleteIndex({ indexName: "${indexName}" }) first to rebuild it.` : `Index "${indexName}" was registered concurrently by another process and removed again before this call could read it. Retry createIndex.`
541
+ });
542
+ }
468
543
  this.indexTargets.set(indexName, {
469
544
  collectionName: targetCollection,
470
545
  searchIndexName: targetSearchIndex,
471
546
  isByo: effectiveIsByo,
472
547
  allowWrites: effectiveWritable,
473
- textSearchIndexName
548
+ textSearchIndexName,
549
+ autoEmbed: resolvedAutoEmbed
474
550
  });
475
551
  const embeddingField = this.embeddingFieldName;
476
552
  const numDimensions = dimension;
477
553
  const declaredMetadataPaths = this.buildDeclaredMetadataPaths(filterFields);
554
+ const autoEmbedPath = resolvedAutoEmbed?.path;
555
+ let searchField;
556
+ if (autoEmbed) {
557
+ const { model, path: _path, ...indexOptions } = autoEmbed;
558
+ searchField = {
559
+ type: "autoEmbed",
560
+ modality: "text",
561
+ path: autoEmbedPath,
562
+ model,
563
+ ...indexOptions
564
+ };
565
+ } else searchField = {
566
+ type: "vector",
567
+ path: embeddingField,
568
+ numDimensions,
569
+ similarity: mongoMetric
570
+ };
571
+ const documentIsEmbedded = autoEmbedPath === this.documentFieldName;
478
572
  const fields = [
479
- {
480
- type: "vector",
481
- path: embeddingField,
482
- numDimensions,
483
- similarity: mongoMetric
484
- },
573
+ searchField,
485
574
  {
486
575
  type: "filter",
487
576
  path: "_id"
488
577
  },
489
- {
578
+ ...documentIsEmbedded ? [] : [{
490
579
  type: "filter",
491
580
  path: this.documentFieldName
492
- },
581
+ }],
493
582
  ...declaredMetadataPaths.map((path) => ({
494
583
  type: "filter",
495
584
  path
@@ -499,7 +588,7 @@ var MongoDBVector = class MongoDBVector extends MastraVector {
499
588
  definition: { fields },
500
589
  name: indexNameInternal,
501
590
  type: "vectorSearch"
502
- })) this.declaredFilterPaths.set(indexName, /* @__PURE__ */ new Set([this.documentFieldName, ...declaredMetadataPaths]));
591
+ })) this.declaredFilterPaths.set(indexName, new Set(documentIsEmbedded ? declaredMetadataPaths : [this.documentFieldName, ...declaredMetadataPaths]));
503
592
  if (!effectiveIsByo) await this.createSearchIndexIgnoringExisting(collection, {
504
593
  definition: { mappings: { dynamic: true } },
505
594
  name: textSearchIndexName ?? defaultTextSearchIndex,
@@ -606,7 +695,7 @@ var MongoDBVector = class MongoDBVector extends MastraVector {
606
695
  */
607
696
  async textQuery(params) {
608
697
  const { indexName, query, paths, topK = 10, filter, metadataMode = "field", searchIndexName } = params;
609
- const { collectionName } = await this.resolveIndexTarget(indexName);
698
+ const { collectionName, autoEmbed } = await this.resolveIndexTarget(indexName);
610
699
  const textIndex = searchIndexName ?? await this.resolveTextSearchIndexName(indexName);
611
700
  try {
612
701
  const collection = await this.getCollection(collectionName, true);
@@ -621,7 +710,7 @@ var MongoDBVector = class MongoDBVector extends MastraVector {
621
710
  } },
622
711
  ...Object.keys(metadataFilter).length ? [{ $match: metadataFilter }] : [],
623
712
  { $limit: Math.min(1e4, topK) },
624
- ...this.buildProjection(metadataMode, false, "searchScore")
713
+ ...this.buildProjection(metadataMode, false, "searchScore", autoEmbed?.path)
625
714
  ];
626
715
  return (await collection.aggregate(pipeline).toArray()).map((r) => ({
627
716
  id: this.idToString(r._id),
@@ -644,9 +733,37 @@ var MongoDBVector = class MongoDBVector extends MastraVector {
644
733
  * need a MongoDB support case to enable, and runs where enabled, e.g. Atlas 8.0.x).
645
734
  */
646
735
  async hybridQuery(params) {
647
- const { indexName, queryVector, query, paths, topK = 10, filter, weights, numCandidates, metadataMode = "field", textSearchIndexName } = params;
736
+ const { indexName, queryVector, queryText, model, query, paths, topK = 10, filter, weights, numCandidates, metadataMode = "field", textSearchIndexName } = params;
737
+ if (model && !queryText) throw new MastraError({
738
+ id: createVectorErrorId("MONGODB", "HYBRID_QUERY", "INVALID_ARGS"),
739
+ text: "model only applies to queryText, which MongoDB embeds server-side for the vector branch.",
740
+ domain: ErrorDomain.STORAGE,
741
+ category: ErrorCategory.USER,
742
+ details: { indexName }
743
+ });
744
+ if (queryVector && queryText) throw new MastraError({
745
+ id: createVectorErrorId("MONGODB", "HYBRID_QUERY", "MUTUALLY_EXCLUSIVE"),
746
+ text: "queryVector and queryText are mutually exclusive: the vector branch takes one or the other.",
747
+ domain: ErrorDomain.STORAGE,
748
+ category: ErrorCategory.USER,
749
+ details: { indexName }
750
+ });
751
+ if (!queryVector && !queryText) throw new MastraError({
752
+ id: createVectorErrorId("MONGODB", "HYBRID_QUERY", "MISSING_VECTOR"),
753
+ text: "The vector branch needs queryVector (or queryText on an autoEmbed index).",
754
+ domain: ErrorDomain.STORAGE,
755
+ category: ErrorCategory.USER,
756
+ details: { indexName }
757
+ });
648
758
  await this.assertRankFusionSupported();
649
- const { collectionName, searchIndexName } = await this.resolveIndexTarget(indexName);
759
+ const { collectionName, searchIndexName, autoEmbed } = await this.resolveIndexTarget(indexName);
760
+ if (queryText && !autoEmbed) throw new MastraError({
761
+ id: createVectorErrorId("MONGODB", "HYBRID_QUERY", "NOT_AUTO_EMBED"),
762
+ text: `Index "${indexName}" was not created with autoEmbed, so MongoDB cannot embed a query string for it. Pass queryVector instead.`,
763
+ domain: ErrorDomain.STORAGE,
764
+ category: ErrorCategory.USER,
765
+ details: { indexName }
766
+ });
650
767
  const textIndex = textSearchIndexName ?? await this.resolveTextSearchIndexName(indexName);
651
768
  try {
652
769
  const collection = await this.getCollection(collectionName, true);
@@ -656,8 +773,9 @@ var MongoDBVector = class MongoDBVector extends MastraVector {
656
773
  const candidates = Math.min(1e4, Math.max(perBranch, numCandidates ?? topK * 20));
657
774
  const vectorSearch = {
658
775
  index: searchIndexName,
659
- path: this.embeddingFieldName,
660
- queryVector,
776
+ path: autoEmbed ? autoEmbed.path : this.embeddingFieldName,
777
+ ...queryText ? { query: { text: queryText } } : { queryVector },
778
+ ...model ? { model } : {},
661
779
  numCandidates: candidates,
662
780
  limit: perBranch
663
781
  };
@@ -699,7 +817,7 @@ var MongoDBVector = class MongoDBVector extends MastraVector {
699
817
  ...combination ? { combination } : {}
700
818
  } },
701
819
  { $limit: Math.min(1e4, topK) },
702
- ...this.buildProjection(metadataMode, false, "score")
820
+ ...this.buildProjection(metadataMode, false, "score", autoEmbed?.path)
703
821
  ];
704
822
  return (await collection.aggregate(pipeline).toArray()).map((r) => ({
705
823
  id: this.idToString(r._id),
@@ -773,17 +891,24 @@ var MongoDBVector = class MongoDBVector extends MastraVector {
773
891
  * @returns The IDs of the upserted vectors in input order.
774
892
  */
775
893
  async upsert({ indexName, vectors, metadata, ids, documents }) {
776
- validateUpsertInput("MONGODB", vectors, metadata, ids);
777
- validateVectorValues("MONGODB", vectors);
894
+ const { collectionName, autoEmbed } = await this.resolveIndexTarget(indexName);
895
+ if (autoEmbed) this.validateTextUpsertInput(indexName, documents, vectors, metadata, ids);
896
+ else {
897
+ validateUpsertInput("MONGODB", vectors, metadata, ids);
898
+ validateVectorValues("MONGODB", vectors);
899
+ }
778
900
  await this.assertWritable(indexName, "UPSERT");
779
901
  if (metadata) this.assertNoCircularReferences(metadata, "UPSERT");
780
902
  try {
781
- const { collectionName } = await this.resolveIndexTarget(indexName);
782
903
  const collection = await this.getCollection(collectionName);
783
- const stats = await this.describeIndex({ indexName });
784
- await this.validateVectorDimensions(vectors, stats.dimension);
785
- const generatedIds = ids || vectors.map(() => v4());
786
- const operations = vectors.map((vector, idx) => {
904
+ if (!autoEmbed) {
905
+ const stats = await this.describeIndex({ indexName });
906
+ await this.validateVectorDimensions(vectors, stats.dimension);
907
+ }
908
+ const rowCount = (autoEmbed ? documents : vectors)?.length ?? 0;
909
+ const generatedIds = ids || Array.from({ length: rowCount }, () => v4());
910
+ const operations = Array.from({ length: rowCount }, (_unused, idx) => {
911
+ const vector = vectors?.[idx];
787
912
  const id = generatedIds[idx];
788
913
  const meta = metadata?.[idx] || {};
789
914
  const doc = documents?.[idx];
@@ -792,10 +917,10 @@ var MongoDBVector = class MongoDBVector extends MastraVector {
792
917
  return acc;
793
918
  }, {});
794
919
  const updateDoc = {
795
- [this.embeddingFieldName]: vector,
920
+ ...autoEmbed ? {} : { [this.embeddingFieldName]: vector },
796
921
  [this.metadataFieldName]: normalizedMeta
797
922
  };
798
- if (doc !== void 0) updateDoc[this.documentFieldName] = doc;
923
+ if (doc !== void 0) updateDoc[autoEmbed ? autoEmbed.path : this.documentFieldName] = doc;
799
924
  const idMatch = this.buildIdMatch(id);
800
925
  return { updateOne: {
801
926
  filter: { _id: idMatch },
@@ -833,42 +958,78 @@ var MongoDBVector = class MongoDBVector extends MastraVector {
833
958
  * recall at the cost of latency. Defaults to 20 * topK, capped at 10000.
834
959
  * @returns Array of results ordered by descending similarity score.
835
960
  */
836
- async query({ indexName, queryVector, topK = 10, filter, includeVector = false, documentFilter, numCandidates, metadataMode = "field" }) {
837
- if (!queryVector) throw new MastraError({
961
+ async query({ indexName, queryVector, queryText, model, topK = 10, filter, includeVector = false, documentFilter, numCandidates, metadataMode = "field" }) {
962
+ if (model && !queryText) throw new MastraError({
963
+ id: createVectorErrorId("MONGODB", "QUERY", "INVALID_ARGS"),
964
+ text: "model only applies to queryText, which MongoDB embeds server-side. A precomputed queryVector has already been embedded.",
965
+ domain: ErrorDomain.STORAGE,
966
+ category: ErrorCategory.USER,
967
+ details: { indexName }
968
+ });
969
+ if (queryVector && queryText) throw new MastraError({
970
+ id: createVectorErrorId("MONGODB", "QUERY", "MUTUALLY_EXCLUSIVE"),
971
+ text: "queryVector and queryText are mutually exclusive: pass a vector you embedded yourself, or a string for MongoDB to embed server-side.",
972
+ domain: ErrorDomain.STORAGE,
973
+ category: ErrorCategory.USER,
974
+ details: { indexName }
975
+ });
976
+ if (!queryVector && !queryText) throw new MastraError({
838
977
  id: createVectorErrorId("MONGODB", "QUERY", "MISSING_VECTOR"),
839
- text: "queryVector is required for MongoDB queries. Metadata-only queries are not supported by this vector store.",
978
+ text: "queryVector (or queryText on an autoEmbed index) is required for MongoDB queries. Metadata-only queries are not supported by this vector store.",
979
+ domain: ErrorDomain.STORAGE,
980
+ category: ErrorCategory.USER,
981
+ details: { indexName }
982
+ });
983
+ const { collectionName, searchIndexName, autoEmbed, isByo } = await this.resolveIndexTarget(indexName);
984
+ if (queryText && !autoEmbed) throw new MastraError({
985
+ id: createVectorErrorId("MONGODB", "QUERY", "NOT_AUTO_EMBED"),
986
+ text: `Index "${indexName}" was not created with autoEmbed, so MongoDB cannot embed a query string for it. Pass queryVector instead, or use textQuery() for full-text search.`,
987
+ domain: ErrorDomain.STORAGE,
988
+ category: ErrorCategory.USER,
989
+ details: { indexName }
990
+ });
991
+ if (includeVector && autoEmbed) throw new MastraError({
992
+ id: createVectorErrorId("MONGODB", "QUERY", "INVALID_ARGS"),
993
+ text: `includeVector is not supported on autoEmbed index "${indexName}": MongoDB keeps the generated embeddings in its own internal database, so they are not on the returned documents.`,
840
994
  domain: ErrorDomain.STORAGE,
841
995
  category: ErrorCategory.USER,
842
996
  details: { indexName }
843
997
  });
844
998
  try {
845
- const { collectionName, searchIndexName } = await this.resolveIndexTarget(indexName);
846
999
  const collection = await this.getCollection(collectionName, true);
847
1000
  const indexNameInternal = searchIndexName;
848
1001
  const metadataFilter = this.transformMetadataFilter(this.transformFilter(filter), metadataMode);
849
1002
  const hasMetadataFilter = Object.keys(metadataFilter).length > 0;
850
1003
  const vectorSearch = {
851
1004
  index: indexNameInternal,
852
- queryVector,
853
- path: this.embeddingFieldName,
1005
+ ...queryText ? { query: { text: queryText } } : { queryVector },
1006
+ ...model ? { model } : {},
1007
+ path: autoEmbed ? autoEmbed.path : this.embeddingFieldName,
854
1008
  numCandidates: Math.min(1e4, Math.max(topK, numCandidates ?? topK * 20)),
855
1009
  limit: Math.min(1e4, topK)
856
1010
  };
857
- if (hasMetadataFilter) {
858
- let declaredPaths;
859
- try {
1011
+ const documentTextField = autoEmbed ? autoEmbed.path : this.documentFieldName;
1012
+ const documentFilterExpr = documentFilter ? { [documentTextField]: documentFilter } : void 0;
1013
+ const verifyDocumentPath = Boolean(documentFilterExpr) && !autoEmbed && isByo;
1014
+ if (hasMetadataFilter || documentFilterExpr) {
1015
+ let declaredPaths = /* @__PURE__ */ new Set();
1016
+ if (hasMetadataFilter || verifyDocumentPath) try {
860
1017
  declaredPaths = await this.getDeclaredFilterPaths(indexName);
861
1018
  } catch {
862
1019
  declaredPaths = /* @__PURE__ */ new Set();
863
1020
  }
864
- if (this.canPushDownFilter(metadataFilter, declaredPaths)) vectorSearch.filter = documentFilter ? { $and: [metadataFilter, { [this.documentFieldName]: documentFilter }] } : metadataFilter;
865
- else {
866
- const candidateIds = await collection.aggregate([{ $match: metadataFilter }, { $project: { _id: 1 } }]).map((doc) => doc._id).toArray();
1021
+ const pushMetadata = !hasMetadataFilter || this.canPushDownFilter(metadataFilter, declaredPaths);
1022
+ const pushDocument = !documentFilterExpr || !autoEmbed && (!verifyDocumentPath || declaredPaths.has(documentTextField));
1023
+ const pushed = [...hasMetadataFilter && pushMetadata ? [metadataFilter] : [], ...documentFilterExpr && pushDocument ? [documentFilterExpr] : []];
1024
+ if (!pushMetadata || !pushDocument) {
1025
+ const preFilter = [...hasMetadataFilter && !pushMetadata ? [metadataFilter] : [], ...documentFilterExpr && !pushDocument ? [documentFilterExpr] : []];
1026
+ const candidateIds = await collection.aggregate([{ $match: preFilter.length > 1 ? { $and: preFilter } : preFilter[0] }, { $project: { _id: 1 } }]).map((doc) => doc._id).toArray();
867
1027
  if (candidateIds.length === 0) return [];
868
- vectorSearch.filter = documentFilter ? { $and: [{ _id: { $in: candidateIds } }, { [this.documentFieldName]: documentFilter }] } : { _id: { $in: candidateIds } };
1028
+ pushed.unshift({ _id: { $in: candidateIds } });
869
1029
  }
870
- } else if (documentFilter) vectorSearch.filter = { [this.documentFieldName]: documentFilter };
871
- const pipeline = [{ $vectorSearch: vectorSearch }, ...this.buildProjection(metadataMode, includeVector, "vectorSearchScore")];
1030
+ vectorSearch.filter = pushed.length > 1 ? { $and: pushed } : pushed[0];
1031
+ }
1032
+ const pipeline = [{ $vectorSearch: vectorSearch }, ...this.buildProjection(metadataMode, includeVector, "vectorSearchScore", autoEmbed?.path)];
872
1033
  return (await collection.aggregate(pipeline).toArray()).map((result) => ({
873
1034
  id: this.idToString(result._id),
874
1035
  score: result.score,
@@ -936,10 +1097,11 @@ var MongoDBVector = class MongoDBVector extends MastraVector {
936
1097
  */
937
1098
  async describeIndex({ indexName }) {
938
1099
  try {
939
- const { collectionName, searchIndexName } = await this.resolveIndexTarget(indexName);
1100
+ const { collectionName, searchIndexName, autoEmbed } = await this.resolveIndexTarget(indexName);
940
1101
  const collection = await this.getCollection(collectionName, true);
1102
+ const embeddedFieldName = autoEmbed ? autoEmbed.path : this.embeddingFieldName;
941
1103
  const count = await collection.countDocuments({
942
- [this.embeddingFieldName]: { $exists: true },
1104
+ [embeddedFieldName]: { $exists: true },
943
1105
  _id: { $ne: "__index_metadata__" }
944
1106
  });
945
1107
  const indexNameInternal = searchIndexName;
@@ -951,7 +1113,7 @@ var MongoDBVector = class MongoDBVector extends MastraVector {
951
1113
  details: { indexName },
952
1114
  text: `Atlas Search index "${indexNameInternal}" does not exist on collection "${indexName}". The collection may predate Mastra or the index may have been dropped externally.`
953
1115
  });
954
- const vectorField = indexData.latestDefinition?.fields?.find((f) => f.type === "vector");
1116
+ const vectorField = indexData.latestDefinition?.fields?.find((f) => f.type === "vector" || f.type === "autoEmbed");
955
1117
  if (!vectorField) throw new MastraError({
956
1118
  id: createVectorErrorId("MONGODB", "DESCRIBE_INDEX", "INVALID"),
957
1119
  domain: ErrorDomain.STORAGE,
@@ -960,9 +1122,9 @@ var MongoDBVector = class MongoDBVector extends MastraVector {
960
1122
  text: `Atlas Search index "${indexNameInternal}" exists but has no vector field. The index may have been created outside of Mastra or without vector configuration.`
961
1123
  });
962
1124
  return {
963
- dimension: vectorField.numDimensions,
1125
+ dimension: vectorField.type === "autoEmbed" ? vectorField.numDimensions ?? 1024 : vectorField.numDimensions,
964
1126
  count,
965
- metric: {
1127
+ metric: vectorField.type === "autoEmbed" && vectorField.similarity === void 0 ? void 0 : {
966
1128
  cosine: "cosine",
967
1129
  euclidean: "euclidean",
968
1130
  dotProduct: "dotproduct"
@@ -1045,9 +1207,16 @@ var MongoDBVector = class MongoDBVector extends MastraVector {
1045
1207
  if (update.metadata) this.assertNoCircularReferences(update.metadata, "UPDATE_VECTOR");
1046
1208
  try {
1047
1209
  if (!update.vector && !update.metadata) throw new Error("No updates provided");
1048
- const { collectionName } = await this.resolveIndexTarget(indexName);
1210
+ const { collectionName, autoEmbed } = await this.resolveIndexTarget(indexName);
1049
1211
  const collection = await this.getCollection(collectionName, true);
1050
1212
  const updateDoc = {};
1213
+ if (update.vector && autoEmbed) throw new MastraError({
1214
+ id: createVectorErrorId("MONGODB", "UPDATE_VECTOR", "INVALID_ARGS"),
1215
+ domain: ErrorDomain.STORAGE,
1216
+ category: ErrorCategory.USER,
1217
+ details: { indexName },
1218
+ text: `Index "${indexName}" was created with autoEmbed, so MongoDB owns the embeddings and a vector cannot be written directly. Upsert the document with new text instead.`
1219
+ });
1051
1220
  if (update.vector) {
1052
1221
  const stats = await this.describeIndex({ indexName });
1053
1222
  await this.validateVectorDimensions([update.vector], stats.dimension);