1 | 'use strict';
|
2 |
|
3 | const typeOf = require('kind-of');
|
4 | const Emitter = require('@sellside/emitter');
|
5 | const visit = require('collection-visit');
|
6 | const hasOwn = require('has-own-deep');
|
7 | const union = require('union-value');
|
8 | const del = require('unset-value');
|
9 | const get = require('get-value');
|
10 | const 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 |
|
24 | class 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 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
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);
|
112 | * console.log(app.cache.two);
|
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 | *
|
145 | *
|
146 | * console.log(app.get('bar'));
|
147 | *
|
148 | *
|
149 | * console.log(app.get('baz'));
|
150 | *
|
151 | *
|
152 | * console.log(app);
|
153 | *
|
154 | *
|
155 | *
|
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 | *
|
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');
|
218 | * app.has('bar');
|
219 | * app.has('baz');
|
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');
|
244 | * app.hasOwn('b');
|
245 | * app.hasOwn('c');
|
246 | * app.hasOwn('a.b.c');
|
247 | * app.hasOwn('x');
|
248 | * app.hasOwn('y');
|
249 | * app.hasOwn('z');
|
250 | * app.hasOwn('lslsls');
|
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 | *
|
268 | *
|
269 | * app.on('del', key => app.set(key, app.default(key)));
|
270 | *
|
271 | * app.del();
|
272 | *
|
273 | * app.del('foo');
|
274 | *
|
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 | *
|
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 | *
|
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 | *
|
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 |
|
369 | function isString(value) {
|
370 | return typeof value === 'string' && value !== '';
|
371 | }
|
372 |
|
373 | /**
|
374 | * Returns true if `value` is an object
|
375 | */
|
376 |
|
377 | function isObject(value) {
|
378 | return typeOf(value) === 'object';
|
379 | }
|
380 |
|
381 | /**
|
382 | * Expose `CacheBase`
|
383 | */
|
384 |
|
385 | module.exports = CacheBase;
|
386 |
|
\ | No newline at end of file |