-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
af29eb1
commit ca204df
Showing
2 changed files
with
204 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
const { DataSource } = require('apollo-datasource'); | ||
const hash = require('object-hash'); | ||
|
||
/** | ||
* An ArangoDb implementation of the Apollo DataSource. | ||
* | ||
* @class ArangoDataSource | ||
* @extends {DataSource} | ||
*/ | ||
class ArangoDataSource extends DataSource { | ||
/** | ||
* Creates an instance of ArangoDataSource. | ||
* @param {Database} db | ||
* @memberof ArangoDataSource | ||
*/ | ||
constructor(db) { | ||
super(); | ||
this.db = db; | ||
} | ||
|
||
/** | ||
* Initializes the DataSource. | ||
* | ||
* Called at the beginning of each request. | ||
* | ||
* @param {*} config A configuration object that provides access to the shared cache and request context. | ||
* @memberof ArangoDataSource | ||
*/ | ||
initialize(config) { | ||
this.cache = config.cache; | ||
} | ||
|
||
/** | ||
* Query the database. | ||
* | ||
* Options: | ||
* useCache: check the cache first and update | ||
* | ||
* @param {*} query The query to use | ||
* @returns {*} A list of results that match the query | ||
* @memberof ArangoDataSource | ||
*/ | ||
async query(query, { useCache } = { useCache: true }) { | ||
if (useCache) { | ||
const cachedValue = await this.queryCached(query); | ||
|
||
if (cachedValue) { | ||
return cachedValue; | ||
} | ||
} | ||
|
||
const cursor = await this.db.query(query); | ||
const result = await cursor.all(); | ||
|
||
if (useCache && result) { | ||
await this.addToCache(query, result); | ||
} | ||
|
||
return result; | ||
} | ||
|
||
/** | ||
* Compute the key for a given query | ||
* | ||
* @private | ||
* @param {*} query The query to compute the key for | ||
* @returns {string} A string key for the query | ||
* @memberof ArangoDataSource | ||
*/ | ||
getCacheKeyForQuery(query) { | ||
return hash(query); | ||
} | ||
|
||
/** | ||
* Check the cache for previously saved results for the given query | ||
* | ||
* @private | ||
* @param {*} query The query to check for | ||
* @returns {*} The results saved to the cache | ||
* @memberof ArangoDataSource | ||
*/ | ||
async queryCached(query) { | ||
const key = this.getCacheKeyForQuery(query); | ||
return this.cache.get(key); | ||
} | ||
|
||
/** | ||
* Add a result set to the cache using the query as a key | ||
* | ||
* @param {*} query The query to key by | ||
* @param {*} result The results to add to the cache | ||
* @memberof ArangoDataSource | ||
*/ | ||
async addToCache(query, result) { | ||
const key = this.getCacheKeyForQuery(query); | ||
await this.cache.set(key, result); | ||
} | ||
} | ||
|
||
module.exports = { | ||
ArangoDataSource: ArangoDataSource | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
const { ArangoDataSource } = require('.'); | ||
const { aql } = require('arangojs'); | ||
const hash = require('object-hash'); | ||
|
||
describe('ArangDataSource', () => { | ||
test('it queries the db (cache miss)', async () => { | ||
const result = { results: 'output' }; | ||
const db = createDb(result); | ||
const config = createConfig(); | ||
const query = aql`DOCUMENT()`; | ||
|
||
const datasource = new ArangoDataSource(db); | ||
|
||
datasource.initialize(config); | ||
|
||
const output = await datasource.query(query); | ||
expect(output).toEqual(result); | ||
expect(db.query).toHaveBeenCalledTimes(1); | ||
expect(config.cache.set).toHaveBeenCalledTimes(1); | ||
expect(config.cache.set).toHaveBeenCalledWith(hash(query), result); | ||
}); | ||
|
||
test('it queries the db (cache hit)', async () => { | ||
const result = { results: 'output' }; | ||
const query = aql`DOCUMENT()`; | ||
const key = hash(query); | ||
const db = createDb(result); | ||
const config = createConfig({ | ||
[key]: result | ||
}); | ||
|
||
const datasource = new ArangoDataSource(db); | ||
|
||
datasource.initialize(config); | ||
|
||
const output = await datasource.query(query); | ||
expect(output).toEqual(result); | ||
expect(db.query).toHaveBeenCalledTimes(0); | ||
expect(config.cache.set).toHaveBeenCalledTimes(0); | ||
expect(config.cache.get).toHaveBeenCalledTimes(1); | ||
expect(config.cache.get).toHaveReturnedWith(Promise.resolve(result)); | ||
}); | ||
|
||
test('it caches a successful query', async () => { | ||
const result = { results: 'output' }; | ||
const db = createDb(result); | ||
const config = createConfig(); | ||
const query = aql`DOCUMENT()`; | ||
|
||
const datasource = new ArangoDataSource(db); | ||
|
||
datasource.initialize(config); | ||
|
||
for (let i = 1; i <= 3; i++) { | ||
const output = await datasource.query(query); | ||
expect(output).toEqual(result); | ||
expect(db.query).toHaveBeenCalledTimes(1); | ||
|
||
expect(config.cache.get).toHaveBeenCalledTimes(i); | ||
expect(config.cache.get).toHaveReturnedWith(Promise.resolve(result)); | ||
|
||
expect(config.cache.set).toHaveBeenCalledTimes(1); | ||
expect(config.cache.set).toHaveBeenCalledWith(hash(query), result); | ||
} | ||
}); | ||
|
||
test('it ignores the cache', async () => { | ||
const result = { results: 'output' }; | ||
const db = createDb(result); | ||
const config = createConfig(); | ||
const query = aql`DOCUMENT()`; | ||
|
||
const datasource = new ArangoDataSource(db); | ||
|
||
datasource.initialize(config); | ||
|
||
const output = await datasource.query(query, { useCache: false }); | ||
expect(output).toEqual(result); | ||
expect(db.query).toHaveBeenCalledTimes(1); | ||
expect(config.cache.set).toHaveBeenCalledTimes(0); | ||
expect(config.cache.get).toHaveBeenCalledTimes(0); | ||
}); | ||
}); | ||
|
||
function createDb(queryResults) { | ||
return { | ||
query: jest.fn().mockResolvedValue({ | ||
all: () => queryResults | ||
}) | ||
}; | ||
} | ||
|
||
function createConfig(cache = {}) { | ||
return { | ||
cache: { | ||
get: jest.fn().mockImplementation(async key => cache[key]), | ||
set: jest | ||
.fn() | ||
.mockImplementation(async (key, result) => (cache[key] = result)) | ||
} | ||
}; | ||
} |