Skip to content

Commit d3cae61

Browse files
committed
Fix creation of caching methods
1 parent b84b49e commit d3cae61

File tree

5 files changed

+95
-64
lines changed

5 files changed

+95
-64
lines changed

README.md

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Apollo [data source](https://www.apollographql.com/docs/apollo-server/features/d
66
npm i apollo-datasource-mongodb
77
```
88

9-
This package uses [DataLoader](https://github.com/graphql/dataloader) for batching and per-request memoization caching. It also optionally (if you provide a `ttl`), does shared application-level caching (using either the default Apollo `InMemoryLRUCache` or the [cache you provide to ApolloServer()](https://www.apollographql.com/docs/apollo-server/features/data-sources#using-memcachedredis-as-a-cache-storage-backend)). It does this only for these two methods, which are added to your collections:
9+
This package uses [DataLoader](https://github.com/graphql/dataloader) for batching and per-request memoization caching. It also optionally (if you provide a `ttl`), does shared application-level caching (using either the default Apollo `InMemoryLRUCache` or the [cache you provide to ApolloServer()](https://www.apollographql.com/docs/apollo-server/features/data-sources#using-memcachedredis-as-a-cache-storage-backend)). It does this only for these two methods:
1010

1111
- [`findOneById(id, options)`](#findonebyid)
1212
- [`findManyByIds(ids, options)`](#findmanybyids)
@@ -32,19 +32,19 @@ This package uses [DataLoader](https://github.com/graphql/dataloader) for batchi
3232

3333
### Basic
3434

35-
The basic setup is subclassing `MongoDataSource`, setting your collections in the constructor, and then using the [API methods](#API) on your collections:
35+
The basic setup is subclassing `MongoDataSource`, setting your collections in the constructor, and then using the [API methods](#API):
3636

3737
```js
3838
import { MongoDataSource } from 'apollo-datasource-mongodb'
3939

4040
class MyMongo extends MongoDataSource {
4141
constructor() {
4242
super()
43-
this.collections = [users, posts]
43+
this.collections = { users, posts }
4444
}
4545

4646
getUser(userId) {
47-
return users.findOneById(userId)
47+
return this.users.findOneById(userId)
4848
}
4949
}
5050
```
@@ -58,7 +58,7 @@ class MyMongo extends MongoDataSource {
5858
async getPrivateUserData(userId) {
5959
const isAuthorized = this.context.currentUserId === userId
6060
if (isAuthorized) {
61-
const user = await users.findOneById(userId)
61+
const user = await this.users.findOneById(userId)
6262
return user && user.privateData
6363
}
6464
}
@@ -71,7 +71,7 @@ If you want to implement an initialize method, it must call the parent method:
7171
class MyMongo extends MongoDataSource {
7272
constructor() {
7373
super()
74-
this.collections = [users, posts]
74+
this.collections = { users, posts }
7575
}
7676

7777
initialize(config) {
@@ -103,15 +103,15 @@ client.connect(e => {
103103
class MyMongo extends MongoDataSource {
104104
constructor() {
105105
super()
106-
this.collections = [users, posts]
106+
this.collections = { users, posts }
107107
}
108108

109109
getUser(userId) {
110-
return users.findOneById(userId)
110+
return this.users.findOneById(userId)
111111
}
112112

113113
getPosts(postIds) {
114-
return posts.findManyByIds(postIds)
114+
return this.posts.findManyByIds(postIds)
115115
}
116116
}
117117

@@ -133,28 +133,28 @@ const server = new ApolloServer({
133133
})
134134
```
135135

136-
You might prefer to structure it as one data source per collection, in which case you'd do:
136+
You might prefer to structure it as one data source per collection, in which case you'd set `this.collection` instead of `this.collections`, and the [API methods](#api) would be available directly on `this`:
137137

138138
```js
139139
class Users extends MongoDataSource {
140140
constructor() {
141141
super()
142-
this.collections = [users]
142+
this.collection = users
143143
}
144144

145145
getUser(userId) {
146-
return users.findOneById(userId)
146+
return this.findOneById(userId)
147147
}
148148
}
149149

150150
class Posts extends MongoDataSource {
151151
constructor() {
152152
super()
153-
this.collections = [posts]
153+
this.collection = posts
154154
}
155155

156156
getPosts(postIds) {
157-
return posts.findManyByIds(postIds)
157+
return this.findManyByIds(postIds)
158158
}
159159
}
160160

@@ -189,15 +189,15 @@ const MINUTE = 60
189189
class MyMongo extends MongoDataSource {
190190
constructor() {
191191
super()
192-
this.collections = [users, posts]
192+
this.collections = { users, posts }
193193
}
194194

195195
getUser(userId) {
196-
return users.findOneById(userId, { ttl: MINUTE })
196+
return this.users.findOneById(userId, { ttl: MINUTE })
197197
}
198198

199199
updateUserName(userId, newName) {
200-
users.deleteFromCacheById(userId)
200+
this.users.deleteFromCacheById(userId)
201201
return users.updateOne({
202202
_id: userId
203203
}, {
@@ -223,18 +223,18 @@ Here we also call [`deleteFromCacheById()`](#deletefromcachebyid) to remove the
223223

224224
### findOneById
225225

226-
`collection.findOneById(id, { ttl })`
226+
`findOneById(id, { ttl })`
227227

228228
Resolves to the found document. Uses DataLoader to load `id`. DataLoader uses `collection.find({ _id: { $in: ids } })`. Optionally caches the document if `ttl` is set (in whole seconds).
229229

230230
### findManyByIds
231231

232-
`collection.findManyByIds(ids, { ttl })`
232+
`findManyByIds(ids, { ttl })`
233233

234234
Calls [`findOneById()`](#findonebyid) for each id. Resolves to an array of documents.
235235

236236
### deleteFromCacheById
237237

238-
`collection.deleteFromCacheById(id)`
238+
`deleteFromCacheById(id)`
239239

240240
Deletes a document from the cache.

src/__tests__/cache.test.js

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { InMemoryLRUCache } from 'apollo-server-caching'
22
import wait from 'waait'
33

4-
import { setupCaching } from '../cache'
4+
import { createCachingMethods } from '../cache'
55

66
const docs = {
77
id1: {
@@ -18,8 +18,9 @@ const docs = {
1818
const collectionName = 'test'
1919
const cacheKey = id => `mongo-${collectionName}-${id}`
2020

21-
describe('setupCaching', () => {
21+
describe('createCachingMethods', () => {
2222
let collection
23+
let api
2324
let cache
2425

2526
beforeEach(() => {
@@ -35,23 +36,23 @@ describe('setupCaching', () => {
3536

3637
cache = new InMemoryLRUCache()
3738

38-
setupCaching({ collection, cache })
39+
api = createCachingMethods({ collection, cache })
3940
})
4041

4142
it('adds the right methods', () => {
42-
expect(collection.findOneById).toBeDefined()
43-
expect(collection.findManyByIds).toBeDefined()
44-
expect(collection.deleteFromCacheById).toBeDefined()
43+
expect(api.findOneById).toBeDefined()
44+
expect(api.findManyByIds).toBeDefined()
45+
expect(api.deleteFromCacheById).toBeDefined()
4546
})
4647

4748
it('finds one', async () => {
48-
const doc = await collection.findOneById('id1')
49+
const doc = await api.findOneById('id1')
4950
expect(doc).toBe(docs.id1)
5051
expect(collection.find.mock.calls.length).toBe(1)
5152
})
5253

5354
it('finds two with batching', async () => {
54-
const foundDocs = await collection.findManyByIds(['id2', 'id3'])
55+
const foundDocs = await api.findManyByIds(['id2', 'id3'])
5556

5657
expect(foundDocs[0]).toBe(docs.id2)
5758
expect(foundDocs[1]).toBe(docs.id3)
@@ -61,43 +62,43 @@ describe('setupCaching', () => {
6162

6263
// TODO why doesn't this pass?
6364
// it.only(`doesn't cache without ttl`, async () => {
64-
// await collection.findOneById('id1')
65-
// await collection.findOneById('id1')
65+
// await api.findOneById('id1')
66+
// await api.findOneById('id1')
6667

6768
// expect(collection.find.mock.calls.length).toBe(2)
6869
// })
6970

7071
it(`doesn't cache without ttl`, async () => {
71-
await collection.findOneById('id1')
72+
await api.findOneById('id1')
7273

7374
const value = await cache.get(cacheKey('id1'))
7475
expect(value).toBeUndefined()
7576
})
7677

7778
it(`caches`, async () => {
78-
await collection.findOneById('id1', { ttl: 1 })
79+
await api.findOneById('id1', { ttl: 1 })
7980
const value = await cache.get(cacheKey('id1'))
8081
expect(value).toBe(docs.id1)
8182

82-
await collection.findOneById('id1')
83+
await api.findOneById('id1')
8384
expect(collection.find.mock.calls.length).toBe(1)
8485
})
8586

8687
it(`caches with ttl`, async () => {
87-
await collection.findOneById('id1', { ttl: 1 })
88+
await api.findOneById('id1', { ttl: 1 })
8889
await wait(1001)
8990

9091
const value = await cache.get(cacheKey('id1'))
9192
expect(value).toBeUndefined()
9293
})
9394

9495
it(`deletes from cache`, async () => {
95-
await collection.findOneById('id1', { ttl: 1 })
96+
await api.findOneById('id1', { ttl: 1 })
9697

9798
const valueBefore = await cache.get(cacheKey('id1'))
9899
expect(valueBefore).toBe(docs.id1)
99100

100-
await collection.deleteFromCacheById('id1')
101+
await api.deleteFromCacheById('id1')
101102

102103
const valueAfter = await cache.get(cacheKey('id1'))
103104
expect(valueAfter).toBeUndefined()

src/__tests__/datasource.test.js

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,32 @@ const posts = {}
66
class MyMongo extends MongoDataSource {
77
constructor() {
88
super()
9-
this.collections = [users, posts]
9+
this.collections = { users, posts }
1010
}
1111

1212
initialize(config) {
1313
super.initialize(config)
1414
}
1515
}
1616

17+
class SingleCollection extends MongoDataSource {
18+
constructor() {
19+
super()
20+
this.collection = users
21+
}
22+
}
23+
1724
describe('MongoDataSource', () => {
1825
it('sets up caching functions', () => {
1926
const source = new MyMongo()
2027
source.initialize({})
21-
expect(users.findOneById).toBeDefined()
22-
expect(posts.findOneById).toBeDefined()
28+
expect(source.users.findOneById).toBeDefined()
29+
expect(source.posts.findOneById).toBeDefined()
30+
})
31+
32+
it('sets up caching functions for single collection', () => {
33+
const source = new SingleCollection()
34+
source.initialize({})
35+
expect(source.findOneById).toBeDefined()
2336
})
2437
})

src/cache.js

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import DataLoader from 'dataloader'
22

3-
export const setupCaching = ({ collection, cache }) => {
3+
export const createCachingMethods = ({ collection, cache }) => {
44
const loader = new DataLoader(ids =>
55
collection
66
.find({ _id: { $in: ids } })
@@ -16,26 +16,28 @@ export const setupCaching = ({ collection, cache }) => {
1616

1717
const cachePrefix = `mongo-${collection.collectionName}-`
1818

19-
collection.findOneById = async (id, { ttl } = {}) => {
20-
const key = cachePrefix + id
21-
22-
const cacheDoc = await cache.get(key)
23-
if (cacheDoc) {
24-
return cacheDoc
25-
}
26-
27-
const doc = await loader.load(id)
28-
if (Number.isInteger(ttl)) {
29-
// https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-caching#apollo-server-caching
30-
cache.set(key, doc, { ttl })
31-
}
32-
33-
return doc
34-
}
35-
36-
collection.findManyByIds = (ids, { ttl } = {}) => {
37-
return Promise.all(ids.map(id => collection.findOneById(id, { ttl })))
19+
const methods = {
20+
findOneById: async (id, { ttl } = {}) => {
21+
const key = cachePrefix + id
22+
23+
const cacheDoc = await cache.get(key)
24+
if (cacheDoc) {
25+
return cacheDoc
26+
}
27+
28+
const doc = await loader.load(id)
29+
if (Number.isInteger(ttl)) {
30+
// https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-caching#apollo-server-caching
31+
cache.set(key, doc, { ttl })
32+
}
33+
34+
return doc
35+
},
36+
findManyByIds: (ids, { ttl } = {}) => {
37+
return Promise.all(ids.map(id => methods.findOneById(id, { ttl })))
38+
},
39+
deleteFromCacheById: id => cache.delete(cachePrefix + id)
3840
}
3941

40-
collection.deleteFromCacheById = id => cache.delete(cachePrefix + id)
42+
return methods
4143
}

src/datasource.js

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,37 @@ import { DataSource } from 'apollo-datasource'
22
import { ApolloError } from 'apollo-server-errors'
33
import { InMemoryLRUCache } from 'apollo-server-caching'
44

5-
import { setupCaching } from './cache'
5+
import { createCachingMethods } from './cache'
66

77
class MongoDataSource extends DataSource {
88
// https://github.com/apollographql/apollo-server/blob/master/packages/apollo-datasource/src/index.ts
99
initialize(config) {
1010
this.context = config.context
1111

12-
if (!this.collections || !this.collections.length) {
12+
const setUpCorrectly =
13+
typeof this.collections === 'object' || this.collection
14+
if (!setUpCorrectly) {
1315
throw new ApolloError(
14-
'Child class of MongoDataSource must set this.collections in constructor'
16+
'Child class of MongoDataSource must set this.collections or this.collection in constructor'
1517
)
1618
}
1719

1820
const cache = config.cache || new InMemoryLRUCache()
1921

20-
this.collections.forEach(collection => setupCaching({ collection, cache }))
22+
if (this.collections) {
23+
for (const key in this.collections) {
24+
this[key] = createCachingMethods({
25+
collection: this.collections[key],
26+
cache
27+
})
28+
}
29+
} else {
30+
const methods = createCachingMethods({
31+
collection: this.collection,
32+
cache
33+
})
34+
Object.assign(this, methods)
35+
}
2136
}
2237
}
2338

0 commit comments

Comments
 (0)