@mastra/mongodb 0.13.0-alpha.2 → 0.13.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.
@@ -73,6 +73,21 @@ export declare class MongoDBVector extends MastraVector<MongoDBVectorFilter> {
73
73
  private validateVectorDimensions;
74
74
  private setIndexDimension;
75
75
  private transformFilter;
76
+ /**
77
+ * Transform metadata field filters to use MongoDB dot notation.
78
+ * Fields that are stored in the metadata subdocument need to be prefixed with 'metadata.'
79
+ * This handles filters from the Memory system which expects direct field access.
80
+ *
81
+ * @param filter - The filter object to transform
82
+ * @returns Transformed filter with metadata fields properly prefixed
83
+ */
84
+ private transformMetadataFilter;
85
+ /**
86
+ * Determine if a field should be treated as a metadata field.
87
+ * Common metadata fields include thread_id, resource_id, message_id, and any field
88
+ * that doesn't start with underscore (MongoDB system fields).
89
+ */
90
+ private isMetadataField;
76
91
  }
77
92
  export {};
78
93
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vector/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EACV,WAAW,EACX,UAAU,EACV,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EACnB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EAAE,kBAAkB,EAA4B,MAAM,SAAS,CAAC;AAI5E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAGpD,MAAM,WAAW,yBAA0B,SAAQ,kBAAkB;IACnE,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,UAAU,wBAAyB,SAAQ,iBAAiB,CAAC,mBAAmB,CAAC;IAC/E,cAAc,CAAC,EAAE,mBAAmB,CAAC;CACtC;AAED,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAWD,qBAAa,aAAc,SAAQ,YAAY,CAAC,mBAAmB,CAAC;IAClE,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,EAAE,CAAK;IACf,OAAO,CAAC,WAAW,CAA2C;IAC9D,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAe;IAClD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAc;IAChD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAc;IAChD,OAAO,CAAC,uBAAuB,CAA4C;IAC3E,OAAO,CAAC,cAAc,CAIpB;gBAEU,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,kBAAkB,CAAA;KAAE;IAQ7F,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAexB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAe3B,WAAW,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,MAAiB,EAAE,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAoGhG;;;;;;;OAOG;IACG,iBAAiB,CAAC,EACtB,SAAS,EACT,SAAiB,EACjB,eAAsB,GACvB,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBpC,MAAM,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,yBAAyB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IA+DtG,KAAK,CAAC,EACV,SAAS,EACT,WAAW,EACX,IAAS,EACT,MAAM,EACN,aAAqB,EACrB,cAAc,GACf,EAAE,wBAAwB,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAkF9C,WAAW,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAgBtC;;;;;OAKG;IACG,aAAa,CAAC,EAAE,SAAS,EAAE,EAAE,mBAAmB,GAAG,OAAO,CAAC,UAAU,CAAC;IAgCtE,WAAW,CAAC,EAAE,SAAS,EAAE,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAyBlE;;;;;;;;;OASG;IACG,YAAY,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IA8ChF;;;;;;OAMG;IACG,YAAY,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;YAqB1D,aAAa;YAoBb,wBAAwB;YAmBxB,iBAAiB;IAM/B,OAAO,CAAC,eAAe;CAKxB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vector/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/mongodb",
3
- "version": "0.13.0-alpha.2",
3
+ "version": "0.13.0",
4
4
  "description": "MongoDB provider for Mastra - includes vector store capabilities",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -31,10 +31,10 @@
31
31
  "tsup": "^8.5.0",
32
32
  "typescript": "^5.8.3",
33
33
  "vitest": "^3.2.4",
34
- "@internal/lint": "0.0.26",
35
- "@internal/storage-test-utils": "0.0.22",
36
- "@internal/types-builder": "0.0.1",
37
- "@mastra/core": "0.13.0-alpha.2"
34
+ "@internal/types-builder": "0.0.2",
35
+ "@internal/lint": "0.0.27",
36
+ "@mastra/core": "0.13.0",
37
+ "@internal/storage-test-utils": "0.0.23"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "@mastra/core": ">=0.13.0-0 <0.14.0-0"
@@ -1,3 +1,4 @@
1
+ import { createVectorTestSuite } from '@internal/storage-test-utils';
1
2
  import { vi, describe, it, expect, beforeAll, afterAll, test } from 'vitest';
2
3
  import { MongoDBVector } from './';
3
4
 
@@ -514,4 +515,137 @@ describe('MongoDBVector Integration Tests', () => {
514
515
  expect(results.length).toBe(0);
515
516
  });
516
517
  });
