Skip to content

Commit a72692b

Browse files
committed
Add support for string ids; fix ObjectId caching
1 parent d0bcb4f commit a72692b

File tree

5 files changed

+96
-45
lines changed

5 files changed

+96
-45
lines changed

index.d.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,15 @@ declare module 'apollo-datasource-mongodb' {
3131
constructor(modelOrCollection: ModelOrCollection<TData>)
3232

3333
findOneById(
34-
id: ObjectId,
34+
id: ObjectId | string,
3535
options?: Options
3636
): Promise<TData | null | undefined>
3737

3838
findManyByIds(
39-
ids: ObjectId[],
39+
ids: (ObjectId | string)[],
4040
options?: Options
4141
): Promise<(TData | null | undefined)[]>
4242

43-
deleteFromCacheById(id: ObjectId): Promise<void>
43+
deleteFromCacheById(id: ObjectId | string): Promise<void>
4444
}
4545
}

package-lock.json

+45-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"apollo-datasource": "^0.3.1",
2222
"apollo-server-caching": "^0.3.1",
2323
"apollo-server-errors": "^2.4.1",
24+
"bson": "^4.1.0",
2425
"dataloader": "^1.4.0"
2526
},
2627
"devDependencies": {

src/__tests__/cache.test.js

+32-26
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
import { InMemoryLRUCache } from 'apollo-server-caching'
22
import wait from 'waait'
3+
import { ObjectId } from 'mongodb'
4+
import { EJSON } from 'bson'
35

4-
import { createCachingMethods } from '../cache'
6+
import { createCachingMethods, idToString } from '../cache'
57

68
const docs = {
79
id1: {
810
_id: 'id1'
11+
// _id: ObjectId()
912
},
1013
id2: {
11-
_id: 'id2'
12-
},
13-
id3: {
14-
_id: 'id3'
14+
_id: ObjectId()
1515
}
1616
}
1717

1818
const collectionName = 'test'
19-
const cacheKey = id => `mongo-${collectionName}-${id}`
19+
const cacheKey = id => `mongo-${collectionName}-${idToString(id)}`
2020

2121
describe('createCachingMethods', () => {
2222
let collection
@@ -29,7 +29,13 @@ describe('createCachingMethods', () => {
2929
find: jest.fn(({ _id: { $in: ids } }) => ({
3030
toArray: () =>
3131
new Promise(resolve => {
32-
setTimeout(() => resolve(ids.map(id => docs[id])), 0)
32+
setTimeout(
33+
() =>
34+
resolve(
35+
ids.map(id => (id === docs.id1._id ? docs.id1 : docs.id2))
36+
),
37+
0
38+
)
3339
})
3440
}))
3541
}
@@ -46,61 +52,61 @@ describe('createCachingMethods', () => {
4652
})
4753

4854
it('finds one', async () => {
49-
const doc = await api.findOneById('id1')
55+
const doc = await api.findOneById(docs.id1._id)
5056
expect(doc).toBe(docs.id1)
5157
expect(collection.find.mock.calls.length).toBe(1)
5258
})
5359

5460
it('finds two with batching', async () => {
55-
const foundDocs = await api.findManyByIds(['id2', 'id3'])
61+
const foundDocs = await api.findManyByIds([docs.id1._id, docs.id2._id])
5662

57-
expect(foundDocs[0]).toBe(docs.id2)
58-
expect(foundDocs[1]).toBe(docs.id3)
63+
expect(foundDocs[0]).toBe(docs.id1)
64+
expect(foundDocs[1]).toBe(docs.id2)
5965

6066
expect(collection.find.mock.calls.length).toBe(1)
6167
})
6268

6369
// TODO why doesn't this pass?
6470
// it.only(`doesn't cache without ttl`, async () => {
65-
// await api.findOneById('id1')
66-
// await api.findOneById('id1')
71+
// await api.findOneById(docs.id1._id)
72+
// await api.findOneById(docs.id1._id)
6773

6874
// expect(collection.find.mock.calls.length).toBe(2)
6975
// })
7076

7177
it(`doesn't cache without ttl`, async () => {
72-
await api.findOneById('id1')
78+
await api.findOneById(docs.id1._id)
7379

74-
const value = await cache.get(cacheKey('id1'))
80+
const value = await cache.get(cacheKey(docs.id1._id))
7581
expect(value).toBeUndefined()
7682
})
7783

7884
it(`caches`, async () => {
79-
await api.findOneById('id1', { ttl: 1 })
80-
const value = await cache.get(cacheKey('id1'))
81-
expect(JSON.parse(value)).toEqual(docs.id1)
85+
await api.findOneById(docs.id1._id, { ttl: 1 })
86+
const value = await cache.get(cacheKey(docs.id1._id))
87+
expect(value).toEqual(EJSON.stringify(docs.id1))
8288

83-
await api.findOneById('id1')
89+
await api.findOneById(docs.id1._id)
8490
expect(collection.find.mock.calls.length).toBe(1)
8591
})
8692

8793
it(`caches with ttl`, async () => {
88-
await api.findOneById('id1', { ttl: 1 })
94+
await api.findOneById(docs.id1._id, { ttl: 1 })
8995
await wait(1001)
9096

91-
const value = await cache.get(cacheKey('id1'))
97+
const value = await cache.get(cacheKey(docs.id1._id))
9298
expect(value).toBeUndefined()
9399
})
94100

95101
it(`deletes from cache`, async () => {
96-
await api.findOneById('id1', { ttl: 1 })
102+
await api.findOneById(docs.id1._id, { ttl: 1 })
97103

98-
const valueBefore = await cache.get(cacheKey('id1'))
99-
expect(JSON.parse(valueBefore)).toEqual(docs.id1)
104+
const valueBefore = await cache.get(cacheKey(docs.id1._id))
105+
expect(valueBefore).toEqual(EJSON.stringify(docs.id1))
100106

101-
await api.deleteFromCacheById('id1')
107+
await api.deleteFromCacheById(docs.id1._id)
102108

103-
const valueAfter = await cache.get(cacheKey('id1'))
109+
const valueAfter = await cache.get(cacheKey(docs.id1._id))
104110
expect(valueAfter).toBeUndefined()
105111
})
106112
})

src/cache.js

+15-12
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
import DataLoader from 'dataloader'
2+
import { ObjectId } from 'mongodb'
3+
import { EJSON } from 'bson'
4+
5+
import { getCollection } from './helpers'
6+
7+
export const idToString = id => (id instanceof ObjectId ? id.toHexString() : id)
28

3-
import { getCollection, isModel } from './helpers'
4-
import { ObjectId } from 'bson'
59
// https://github.com/graphql/dataloader#batch-function
610
const orderDocs = ids => docs => {
711
const idMap = {}
812
docs.forEach(doc => {
9-
idMap[doc._id] = doc
13+
idMap[idToString(doc._id)] = doc
1014
})
11-
return ids.map(id => idMap[id])
15+
return ids.map(id => idMap[idToString(id)])
1216
}
1317

1418
export const createCachingMethods = ({ collection, model, cache }) => {
@@ -29,18 +33,17 @@ export const createCachingMethods = ({ collection, model, cache }) => {
2933

3034
const methods = {
3135
findOneById: async (id, { ttl } = {}) => {
32-
const _id = id instanceof ObjectId ? id.toHexString() : id
33-
const key = cachePrefix + _id
36+
const key = cachePrefix + idToString(id)
3437

3538
const cacheDoc = await cache.get(key)
3639
if (cacheDoc) {
37-
return JSON.parse(cacheDoc)
40+
return EJSON.parse(cacheDoc)
3841
}
3942

40-
const doc = await loader.load(_id)
43+
const doc = await loader.load(id)
4144
if (Number.isInteger(ttl)) {
4245
// https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-caching#apollo-server-caching
43-
cache.set(key, JSON.stringify(doc), { ttl })
46+
cache.set(key, EJSON.stringify(doc), { ttl })
4447
}
4548

4649
return doc
@@ -49,9 +52,9 @@ export const createCachingMethods = ({ collection, model, cache }) => {
4952
return Promise.all(ids.map(id => methods.findOneById(id, { ttl })))
5053
},
5154
deleteFromCacheById: async id => {
52-
const _id = id instanceof ObjectId ? id.toHexString() : id
53-
loader.clear(_id)
54-
await cache.delete(cachePrefix + _id)
55+
const stringId = idToString(id)
56+
loader.clear(stringId)
57+
await cache.delete(cachePrefix + stringId)
5558
}
5659
}
5760

0 commit comments

Comments
 (0)