UNPKG

incache

Version:

Powerful key/value in-memory storage or on disk to persist some data

1,245 lines (1,079 loc) 38.7 kB
const helper = require('./helper'); const Flak = require('flak'); const fs = require('fs'); const write = require('write'); const uuid = require('uuid/v1'); const clone = require('clone'); const sizeOf = require('object-sizeof'); const {SAVE_MODE, RECORD} = require('./defined'); /** * @constant SAVE_MODE */ /** * @memberOf SAVE_MODE * @name TERMINATE */ /** * @memberOf SAVE_MODE * @name TIMER */ /** * InCache record * @typedef {Object} InCache~record * @property {string} id - uuid * @property {boolean} isNew - indicates if is a new record * @property {boolean} isPreserved - indicates if record will no longer be editable once created * @property {boolean} toDelete - indicates if record will be deleted after expiry * @property {number} hits - how many times it has been used * @property {Date|null} lastHit - last usage * @property {Date|null} createdOn - creation date * @property {Date|null} updatedOn - update date * @property {Date|null} expiresOn - expiry date * @property {*} value - record value */ /** * InCache recordInfo * @typedef {Object} InCache~recordInfo * @property {string} id - uuid * @property {boolean} isNew - indicates if is a new record * @property {boolean} isPreserved - indicates if record will no longer be editable once created * @property {boolean} toDelete - indicates if record will be deleted after expiry * @property {number} hits - how many times it has been used * @property {Date|null} lastHit - last usage * @property {Date|null} createdOn - creation date * @property {Date|null} updatedOn - update date * @property {Date|null} expiresOn - expiry date */ /** * @class */ class InCache extends Flak { /** * Create instance * @param [opts] {Object} configuration object * @param [opts.maxAge=0] {number} max age in milliseconds. If 0 not expire. (overwritable by `set`) * @param [opts.expires] {Date|string} a Date for expiration. (overwrites `opts.maxAge`, overwritable by `set`) * @param [opts.silent=false] {boolean} if true no event will be triggered. (overwritable by `set`) * @param [opts.deleteOnExpires=true] {boolean} if false, the record will not be deleted after expiry. (overwritable by `set`) * @param [opts.clone=false] {boolean} if true, the object will be cloned before to put it in storage. (overwritable by `set`) * @param [opts.preserve=false] {boolean} if true, you will no longer be able to update the record once created. (overwritable by `set`) * @param [opts.maxRecordNumber=0] {number} the maximum of record number of the cache, if exceeded some records will be deleted. If 0 is disabled * @param [opts.autoLoad=true] {boolean} load cache from disk when instance is created. * @param [opts.autoSave=false] {boolean} if true saves cache in disk when the process is terminated. * @param [opts.autoSaveMode=terminate] {string} there are 2 modes -> "terminate": saves before the process is terminated (server only). "timer": every n seconds checks for new changes and save on disk. * @param [opts.autoSavePeriod=5] {number} period in seconds to check for new changes to save on disk. Works only if `opts.autoSaveMode` is set to 'timer' mode. * @param [opts.filePath=.incache] {string|*} cache file path or key. If is a falsy value, `load` and `save` will always be solved * @param [opts.storeName] {string} store name * @param [opts.share=false] {boolean} if true, use global object as storage * @param [opts.autoRemovePeriod=0] {number} period in seconds to remove expired records. When set, the records will be removed only on check, when 0 it won't run * @param [opts.nullIfNotFound=false] {boolean} calling `get` if the key is not found returns `null`. If false returns `undefined` * @param [opts.save=false] {boolean} **deprecated:** if true saves cache in disk when the process is terminated. Use `autoSave` instead. * @param [opts.global] {Object} **deprecated:** global record configuration * @param [opts.global.silent=false] {boolean} **deprecated:** if true no event will be triggered, use `silent` instead * @param [opts.global.life=0] {number} **deprecated:** max age in seconds. If 0 not expire, use `maxAge` instead * @fires InCache#expired * @fires InCache#change * @fires InCache#exceed * @constructor * @returns {InCache} */ constructor(opts = {}) { super(); Object.defineProperties(this, { _root: { writable: true, enumerable: false }, _storage: { writable: true, enumerable: false }, _memory: { writable: true, enumerable: false }, _opts: { writable: true, enumerable: false }, _timerExpiryCheck: { value: null, writable: true, enumerable: false }, _timerSaveCheck: { value: null, writable: true, enumerable: false }, GLOBAL_KEY: { writable: true, enumerable: false }, _lastChange: { value: null, writable: true, enumerable: false }, _lastChangeDetected: { value: null, writable: true, enumerable: false }, _lastSave: { value: null, writable: true, enumerable: false }, _saving: { value: false, writable: true, enumerable: false }, _loading: { value: false, writable: true, enumerable: false } }); /** * Global key * @type {string} * @ignore */ this.GLOBAL_KEY = '___InCache___storage___global___key___'; /** * InCache default configuration * @ignore */ this.DEFAULT_CONFIG = { storeName: '', autoLoad: true, autoSave: false, autoSaveMode: SAVE_MODE.TERMINATE, autoSavePeriod: 5, save: false, clone: false, preserve: false, deleteOnExpires: true, filePath: '.incache', maxAge: 0, maxRecordNumber: 0, expires: null, silent: false, share: false, autoRemovePeriod: 0, nullIfNotFound: false, global: { silent: false, life: 0 } }; // Defines callback private this._onRemoved = () => { }; this._onCreated = () => { }; this._onUpdated = () => { }; this.on('_change', (by) => { this._lastChange = (new Date()).getTime(); if (!this._opts.silent) this.fire('change', by); }); this.setConfig(opts); //return this; } _checkExceeded() { if (!this._opts.maxRecordNumber) return; let keys = Object.keys(this._memory.data); /* istanbul ignore else */ if (keys.length > this._opts.maxRecordNumber) { let diff = keys.length - this._opts.maxRecordNumber; this.fire('exceed', diff); this.bulkRemove(keys.slice(0, diff), true); } } _importData(data) { /* istanbul ignore else */ if (helper.is(data, 'object')) { let keys = Object.keys(data); if (keys.length) { if (InCache.isRecord(data[keys[0]])) this._memory.data = data; else this.bulkSet(data, true); } } else if (helper.is(data, 'array')) { if (data.length) this.bulkSet(data, true); } else { throw new Error('bad data'); } } /** * Load cache from disk * @param [path=opts.filePath] {string} file path or key (browser scenario) * @fires InCache#beforeLoad * @fires InCache#load * @returns {Promise} * @since 6.0.0 */ load(path = this._opts.filePath) { return new Promise( (resolve, reject) => { if (!path) return resolve(this); if (this._loading) return reject('loading locked'); /* istanbul ignore else */ if (this.fireTheFirst('beforeLoad', this) === false) { return reject(); } this._loading = true; try { let content = helper.isServer() ? fs.readFileSync(path) : window.localStorage.getItem(path); if (content === null) helper.throwError('content cannot is null'); this._importData(JSON.parse(content.toString())); this._loading = false; resolve(this); this.fireAsync('load', null, this); } catch (err) { err = err.message; this._loading = false; reject(err); this.fireAsync('load', err, this); } } ) } /** * Save cache into disk * @param [path=opts.filePath] {string} file path or key (browser scenario) * @fires InCache#beforeSave * @fires InCache#save * @returns {Promise} * @since 6.0.0 */ save(path = this._opts.filePath) { return new Promise( (resolve, reject) => { if (!path) return resolve(this); if (this._saving) return reject('saving locked'); /* istanbul ignore else */ if (this.fireTheFirst('beforeSave', this) === false) { return reject(); } this._saving = true; let dataString = JSON.stringify(this._memory.data); try { if (helper.isServer()) write.sync(path, dataString, {}); else window.localStorage.setItem(path, dataString); this._lastSave = (new Date()).getTime(); this._saving = false; resolve(this); this.fireAsync('save', null, this); } catch (err) { err = err.message; this._saving = false; reject(err); this.fireAsync('save', err, this); } } ) } /** * Set configuration * @param [opts] {Object} configuration object * @see {@link constructor} for further information * @since 3.0.0 */ setConfig(opts = {}) { /* istanbul ignore if */ if (opts.global) { helper.deprecated(opts.global.life, 'global.life is deprecated use maxAge instead'); helper.deprecated(opts.global.silent, 'global.silent is deprecated use silent instead'); } helper.defaults(opts, this.DEFAULT_CONFIG); this._opts = opts; /** * Root object * @ignore */ this._root = opts.share ? helper.isServer() ? global : window : {}; /* istanbul ignore else */ if (opts.storeName) this.GLOBAL_KEY += opts.storeName; /* istanbul ignore else */ if (!this._root[this.GLOBAL_KEY]) { this._root[this.GLOBAL_KEY] = { data: {} }; } this._memory = this._root[this.GLOBAL_KEY]; /* istanbul ignore else */ //if (helper.isServer()) { if (opts.autoLoad) this.load().then().catch((e) => { }); /* istanbul ignore else */ if (opts.autoSave || opts.save) { /* istanbul ignore else */ if (opts.autoSaveMode === SAVE_MODE.TERMINATE && helper.isServer()) { let self = this; // Wrap function function pWrite() { self.save().then().catch((e) => { }); } // Remove if event already exists process.removeListener('exit', pWrite); process.removeListener('SIGINT', pWrite); //process.stdin.resume(); process.on('exit', pWrite); process.on('SIGINT', pWrite); } else if (opts.autoSaveMode === SAVE_MODE.TIMER) { /* istanbul ignore else */ if (this._timerSaveCheck) { clearInterval(this._timerSaveCheck); this._timerSaveCheck = null; } process.on('SIGINT', ()=>{ process.exit(); }); /* istanbul ignore else */ if (opts.autoSavePeriod) { this._timerSaveCheck = setInterval(() => { if (this._lastChange !== this._lastChangeDetected) { this._lastChangeDetected = this._lastChange; this.save().then().catch(e => { }); } }, opts.autoSavePeriod * 1000); } } } //} /* istanbul ignore else */ if (this._timerExpiryCheck) { clearInterval(this._timerExpiryCheck); this._timerExpiryCheck = null; } /* istanbul ignore else */ if (opts.autoRemovePeriod) { this._timerExpiryCheck = setInterval(() => { let expired = this.removeExpired(); if (expired.length) { this.fire('expired', expired); } }, opts.autoRemovePeriod * 1000); } } /** * Get configuration * @returns {*} */ getConfig() { return this._opts; } /** * Set/update record * @param key {string} * @param value {*} * @param [opts] {Object} options object * @param [opts.silent=false] {boolean} if true no event will be triggered. (overwrites global configuration) * @param [opts.maxAge=0] {number} max age in milliseconds. If 0 not expire. (overwrites global configuration) * @param [opts.clone=false] {boolean} if true, the object will be cloned before to put it in storage. (overwrites global configuration) * @param [opts.preserve=false] {boolean} if true, you will no longer be able to update the record once created. (overwrites global configuration) * @param [opts.expires] {Date|string} a Date for expiration. (overwrites global configuration and `opts.maxAge`) * @param [opts.deleteOnExpires=true] {boolean} if false, the record will not be deleted after expiry. (overwrites global configuration) * @param [opts.life=0] {number} **deprecated:** max age in seconds. If 0 not expire. (overwrites global configuration) * @returns {InCache~record|*} * @fires InCache#beforeSet * @fires InCache#create * @fires InCache#update * @fires InCache#set * @example * inCache.set('my key', 'my value'); * inCache.set('my object', {a: 1, b: 2}); * inCache.set('my boolean', true, {maxAge: 2000}); // Expires after 2 seconds */ set(key, value, opts = {}) { /* istanbul ignore else */ if (!opts.silent && this.fireTheFirst('beforeSet', key, value) === false) { return; } opts = helper.defaults(opts, this._opts); /* istanbul ignore else */ if (this.has(key) && this._memory.data[key].isPreserved) { return; } /* istanbul ignore else */ if (opts.clone) { value = clone(value); } let record = Object.assign({}, RECORD); record.isNew = true; record.isPreserved = opts.preserve; record.toDelete = opts.deleteOnExpires; record.hits = 0; record.value = value; if (opts.expires && (helper.is(opts.expires, 'date') || helper.is(opts.expires, 'string'))) { record.expiresOn = new Date(opts.expires); } else if (opts.maxAge && helper.is(opts.maxAge, 'number')) { record.expiresOn = helper.addMSToNow(opts.maxAge); } else if (opts.life && helper.is(opts.life, 'number')) { helper.deprecated(opts.life, 'life is deprecated use maxAge instead'); record.expiresOn = helper.addSecondsToNow(opts.life); } if (this.has(key)) { record.isNew = false; record.hits = this._memory.data[key].hits; record.id = this._memory.data[key].id; record.createdOn = this._memory.data[key].createdOn; record.updatedOn = new Date(); if (!opts.silent) { this._onUpdated.call(this, key, record); this.fire('update', key, record); } } else { record.id = uuid(); record.createdOn = new Date(); if (!opts.silent) { this._onCreated.call(this, key, record); this.fire('create', key, record); } } this._memory.data[key] = record; if (!opts.silent) { this.fire('set', key, record); } this._checkExceeded(); this.fire('_change', 'set'); return record; } /** * Get record by key * @param key {string} * @param [onlyValue=true] {boolean} if false get InCache record * @returns {InCache~record|*|null|undefined} * @fires InCache#get * @fires InCache#beforeGet * @example * inCache.get('my key'); */ get(key, onlyValue = true) { if (this.has(key)) { if (this.canBeAutoRemove(key)) { this.remove(key, true); return (this._opts.nullIfNotFound ? null : undefined); } /* istanbul ignore else */ if (this.fireTheFirst('beforeGet', key, this._memory.data[key]) === false) { return; } this._memory.data[key].hits += 1; this._memory.data[key].lastHit = new Date(); this.fire('get', key, this._memory.data[key]); return onlyValue ? this._memory.data[key].value : this._memory.data[key]; } else { return (this._opts.nullIfNotFound ? null : undefined); } } /** * Get info record by key * @param key {string} * @returns {InCache~recordInfo|*|undefined} * @example * inCache.info('my key'); * @since 7.1.0 */ info(key) { if (this.has(key)) { if (this.canBeAutoRemove(key)) { this.remove(key, true); return undefined; } const record = Object.assign({}, this._memory.data[key]); delete record.value; return record; } else { return undefined; } } /** * Delete a record * @param key {string} * @param [silent=false] {boolean} if true no event will be triggered * @fires InCache#beforeRemove * @fires InCache#remove * @example * inCache.remove('my key'); */ remove(key, silent = false) { if (!silent && this.fireTheFirst('beforeRemove', key) === false) { return; } delete this._memory.data[key]; if (!silent) { this._onRemoved.call(this, key); this.fire('remove', key); } this.fire('_change', 'remove'); } /** * Given a key that has value like an array removes key(s) if `where` is satisfied * @param key {string} * @param where {*} * @example * inCache.set('myArray', ['hello', 'world']); * inCache.removeFrom('myArray', 'hello'); //-> ['world']; * @since 3.0.0 */ removeFrom(key, where) { if (!this.has(key)) return null; if (helper.is(where, 'undefined')) throw new Error('where cannot be undefined'); if (this._memory.data[key].isPreserved) { return; } let recordValue = this.get(key); if (!helper.is(recordValue, 'array')) throw new Error('value must be an array'); let recordLengthBefore = recordValue.length; for (let i in recordValue) { if (recordValue.hasOwnProperty(i)) { let result = []; for (let prop in where) { if (where.hasOwnProperty(prop)) if (helper.is(where, 'object')) result.push(typeof recordValue[i][prop] !== 'undefined' && recordValue[i][prop] === where[prop]); else result.push(recordValue[i] === where); } if (result.length && result.indexOf(false) === -1) recordValue.splice(i, 1); } } if (recordLengthBefore !== recordValue.length) { this.set(key, recordValue); } } /** * Remove expired records * @returns {Array} expired keys * @since 4.1.0 * @example * inCache.set('my key 1', 'my value'); * inCache.set('my key 2', 'my value', {maxAge: 1000}); * inCache.set('my key 3', 'my value', {maxAge: 1500}); * setTimeout(()=>{ * inCache.removeExpired(); * inCache.all(); //-> [{key: 'my key 1', value: 'my value'}] * }, 2000) */ removeExpired() { const expired = []; for (let key in this._memory.data) { if (this._memory.data.hasOwnProperty(key) && this.expired(key) && this._memory.data[key].toDelete) { this.remove(key, true); expired.push(key); } } return expired; } /** * Given a key that has value like an array adds value to end of array * @param key {string} * @param value {*} * @returns {InCache~record|undefined} * @example * inCache.set('myArray', ['hello', 'world']); * inCache.addTo('myArray', 'ciao'); //-> ['hello', 'world', 'ciao']; * @since 3.0.0 */ addTo(key, value) { if (!this.has(key)) return; let record = this.get(key); if (this._memory.data[key].isPreserved) { return; } if (!helper.is(record, 'array')) throw new Error('object must be an array'); record.push(value); return this.set(key, record); } /** * Given a key that has value like an array adds value to beginning of array * @param key {string} * @param value {*} * @returns {InCache~record|undefined} * @example * inCache.set('myArray', ['hello', 'world']); * inCache.prependTo('myArray', 'ciao'); //-> ['ciao', 'hello', 'world']; * @since 3.0.0 */ prependTo(key, value) { if (!this.has(key)) return; if (this._memory.data[key].isPreserved) { return; } let record = this.get(key); if (!helper.is(record, 'array')) throw new Error('object must be an array'); record.unshift(value); return this.set(key, record); } /** * Given a key that has value like an array updates key(s) if `where` is satisfied * @param key {string} * @param value {*} * @param where {*} * @example * inCache.set('myArray', ['hello', 'world']); * inCache.updateIn('myArray', 'ciao', 'hello'); //-> ['ciao', 'world']; * * inCache.set('myArray', [{a: 1, b: 2, c: 3], {b: 2, c: 3}, {b: 4, e: 5}); * inCache.updateIn('myArray', {z: 0, x: 0}, {b: 2, c: 3}); //-> [{z: 0, x: 0}, {z: 0, x: 0}, {b: 4, e: 5}]; * @since 3.0.0 */ updateIn(key, value, where) { if (!this.has(key)) return; if (helper.is(value, 'undefined')) throw new Error('value cannot be undefined'); if (helper.is(where, 'undefined')) throw new Error('where cannot be undefined'); if (this._memory.data[key].isPreserved) { return; } let recordValue = this.get(key); if (!helper.is(recordValue, 'array')) throw new Error('value must be an array'); let updated = false; for (let i in recordValue) { if (recordValue.hasOwnProperty(i)) { let result = []; for (let prop in where) { if (where.hasOwnProperty(prop)) if (helper.is(where, 'object')) result.push(typeof recordValue[i][prop] !== 'undefined' && recordValue[i][prop] === where[prop]); else result.push(recordValue[i] === where); } if (result.length && result.indexOf(false) === -1) { updated = true; recordValue[i] = value; } } } if (updated) { this.set(key, recordValue); } } /** * Set/update multiple records. This method not trigger any event. * @param records {Array} e.g. [{key: foo1, value: bar1},{key: foo2, value: bar2}] * @param [silent=false] {boolean} if true no event will be triggered * @fires InCache#beforeBulkSet * @fires InCache#bulkSet * @example * inCache.bulkSet([ * {key: 'my key 1', value: 'my value 1'}, * {key: 'my key 2', value: 'my value 2'}, * {key: 'my key 3', value: 'my value 3'}, * {key: 'my key 4', value: 'my value 4'} * ]); * // or * inCache.bulkSet(['hello','world']); * * @returns {{}|undefined} */ bulkSet(records, silent = false) { if (!helper.is(records, 'array') && !helper.is(records, 'object')) throw new Error('records must be an array, e.g. {key: foo, value: bar}'); if (!silent && this.fireTheFirst('beforeBulkSet', records) === false) { return; } const result = {}; let record; for (let i = 0; i < records.length; i++) { if (!helper.is(records[i].key, 'undefined') && !helper.is(records[i].value, 'undefined')) { record = this.set(records[i].key, records[i].value, {silent: true, fromBulk: true}); if (record) result[records[i].key] = record; } else { record = this.set(i, records[i], {silent: true, fromBulk: true}); if (record) result[i] = record; } } if (!silent) { this.fire('bulkSet', records); } return result; } /** * Delete multiple records * @param keys {Array} an array of keys * @param [silent=false] {boolean} if true no event will be triggered * @fires InCache#beforeBulkRemove * @fires InCache#bulkRemove * @example * inCache.bulkRemove(['key1', 'key2', 'key3']); */ bulkRemove(keys, silent) { if (!helper.is(keys, 'array')) throw new Error('keys must be an array of keys'); if (!silent && this.fireTheFirst('beforeBulkRemove', keys) === false) { return; } for (let i = 0; i < keys.length; i++) { this.remove(keys[i], true); } if (!silent) { this.fire('bulkRemove', keys); } } /** * Delete multiple records that contain the passed keyword * @param key {string} a string that is relative to a group of keys * @example * inCache.set('/api/users/foo', 'Mario Rossi'); * inCache.set('/api/users/bar', 'Antonio Bianchi'); * inCache.clean('/api/users'); */ clean(key) { if (!helper.is(key, 'string')) throw new Error('key must be a string'); for (let k in this._memory.data) { if (this._memory.data.hasOwnProperty(k) && k.indexOf(key) !== -1) { delete this._memory.data[k]; this.fire('_change', 'clean'); } } } /** * Fetch all records * @param asObject * @returns {*} */ all(asObject = false) { let records = asObject ? {} : []; for (let key in this._memory.data) { if (this._memory.data.hasOwnProperty(key)) { if (this.canBeAutoRemove(key)) { this.remove(key, true); } else { if (Array.isArray(records)) { records.push({ key: key, value: this._memory.data[key].value }); } else { records[key] = this._memory.data[key].value; } } } } return records; } /** * Returns total of records in storage * @returns {Number} * @since 6.0.0 */ count() { this.removeExpired(); return Object.keys(this._memory.data).length; } /** * Check if record is expired * @param key {string} * @returns {boolean} */ expired(key) { if (this._memory.data[key] && this._memory.data[key].expiresOn) { let now = new Date(); let expiry = new Date(this._memory.data[key].expiresOn); return now > expiry; } else { return false; } } /** * Remove all records */ clear() { /** * Reset object * @ignore */ this._memory.data = {}; this.fire('_change', 'clear'); } /** * Check if key exists * @param key {string} * @returns {boolean} * @example * inCache.has('my key'); */ has(key) { return this._memory.data.hasOwnProperty(key); } /** * Alias of `remove` * @borrows remove as destroy * @param args * @since 4.1.1 */ destroy(...args) { this.remove.apply(this, args); } /** * Returns stats of storage * @returns {{count: Number, size: Number}} * @since 6.3.0 */ stats() { return { count: this.count(), size: sizeOf(this._memory.data) } } /** * Check if key can be auto removed * @param key * @returns {boolean|*} */ canBeAutoRemove(key) { return !this._opts.autoRemovePeriod && this.expired(key) && this._memory.data[key].toDelete } /** * Adds listener to instance * @param eventName {string} event name * @param callback {Function} callback * @returns {InCache} */ /** * Suspends firing of the named event(s). * @param eventName {...string} multiple event names to suspend * @returns {InCache} * @since 6.6.0 */ /** * Resumes firing of the named event(s). * @param eventName {...string} multiple event names to resume. * @returns {InCache} * @since 6.6.0 */ /** * Suspends all events. * @returns {InCache} * @since 6.6.0 */ /** * Resume all events. * @returns {InCache} * @since 6.6.0 */ /** * Triggered before get * @event InCache#beforeGet * @param key {string} key * @param record {InCache~record} record object * @since 7.2.0 */ /** * Triggered after get * @event InCache#get * @param key {string} key * @param record {InCache~record} record object * @since 7.2.0 */ /** * Triggered before set * @event InCache#beforeSet * @param key {string} key * @param value {string} value * @since 5.0.0 */ /** * Triggered after set * @event InCache#set * @param key {string} key * @param record {InCache~record} record object * @since 5.0.0 */ /** * Triggered after create the record * @event InCache#create * @param key {string} key of record * @param record {InCache~record} record object * @since 5.0.0 */ /** * Triggered after update the record * @event InCache#update * @param key {string} key of record * @param record {InCache~record} record object * @since 5.0.0 */ /** * Triggered before remove the record * @event InCache#beforeRemove * @param key {string} key of record to be removed * @since 5.0.0 */ /** * Triggered after record has been removed * @event InCache#remove * @param key {string} key of record * @since 5.0.0 */ /** * Triggered before bulk set * @event InCache#beforeBulkSet * @param records {Array} array of objects * @since 5.0.0 */ /** * Triggered after bulk set * @event InCache#bulkSet * @param records {Array} array of objects * @since 5.0.0 */ /** * Triggered before remove the records * @event InCache#beforeBulkRemove * @param keys {Array} array of keys to be removed * @since 5.0.0 */ /** * Triggered after records have been removed * @event InCache#bulkRemove * @param keys {Array} array of keys removed * @since 5.0.0 */ /** * Triggered when records are expired and `opts.autoRemovePeriod` is set * @event InCache#expired * @param keys {Array} array of keys expired * @since 5.0.0 */ /** * Triggered before load (only if `autoLoad` is false) * @event InCache#beforeLoad * @param me {InCache} * @since 6.4.0 */ /** * Triggered after load invocation * @event InCache#load * @param err {null|string} error message, if no errors occurred is null * @param me {InCache} * @since 6.0.0 */ /** * Triggered before save * @event InCache#beforeSave * @param me {InCache} * @since 6.4.0 */ /** * Triggered after save invocation * @event InCache#save * @param err {null|string} error message, if no errors occurred is null * @param me {InCache} * @since 6.0.0 */ /** * Triggered when data is changed * @event InCache#change * @param by {string} event called by `set`,`remove`,`clear` or `clean` * @since 6.1.0 */ /** * Triggered when data exceed max size * @event InCache#exceed * @param diff {number} exceeded by record number * @since 6.1.0 */ /** * Check if object is a InCache~record * @param obj {InCache~record} InCache record * @returns {boolean} */ static isRecord(obj) { return Object.keys(RECORD).every(el => { return typeof obj === 'object' && obj.hasOwnProperty(el); }); } /***************************** DEPRECATED ********************************/ /** * Triggered when a record has been deleted. **Deprecated since 5.0.0:** use `on('remove', callback)` instead. * @param callback {InCache~removedCallback} callback function * @deprecated * @example * inCache.onRemoved((key)=>{ * console.log('removed', key); * }); */ onRemoved(callback) { this._onRemoved = callback; } /** * onRemoved callback * @callback InCache~removedCallback * @param key {string} key of record removed * @deprecated */ /** * Triggered when a record has been created. **Deprecated since 5.0.0:** use `on('create', callback)` instead * @param callback {InCache~createdCallback} callback function * @deprecated * @example * inCache.onCreated((key, record)=>{ * console.log('created', key, record); * }); */ onCreated(callback) { this._onCreated = callback; } /** * onCreated callback * @callback InCache~createdCallback * @param key {string} key of record created * @param record {InCache~record} record object * @deprecated */ /** * Triggered when a record has been updated. **Deprecated since 5.0.0:** use `on('update', callback)` instead * @param callback {InCache~updatedCallback} callback function * @deprecated * @example * inCache.onUpdated((key, record)=>{ * console.log('updated', key, record); * }); */ onUpdated(callback) { this._onUpdated = callback; } /** * onUpdated callback * @callback InCache~updatedCallback * @param key {string} key of record updated * @param record {InCache~record} record object * @deprecated */ } /** * Expose module */ module.exports = InCache; module.exports.SAVE_MODE = SAVE_MODE;