518
+
519
+ describe('Metadata Field Filtering Bug Reproduction', () => {
520
+ const bugTestIndexName = 'metadata_filter_bug_test_' + Date.now();
521
+
522
+ beforeAll(async () => {
523
+ // Create index for bug reproduction
524
+ await createIndexAndWait(vectorDB, bugTestIndexName, 4, 'cosine');
525
+
526
+ // Insert vectors with thread_id and resource_id in metadata
527
+ // Simulating what the Memory system does
528
+ const vectors = [
529
+ [1, 0, 0, 0],
530
+ [0, 1, 0, 0],
531
+ [0, 0, 1, 0],
532
+ [0, 0, 0, 1],
533
+ ];
534
+
535
+ const metadata = [
536
+ { thread_id: 'thread-123', resource_id: 'resource-123', message: 'first' },
537
+ { thread_id: 'thread-123', resource_id: 'resource-123', message: 'second' },
538
+ { thread_id: 'thread-456', resource_id: 'resource-456', message: 'third' },
539
+ { thread_id: 'thread-456', resource_id: 'resource-456', message: 'fourth' },
540
+ ];
541
+
542
+ await vectorDB.upsert({
543
+ indexName: bugTestIndexName,
544
+ vectors,
545
+ metadata,
546
+ });
547
+
548
+ // Wait for indexing
549
+ await new Promise(resolve => setTimeout(resolve, 5000));
550
+ });
551
+
552
+ afterAll(async () => {
553
+ await deleteIndexAndWait(vectorDB, bugTestIndexName);
554
+ });
555
+
556
+ test('filtering by thread_id WITHOUT metadata prefix works correctly', async () => {
557
+ // This is what Memory.rememberMessages does - passes thread_id directly
558
+ // Previously this would ignore the filter, but now it works correctly
559
+ const results = await vectorDB.query({
560
+ indexName: bugTestIndexName,
561
+ queryVector: [1, 0, 0, 0],
562
+ topK: 10,
563
+ filter: { thread_id: 'thread-123' }, // Now correctly filters by thread_id
564
+ });
565
+
566
+ // Verify the fix works - should return only documents from thread-123
567
+ expect(results).toHaveLength(2);
568
+ expect(results.every(r => r.metadata?.thread_id === 'thread-123')).toBe(true);
569
+
570
+ // Should NOT contain documents from other threads
571
+ const threadIds = results.map(r => r.metadata?.thread_id);
572
+ expect(threadIds).not.toContain('thread-456');
573
+ });
574
+
575
+ test('filtering by resource_id WITHOUT metadata prefix works correctly', async () => {
576
+ // This is what Memory.rememberMessages does with resource scope
577
+ const results = await vectorDB.query({
578
+ indexName: bugTestIndexName,
579
+ queryVector: [0, 1, 0, 0],
580
+ topK: 10,
581
+ filter: { resource_id: 'resource-123' }, // Now correctly filters by resource_id
582
+ });
583
+
584
+ // Verify the fix works - should return only documents from resource-123
585
+ expect(results).toHaveLength(2);
586
+ expect(results.every(r => r.metadata?.resource_id === 'resource-123')).toBe(true);
587
+
588
+ // Should NOT contain documents from other resources
589
+ const resourceIds = results.map(r => r.metadata?.resource_id);
590
+ expect(resourceIds).not.toContain('resource-456');
591
+ });
592
+
593
+ test('filtering WITH metadata. prefix works correctly (workaround)', async () => {
594
+ // This is the workaround - using metadata.thread_id
595
+ const results = await vectorDB.query({
596
+ indexName: bugTestIndexName,
597
+ queryVector: [1, 0, 0, 0],
598
+ topK: 10,
599
+ filter: { 'metadata.thread_id': 'thread-123' },
600
+ });
601
+
602
+ // This works correctly
603
+ expect(results).toHaveLength(2);
604
+ expect(results[0]?.metadata?.thread_id).toBe('thread-123');
605
+ expect(results[1]?.metadata?.thread_id).toBe('thread-123');
606
+ });
607
+
608
+ test('semantic search without filter returns all vectors (shows data exists)', async () => {
609
+ // Verify that the data exists and can be retrieved without filters
610
+ const results = await vectorDB.query({
611
+ indexName: bugTestIndexName,
612
+ queryVector: [0.5, 0.5, 0.5, 0.5],
613
+ topK: 10,
614
+ });
615
+
616
+ // Should return all 4 vectors
617
+ expect(results).toHaveLength(4);
618
+
619
+ // Verify metadata is stored correctly
620
+ const threadIds = results.map(r => r.metadata?.thread_id);
621
+ expect(threadIds).toContain('thread-123');
622
+ expect(threadIds).toContain('thread-456');
623
+ });
624
+ });
625
+ });
626
+
627
+ // Use the shared test suite with factory pattern
628
+ const vectorDB = new MongoDBVector({ uri, dbName });
629
+
630
+ createVectorTestSuite({
631
+ vector: vectorDB,
632
+ connect: async () => {
633
+ await vectorDB.connect();
634
+ await waitForAtlasSearchReady(vectorDB);
635
+ },
636
+ disconnect: async () => {
637
+ await vectorDB.disconnect();
638
+ },
639
+ createIndex: async (indexName: string) => {
640
+ await vectorDB.createIndex({ indexName, dimension: 4, metric: 'cosine' });
641
+ await vectorDB.waitForIndexReady({ indexName });
642
+ },
643
+ deleteIndex: async (indexName: string) => {
644
+ try {
645
+ await vectorDB.deleteIndex({ indexName });
646
+ } catch (error) {
647
+ console.error(`Error deleting index ${indexName}:`, error);
648
+ }
649
+ },
650
+ waitForIndexing: () => new Promise(resolve => setTimeout(resolve, 5000)),
517
651
  });
