UNPKG

9.89 kBJavaScriptView Raw
1'use strict';
2
3const typeOf = require('kind-of');
4const Emitter = require('@sellside/emitter');
5const visit = require('collection-visit');
6const hasOwn = require('has-own-deep');
7const union = require('union-value');
8const del = require('unset-value');
9const get = require('get-value');
10const set = require('set-value');
11
12/**
13 * Create an instance of `CacheBase`.
14 *
15 * ```js
16 * const app = new CacheBase();
17 * ```
18 * @param {String|Object} `prop` (optional) Property name to use for the cache, or the object to initialize with.
19 * @param {Object} `cache` (optional) An object to initialize with.
20 * @constructor
21 * @api public
22 */
23
24class CacheBase extends Emitter {
25 constructor(prop, cache) {
26 super();
27
28 if (typeof prop !== 'string') {
29 cache = prop || cache;
30 prop = 'cache';
31 }
32
33 Reflect.defineProperty(this, 'prop', { value: prop });
34 this[this.prop] = {};
35
36 if (cache) {
37 this.set(cache);
38 }
39 }
40
41 /**
42 * Assign `value` to `key`. Also emits `set` with the key and value.
43 *
44 * ```js
45 * app.on('set', function(key, val) {
46 * // do something when `set` is emitted
47 * });
48 *
49 * app.set('admin', true);
50 *
51 * // also takes an object or an array of objects
52 * app.set({ name: 'Brian' });
53 * app.set([{ foo: 'bar' }, { baz: 'quux' }]);
54 * console.log(app);
55 * //=> { name: 'Brian', foo: 'bar', baz: 'quux' }
56 * ```
57 * @name .set
58 * @emits `set` with `key` and `value` as arguments.
59 * @param {String|Array} `key` The name of the property to set. Dot-notation may be used to set nested properties.
60 * @param {any} `value`
61 * @return {Object} Returns the instance for chaining.
62 * @api public
63 */
64
65 set(key, ...rest) {
66 if (isObject(key) || (rest.length === 0 && Array.isArray(key))) {
67 return this.visit('set', key, ...rest);
68 }
69 if (Array.isArray(key)) key = key.join('.');
70 set(this[this.prop], key, ...rest);
71 this.emit('set', key, ...rest);
72 return this;
73 }
74
75 /**
76 * Return the value of `key`.
77 *
78 * ```js
79 * app.set('a.b.c', 'd');
80 * app.get('a.b');
81 * //=> { c: 'd' }
82 * ```
83 * @name .get
84 * @emits `get` with `key` and `value` as arguments.
85 * @param {String|Array} `key` The name of the property to get. Dot-notation may be used to set nested properties.
86 * @return {any} Returns the value of `key`
87 * @api public
88 */
89
90 get(key) {
91 if (Array.isArray(key)) key = key.join('.');
92 let val = get(this[this.prop], key);
93
94 if (typeof val === 'undefined' && this.defaults) {
95 val = get(this.defaults, key);
96 }
97
98 this.emit('get', key, val);
99 return val;
100 }
101
102 /**
103 * Create a property on the cache with the given `value` only if it doesn't
104 * already exist.
105 *
106 * ```js
107 * console.log(app.cache); //=> {}
108 * app.set('one', { foo: 'bar' });
109 * app.prime('one', { a: 'b' });
110 * app.prime('two', { c: 'd' });
111 * console.log(app.cache.one); //=> { foo: 'bar' }
112 * console.log(app.cache.two); //=> { c: 'd' }
113 * ```
114 * @name .prime
115 * @param {String} `key` Property name or object path notation.
116 * @param {any} `val`
117 * @return {Object} Returns the instance for chaining.
118 * @api public
119 */
120
121 prime(key, ...rest) {
122 if (isObject(key) || (rest.length === 0 && Array.isArray(key))) {
123 return this.visit('prime', key, ...rest);
124 }
125 if (Array.isArray(key)) key = key.join('.');
126 if (!this.has(key)) {
127 this.set(key, ...rest);
128 }
129 return this;
130 }
131
132 /**
133 * Set a default value to be used when `.get()` is called and the value is not defined
134 * on the cache. Returns a value from the defaults when only a key is passed.
135 *
136 * ```js
137 * app.set('foo', 'xxx');
138 * app.default('foo', 'one');
139 * app.default('bar', 'two');
140 * app.default('baz', 'three');
141 * app.set('baz', 'zzz');
142 *
143 * console.log(app.get('foo'));
144 * //=> 'xxx'
145 *
146 * console.log(app.get('bar'));
147 * //=> 'two'
148 *
149 * console.log(app.get('baz'));
150 * //=> 'zzz'
151 *
152 * console.log(app);
153 * // CacheBase {
154 * // cache: { foo: 'xxx', bar: 'two', baz: 'zzz' },
155 * // defaults: { foo: 'one', bar: 'two', baz: 'three' } }
156 * ```
157 * @name .default
158 * @param {String|Array} `key` The name of the property to set. Dot-notation may be used to set nested properties.
159 * @param {any} `value` (optional) The value to set on the defaults object.
160 * @return {Object} Returns the instance for chaining.
161 * @api public
162 */
163
164 default(key, ...rest) {
165 this.defaults = this.defaults || {};
166
167 if (isObject(key) || (rest.length === 0 && Array.isArray(key))) {
168 return this.visit('default', key, ...rest);
169 }
170
171 if (Array.isArray(key)) key = key.join('.');
172 if (!isString(key)) {
173 throw new TypeError('expected "key" to be a string, object or array');
174 }
175
176 if (rest.length === 0) {
177 return get(this.defaults, key);
178 }
179
180 set(this.defaults, key, ...rest);
181 this.emit('default', key, rest);
182 return this;
183 }
184
185 /**
186 * Set an array of unique values on cache `key`.
187 *
188 * ```js
189 * app.union('a.b.c', 'foo');
190 * app.union('a.b.c', 'bar');
191 * app.union('a.b.c', ['bar', 'baz']);
192 * console.log(app.get('a'));
193 * //=> { b: { c: ['foo', 'bar', 'baz'] } }
194 * ```
195 * @name .union
196 * @param {String|Array} `key` The name of the property to union. Dot-notation may be used to set nested properties.
197 * @param {any} `value`
198 * @return {Object} Returns the instance for chaining.
199 * @api public
200 */
201
202 union(key, ...rest) {
203 if (Array.isArray(key)) key = key.join('.');
204 union(this[this.prop], key, ...rest);
205 this.emit('union', ...rest);
206 return this;
207 }
208
209 /**
210 * Return true if the value of property `key` is not `undefined`.
211 *
212 * ```js
213 * app.set('foo', true);
214 * app.set('baz', null);
215 * app.set('bar', undefined);
216 *
217 * app.has('foo'); //=> true
218 * app.has('bar'); //=> true
219 * app.has('baz'); //=> false
220 * ```
221 * @name .has
222 * @param {String|Array} `key` The name of the property to check. Dot-notation may be used to set nested properties.
223 * @return {Boolean}
224 * @api public
225 */
226
227 has(key) {
228 if (Array.isArray(key)) key = key.join('.');
229 return typeof get(this[this.prop], key) !== 'undefined';
230 }
231
232 /**
233 * Returns true if the specified property is an own (not inherited) property.
234 * Similar to [.has()](#has), but returns true if the key exists, even if the
235 * value is `undefined`.
236 *
237 * ```js
238 * app.set('a.b.c', 'd');
239 * app.set('x', false);
240 * app.set('y', null);
241 * app.set('z', undefined);
242 *
243 * app.hasOwn('a'); //=> true
244 * app.hasOwn('b'); //=> true
245 * app.hasOwn('c'); //=> true
246 * app.hasOwn('a.b.c'); //=> true
247 * app.hasOwn('x'); //=> true
248 * app.hasOwn('y'); //=> true
249 * app.hasOwn('z'); //=> true
250 * app.hasOwn('lslsls'); //=> false
251 * ```
252 * @name .hasOwn
253 * @param {String} `key`
254 * @return {Boolean} Returns true if object `key` exists. Dot-notation may be used to set nested properties.
255 * @api public
256 */
257
258 hasOwn(key) {
259 if (Array.isArray(key)) key = key.join('.');
260 return hasOwn(this[this.prop], key);
261 }
262
263 /**
264 * Delete one or more properties from the instance.
265 *
266 * ```js
267 * // setup a listener to update a property with a default
268 * // value when it's deleted by the user
269 * app.on('del', key => app.set(key, app.default(key)));
270 *
271 * app.del(); // delete all properties on the cache
272 * // or
273 * app.del('foo');
274 * // or an array of keys
275 * app.del(['foo', 'bar']);
276 * ```
277 * @name .del
278 * @emits `del` with the `key` as the only argument.
279 * @param {string} `key` The name of the property to delete. Dot-notation may be used to delete nested properties. This method does not accept key as an array.
280 * @return {Object} Returns the instance for chaining.
281 * @api public
282 */
283
284 del(key) {
285 if (!key) return this.clear();
286 del(this[this.prop], key);
287 this.emit('del', key);
288 return this;
289 }
290
291 /**
292 * Reset the entire cache to an empty object. Note that this does not also clear the `defaults`
293 * object, since you can manually do `cache.defaults = {}` if you want to reset that object as well.
294 *
295 * ```js
296 * // clear "defaults" whenever the cache is cleared
297 * app.on('clear', key => (app.defaults = {}));
298 * app.clear();
299 * ```
300 * @name .clear
301 * @api public
302 */
303
304 clear() {
305 this[this.prop] = {};
306 this.emit('clear');
307 return this;
308 }
309
310 /**
311 * Visit (or map visit) the specified method (`key`) over the properties in the
312 * given object or array.
313 *
314 * @name .visit
315 * @param {String|Array} `key` The name of the method to visit.
316 * @param {Object|Array} `val` The object or array to iterate over.
317 * @return {Object} Returns the instance for chaining.
318 * @api public
319 */
320
321 visit(key, ...rest) {
322 visit(this, key, ...rest);
323 return this;
324 }
325
326 /**
327 * Gets an array of names of all enumerable properties on the cache.
328 *
329 * ```js
330 * const app = new CacheBase();
331 * app.set('user', true);
332 * app.set('admin', false);
333 *
334 * console.log(app.keys);
335 * //=> ['user', 'admin']
336 * ```
337 * @name .keys
338 * @api public
339 */
340
341 get keys() {
342 return Object.keys(this[this.prop]);
343 }
344
345 /**
346 * Gets the length of [keys](#keys).
347 *
348 * ```js
349 * const app = new CacheBase();
350 * app.set('user', true);
351 * app.set('admin', false);
352 *
353 * console.log(app.size);
354 * //=> 2
355 * ```
356 * @name .size
357 * @api public
358 */
359
360 get size() {
361 return this.keys.length;
362 }
363}
364
365/**
366 * Returns true if `value` is a non-empty string.
367 */
368
369function isString(value) {
370 return typeof value === 'string' && value !== '';
371}
372
373/**
374 * Returns true if `value` is an object
375 */
376
377function isObject(value) {
378 return typeOf(value) === 'object';
379}
380
381/**
382 * Expose `CacheBase`
383 */
384
385module.exports = CacheBase;
386
\No newline at end of file