Skip to content

Commit

Permalink
feat(datasource): add datasource
Browse files Browse the repository at this point in the history
  • Loading branch information
danwkennedy committed May 14, 2019
1 parent af29eb1 commit ca204df
Show file tree
Hide file tree
Showing 2 changed files with 204 additions and 0 deletions.
102 changes: 102 additions & 0 deletions arango-datasource.js
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
};
102 changes: 102 additions & 0 deletions arango-datasource.test.js
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))
}
};
}

0 comments on commit ca204df

Please sign in to comment.