@@ -302,12 +302,15 @@ export class MongoDBVector extends MastraVector<MongoDBVectorFilter> {
302
302
  const mongoFilter = this.transformFilter(filter);
303
303
  const documentMongoFilter = documentFilter ? { [this.documentFieldName]: documentFilter } : {};
304
304
 
305
+ // Transform metadata field filters to use dot notation
306
+ const transformedMongoFilter = this.transformMetadataFilter(mongoFilter);
307
+
305
308
  // Combine the filters
306
309
  let combinedFilter: any = {};
307
- if (Object.keys(mongoFilter).length > 0 && Object.keys(documentMongoFilter).length > 0) {
308
- combinedFilter = { $and: [mongoFilter, documentMongoFilter] };
309
- } else if (Object.keys(mongoFilter).length > 0) {
310
- combinedFilter = mongoFilter;
310
+ if (Object.keys(transformedMongoFilter).length > 0 && Object.keys(documentMongoFilter).length > 0) {
311
+ combinedFilter = { $and: [transformedMongoFilter, documentMongoFilter] };
312
+ } else if (Object.keys(transformedMongoFilter).length > 0) {
313
+ combinedFilter = transformedMongoFilter;
311
314
  } else if (Object.keys(documentMongoFilter).length > 0) {
312
315
  combinedFilter = documentMongoFilter;
313
316
  }
@@ -329,6 +332,9 @@ export class MongoDBVector extends MastraVector<MongoDBVectorFilter> {
329
332
 
330
333
  if (candidateIds.length > 0) {
331
334
  vectorSearch.filter = { _id: { $in: candidateIds } };
335
+ } else {
336
+ // No documents match the filter, return empty results
337
+ return [];
332
338
  }
333
339
  }
334
340
 
@@ -588,4 +594,63 @@ export class MongoDBVector extends MastraVector<MongoDBVectorFilter> {
588
594
  if (!filter) return {};
589
595
  return translator.translate(filter);
590
596
  }
597
+
598
+ /**
599
+ * Transform metadata field filters to use MongoDB dot notation.
600
+ * Fields that are stored in the metadata subdocument need to be prefixed with 'metadata.'
601
+ * This handles filters from the Memory system which expects direct field access.
602
+ *
603
+ * @param filter - The filter object to transform
604
+ * @returns Transformed filter with metadata fields properly prefixed
605
+ */
606
+ private transformMetadataFilter(filter: any): any {
607
+ if (!filter || typeof filter !== 'object') return filter;
608
+
609
+ const transformed: any = {};
610
+
611
+ for (const [key, value] of Object.entries(filter)) {
612
+ // Check if this is a MongoDB operator (starts with $)
613
+ if (key.startsWith('$')) {
614
+ // For logical operators like $and, $or, recursively transform their contents
615
+ if (Array.isArray(value)) {
616
+ transformed[key] = value.map(item => this.transformMetadataFilter(item));
617
+ } else {
618
+ transformed[key] = this.transformMetadataFilter(value);
619
+ }
620
+ }
621
+ // Check if the key already has 'metadata.' prefix
622
+ else if (key.startsWith('metadata.')) {
623
+ // Already prefixed, keep as is
624
+ transformed[key] = value;
625
+ }
626
+ // Check if this is a known metadata field that needs prefixing
627
+ else if (this.isMetadataField(key)) {
628
+ // Add metadata. prefix for fields stored in metadata subdocument
629
+ transformed[`metadata.${key}`] = value;
630
+ } else {
631
+ // Keep other fields as is
632
+ transformed[key] = value;
633
+ }
634
+ }
635
+
636
+ return transformed;
637
+ }
638
+
639
+ /**
640
+ * Determine if a field should be treated as a metadata field.
641
+ * Common metadata fields include thread_id, resource_id, message_id, and any field
642
+ * that doesn't start with underscore (MongoDB system fields).
643
+ */
644
+ private isMetadataField(key: string): boolean {
645
+ // MongoDB system fields start with underscore
646
+ if (key.startsWith('_')) return false;
647
+
648
+ // Document-level fields that are NOT in metadata
649
+ const documentFields = ['_id', this.embeddingFieldName, this.documentFieldName];
650
+ if (documentFields.includes(key)) return false;
651
+
652
+ // Everything else is assumed to be in metadata
653
+ // This includes thread_id, resource_id, message_id, and any custom fields
654
+ return true;
655
+ }
591
656
  }