UNPKG

7.56 kBJavaScriptView Raw
1/**
2* Glad.cache is available for general caching and is different than the controller cache.
3* Glad.cache is available anywhere in your code base, whereas the controller cache is meant as a
4* endpoint level cache. Use Glad.cache when you need to cache things that live outside the controller.
5*
6* For example, say you have a class that finds hotels on a given date, close to a city center and you want it cached for an hour.
7* We'll be using the resolve method which is nice if your method returns a promise. By passing in the resolver of your method, the cache
8* retrieval will automatically resolve it for you if there is a hit, otherwise it calls the function passed in `doHotelQuery` (in the example below).
9* the function `doHotelQuery` receives a cache method that you can call to cache the results for the next time around.
10* ```javascript
11* getHotelsNearCityCenterOnDate (citySlug = '', date = new Date().toString()) {
12* return new Promise ( function (resolve, reject) {
13* let key = `getHotelsNearCityCenterOnDate_${citySlug}_${date}`;
14* Glad.cache.resolve(key, resolve, 60 * 60).miss(function doHotelQuery (cache) {
15* yourHotelQuery().then(results => {
16* cache(results);
17* resolve(results);
18* }).catch(reject);
19* })
20* });
21* }
22* ```
23*
24* The same thing, using get and store.
25* ```javascript
26* getHotelsNearCityCenterOnDate (citySlug, date) {
27* return new Promise ( function (resolve, reject) {
28* let key = `getHotelsNearCityCenterOnDate_${citySlug}_${date}`;
29* Glad.cache.get(key).then(hotels => {
30* if (hotels) {
31* resolve(hotels);
32* } else {
33* yourHotelQuery().then(results => {
34* Glad.cache.store(key, results, 60 * 60);
35* resolve(results);
36* }).catch(reject);
37* }
38* })
39* });
40* }
41* ```
42*
43* In effort to make life better on this planet,
44* Including `GLAD_ENV=development` will disable caching by default
45*
46* In effort to test caching in development, just set the disabled flag.
47*
48* From Glad
49* ```javascript
50* Glad.cache.disabled = false; // enables the cache on development
51* ```
52*/
53
54const args = require('optimist').argv;
55
56class Cache {
57
58 /**
59 * Create a Cache instance. (Private, the instance is available at Glad.cache)
60 * @param {Server} server - The Server Object.
61 * @param {Project} project - The Project Object.
62 */
63 constructor (server, project) {
64 this.redis = server.redis;
65 this.project = project;
66 this.disabled = args['disable-cache'] || (!args['enable-cache'] && project.development);
67 }
68
69 /**
70 * Gets a value from the cache.
71 * ### Example
72 * ```javascript
73 * myCache.get('/user/5/analytics')
74 * .then(cachedData => doStuff(cachedData))
75 * .catch(err => ohNo(err));
76 * ```
77 * @param {string} name - The name of the cache key to retrieve
78 * @returns {object|array|string|number} - The item being stored in cache
79 */
80 get (name) {
81 return new Promise( (resolve, reject) => {
82 this.redis.get(name, (err, data) => {
83 if (err) return reject(err);
84 try {
85 return resolve( (data || false) && JSON.parse(data));
86 } catch (e) {
87 return resolve(data || false);
88 }
89 });
90 });
91 }
92
93 /**
94 * Adds an item to the cache
95 *
96 * Example:
97 * ```javascript
98 * let { cache } = require('glad');
99 * cache.store('latestAnalytics', { math: 'Numbers and stuff' }, 604800).then(redisResponse => cool(redisResponse)).catch(err => shucks(err));
100 * ```
101 * @param {string} name - The name of the cache key to set
102 * @param {object|string} data - The data to cache
103 * @param {number} time - Time in seconds until this item expires (defaults to 24 hours)
104 * @returns {object|array|string|number} - The item being stored in cache
105 */
106 store (name, data, time = 864e2) {
107
108 if (this.disabled) {
109 return Promise.resolve();
110 }
111
112 return new Promise( (resolve, reject) => {
113 if (typeof data === typeof {}) {
114 this.redis.setex(name, time, JSON.stringify(data), resolve);
115 } else if (data) {
116 this.redis.setex(name, time, data, resolve);
117 } else {
118 reject(`
119 You are trying to cache data,
120 but you did not provide data.\n
121 This request item not be cached.
122 `);
123 }
124 });
125 }
126
127 /**
128 * Checks for an item in the cache. If it exists, then `resolve` is called with the cached value.
129 * Otherwise, the method passed in will get executed and receives a cache function
130 * that can be used to add the item to the cache next time.
131 *
132 * Usage: (Same as above example)
133 * ```javascript
134 * getHotelsNearCityCenterOnDate (citySlug = '', date = new Date().toString()) {
135 * return new Promise ( function (resolve, reject) {
136 * let key = `getHotelsNearCityCenterOnDate_${citySlug}_${date}`;
137 * Glad.cache.resolve(key, resolve).miss(function doHotelQuery (cache) {
138 * yourHotelQuery().then(results => {
139 * cache(results);
140 * resolve(results);
141 * }).catch(reject);
142 * })
143 * });
144 * }
145 * ```
146 *
147 * @param {string} name - The name of the cache key to set
148 * @param {Promise.resolve} resolver - the resolver to call if the data is available
149 * @param {number} time - Time in seconds until this item expires (defaults to 24 hours)
150 * @returns {object} - A chainable miss method
151 */
152 resolve (name, resolve, time) {
153
154 if (typeof name === typeof {}) {
155 name = name.url;
156 }
157
158 let chain = {
159 __method () {},
160 miss (method) {
161 this.__method = method;
162 }
163 };
164
165 this.redis.get(name, (err, data) => {
166
167 if (err) {
168 throw new Error(err);
169 }
170
171 if (data) {
172 try {
173 return resolve( (data || false) && JSON.parse(data));
174 } catch (e) {
175 return resolve(data || false);
176 }
177 } else {
178 chain.__method(cacheData => {
179 this.store(name, cacheData, time);
180 });
181 }
182 });
183
184 return chain;
185 }
186
187 /**
188 * Clears the cache for a key, or everything.
189 * @param {string} name - Optional, the name of the key to expire. If none is provided, all of the caches are wiped out. (careful!)
190 * @returns {boolean} - If the operation was successful / keys were removed * see (`redis.del` and `redis.flushall`).
191 */
192 clear (name = false) {
193 return new Promise( resolve => {
194 if (name) {
195 return this.redis.del(name, resolve);
196 } else {
197 return this.redis.flushall(resolve);
198 }
199 });
200 }
201
202 /**
203 * Clears keys matching a pattern. This method can be memory intensive depending on how many keys you have in redis.
204 * It requires all keys that match the pattern to be loaded into memory. (Just the key names).
205 */
206 clearWhere (pattern) {
207 return new Promise( (resolve, reject) => {
208 this.list(pattern).then(keys => {
209 let queue = function () {
210 if (keys.length) {
211 this.clear(keys.shift()).then(queue).catch(err => {
212 reject({err: err, remainingKeys: keys});
213 });
214 } else {
215 resolve(true);
216 }
217 }.bind(this);
218 queue();
219 }).catch(reject);
220 });
221 }
222
223 /**
224 * Lists all of the keys in the cache.
225 * @param {string} pattern - Optional, a redis pattern to match against. Defaults to * (So be careful!)
226 */
227 list (pattern) {
228 return new Promise( (resolve, reject) => {
229 this.redis.keys(pattern || '*', (err, data) => err ? reject(err) : resolve(data));
230 });
231 }
232
233}
234
235module.exports = Cache;