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 |
|
54 | const args = require('optimist').argv;
|
55 |
|
56 | class 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 |
|
235 | module.exports = Cache;
|