GraphQLGuide / apollo-datasource-mongodb

Apollo data source for MongoDB
MIT License
285 stars 64 forks source link

Add findByFields method #50

Closed ryangoree closed 3 years ago

ryangoree commented 3 years ago

Fixes #48

Added a findByFields method and refactored the DataLoader batch fn and tests to make it work.

This new method makes it possible to batch and cache calls for arbitrary fields on the collection.

example:


// Find by a single field
await this.findByFields({ tags: ['gaming', 'games'] }, { ttl: 1 })
// Resulting app level cache key (if the collection was "users"):
//   - 'mongo-users-{"tags":["gaming","games"]}'
// Resulting dataloader memoization cache keys:
//   - '{"tags":"gaming"}'
//   - '{"tags":"games"}'
//     .load() is called with each tag individually, so subsequent calls for either or both tags will load from memoization cache
// Resulting query:
//   - collection.find({ tags: { $in: ['gaming', 'games'] } })
// 

// Find by multiple fields
await this.findByFields({
  tags: ['gaming', 'games'],
  userName: 'testUser'
}, { ttl: 1 })
// Resulting app level cache key (if the collection was "users"):
//   - 'mongo-users-{"tags":["gaming","games"],"userName":["testUser"]}'
// Resulting dataloader memoization cache key:
//   - '{"tags":["gaming","games"],"userName":["testUser"]}'
//     Since the results need to match both the tags AND the userName, .load() is only called once with all the fields and values.
// resulting query:
//   - collection.find({
//       tags: { $in: ['gaming', 'games'] },
//       userName: { $in: ['testUser'] }
//     })

apollo-datasource-mongodb-tests-passed

ryangoree commented 3 years ago

Forgot to mention, this will also use $or to merge multiple pending requests with different fields.

Examples


// 2 calls for completely different fields -> merged with $or
const pendingUsers1 = this.findByFields({ userName: 'ryan' })
const pendingUsers2 = this.findByFields({ tags: 'gaming' })
await Promise.all([pendingUsers1, pendingUsers2]);
// resulting query:
//   - collection.find({
//       $or: [
//         { userName: { $in: ['ryan'] } },
//         { tags: { $in: ['gaming'] } }
//       ]
//     })

// 2 calls for partially different fields -> merged with $or
const pendingUsers1 = this.findByFields({ userName: 'ryan' })
const pendingUsers2 = this.findByFields({ userName: 'ryan', tags: 'gaming' })
await Promise.all([pendingUsers1, pendingUsers2]);
// resulting query:
//   - collection.find({
//       $or: [
//         { userName: { $in: ['ryan'] } },
//         {
//           userName: { $in: ['ryan'] },
//           tags: { $in: ['gaming'] } }
//         }
//       ]
//     })

// 2 calls for the same fields -> merged without $or
const pendingUsers1 = this.findByFields({ userName: 'ryan' })
const pendingUsers2 = this.findByFields({ userName: 'loren' })
await Promise.all([pendingUsers1, pendingUsers2]);
// resulting query:
//   - collection.find({ userName: { $in: ['ryan', 'loren'] } })

// Mixed
const pendingUsers1 = this.findByFields({ userName: 'ryan', tags: 'games' })
const pendingUsers2 = this.findByFields({ userName: 'loren', tags: 'gaming' })
const pendingUsers3 = this.findByFields({ tags: ['games', 'gaming'] })
await Promise.all([pendingUsers1, pendingUsers2, pendingUsers3]);
// resulting query:
//   - collection.find({
//       $or: [
//         {
//           userName: { $in: ['ryan', 'loren'] },
//           tags: { $in: ['games', 'gaming'] } }
//         },
//         { tags: { $in: ['games', 'gaming']  } }
//       ]
//     })
lorensr commented 3 years ago

Sorry for the delay! And thanks again for contributing. Published in 0.4.0