Demo for query storing

Change-Id: I947bcac841992c3f6cfd01ab337c265b0d01cb70
diff --git a/node_modules/cacheable-request/src/index.js b/node_modules/cacheable-request/src/index.js
new file mode 100644
index 0000000..1935b2d
--- /dev/null
+++ b/node_modules/cacheable-request/src/index.js
@@ -0,0 +1,155 @@
+'use strict';
+
+const EventEmitter = require('events');
+const urlLib = require('url');
+const normalizeUrl = require('normalize-url');
+const getStream = require('get-stream');
+const CachePolicy = require('http-cache-semantics');
+const Response = require('responselike');
+const lowercaseKeys = require('lowercase-keys');
+const cloneResponse = require('clone-response');
+const Keyv = require('keyv');
+
+class CacheableRequest {
+	constructor(request, cacheAdapter) {
+		if (typeof request !== 'function') {
+			throw new TypeError('Parameter `request` must be a function');
+		}
+
+		this.cache = new Keyv({
+			uri: typeof cacheAdapter === 'string' && cacheAdapter,
+			store: typeof cacheAdapter !== 'string' && cacheAdapter,
+			namespace: 'cacheable-request'
+		});
+
+		return this.createCacheableRequest(request);
+	}
+
+	createCacheableRequest(request) {
+		return (opts, cb) => {
+			if (typeof opts === 'string') {
+				opts = urlLib.parse(opts);
+			}
+			opts = Object.assign({
+				headers: {},
+				method: 'GET',
+				cache: true,
+				strictTtl: false,
+				automaticFailover: false
+			}, opts);
+			opts.headers = lowercaseKeys(opts.headers);
+
+			const ee = new EventEmitter();
+			const url = normalizeUrl(urlLib.format(opts));
+			const key = `${opts.method}:${url}`;
+			let revalidate = false;
+			let madeRequest = false;
+
+			const makeRequest = opts => {
+				madeRequest = true;
+				const handler = response => {
+					if (revalidate) {
+						const revalidatedPolicy = CachePolicy.fromObject(revalidate.cachePolicy).revalidatedPolicy(opts, response);
+						if (!revalidatedPolicy.modified) {
+							const headers = revalidatedPolicy.policy.responseHeaders();
+							response = new Response(response.statusCode, headers, revalidate.body, revalidate.url);
+							response.cachePolicy = revalidatedPolicy.policy;
+							response.fromCache = true;
+						}
+					}
+
+					if (!response.fromCache) {
+						response.cachePolicy = new CachePolicy(opts, response);
+						response.fromCache = false;
+					}
+
+					let clonedResponse;
+					if (opts.cache && response.cachePolicy.storable()) {
+						clonedResponse = cloneResponse(response);
+						getStream.buffer(response)
+							.then(body => {
+								const value = {
+									cachePolicy: response.cachePolicy.toObject(),
+									url: response.url,
+									statusCode: response.fromCache ? revalidate.statusCode : response.statusCode,
+									body
+								};
+								const ttl = opts.strictTtl ? response.cachePolicy.timeToLive() : undefined;
+								return this.cache.set(key, value, ttl);
+							})
+							.catch(err => ee.emit('error', new CacheableRequest.CacheError(err)));
+					} else if (opts.cache && revalidate) {
+						this.cache.delete(key)
+							.catch(err => ee.emit('error', new CacheableRequest.CacheError(err)));
+					}
+
+					ee.emit('response', clonedResponse || response);
+					if (typeof cb === 'function') {
+						cb(clonedResponse || response);
+					}
+				};
+
+				try {
+					const req = request(opts, handler);
+					ee.emit('request', req);
+				} catch (err) {
+					ee.emit('error', new CacheableRequest.RequestError(err));
+				}
+			};
+
+			const get = opts => Promise.resolve()
+				.then(() => opts.cache ? this.cache.get(key) : undefined)
+				.then(cacheEntry => {
+					if (typeof cacheEntry === 'undefined') {
+						return makeRequest(opts);
+					}
+
+					const policy = CachePolicy.fromObject(cacheEntry.cachePolicy);
+					if (policy.satisfiesWithoutRevalidation(opts)) {
+						const headers = policy.responseHeaders();
+						const response = new Response(cacheEntry.statusCode, headers, cacheEntry.body, cacheEntry.url);
+						response.cachePolicy = policy;
+						response.fromCache = true;
+
+						ee.emit('response', response);
+						if (typeof cb === 'function') {
+							cb(response);
+						}
+					} else {
+						revalidate = cacheEntry;
+						opts.headers = policy.revalidationHeaders(opts);
+						makeRequest(opts);
+					}
+				});
+
+			this.cache.on('error', err => ee.emit('error', new CacheableRequest.CacheError(err)));
+
+			get(opts).catch(err => {
+				if (opts.automaticFailover && !madeRequest) {
+					makeRequest(opts);
+				}
+				ee.emit('error', new CacheableRequest.CacheError(err));
+			});
+
+			return ee;
+		};
+	}
+}
+
+CacheableRequest.RequestError = class extends Error {
+	constructor(err) {
+		super(err.message);
+		this.name = 'RequestError';
+		Object.assign(this, err);
+	}
+};
+
+CacheableRequest.CacheError = class extends Error {
+	constructor(err) {
+		super(err.message);
+		this.name = 'CacheError';
+		Object.assign(this, err);
+	}
+};
+
+module.exports = CacheableRequest;