@cachemap/core
Version:
The Cachemap core module.
3 lines (2 loc) • 13.2 kB
JavaScript
"use strict";require("core-js/modules/es.array.push.js");var e=require("@cachemap/controller"),t=require("@cachemap/map"),a=require("@cachemap/utils"),r=require("cacheability"),s=require("eventemitter3"),i=require("lodash-es"),n=require("ts-md5"),o=require("@cachemap/types");class c{static{this._sortComparator=(e,t)=>{let a;return a=e.accessedCount>t.accessedCount?-1:e.accessedCount<t.accessedCount?1:e.lastAccessed>t.lastAccessed?-1:e.lastAccessed<t.lastAccessed?1:e.lastUpdated>t.lastUpdated?-1:e.lastUpdated<t.lastUpdated?1:e.added>t.added?-1:e.added<t.added?1:e.size<t.size?-1:e.size>t.size?1:0,a}}constructor(e){this.events={ENTRY_DELETED:"ENTRY_DELETED"},this._handleClearEvent=({name:e,type:t})=>{(i.isString(e)&&e===this._name||i.isString(t)&&t===this._type)&&this._clear()},this._handleStartReaperEvent=({name:e,type:t})=>{(i.isString(e)&&e===this._name||i.isString(t)&&t===this._type)&&this._reaper?.start()},this._handleStopReaperEvent=({name:e,type:t})=>{(i.isString(e)&&e===this._name||i.isString(t)&&t===this._type)&&this._reaper?.stop()},this._handleStartBackupEvent=({name:e,type:t})=>{(i.isString(e)&&e===this._name||i.isString(t)&&t===this._type)&&this._startBackup()},this._handleStopBackupEvent=({name:e,type:t})=>{(i.isString(e)&&e===this._name||i.isString(t)&&t===this._type)&&this._stopBackup()},this._backupInterval=1e4,this._emitter=new s.EventEmitter,this._maxHeapSize=4194304,this._metadata=[],this._persistedStore=!0,this._processing=[],this._ready=!1,this._requestQueue=[],this._usedHeapSize=0,this._valueFormatting=a.ValueFormat.String;const r=[];if(i.isPlainObject(e)||r.push(new a.ArgsError("@cachemap/core expected options to be a plain object.")),i.isString(e.name)||r.push(new a.ArgsError("@cachemap/core expected options.name to be a string.")),i.isFunction(e.store)||r.push(new a.ArgsError("@cachemap/core expected options.store to be a function.")),i.isString(e.type)||r.push(new a.ArgsError("@cachemap/core expected options.type to be a string.")),e.valueFormatting!==a.ValueFormat.Ecrypt||e.encryptionSecret||r.push(new a.ArgsError('@cachemap/core expected encryptionSecret to be set when valueFormatting is "encrypt"')),r.length>0)throw new a.GroupedError("@cachemap/core constructor argument validation errors.",r);const{backupInterval:n,backupStore:o,disableCacheInvalidation:h=!1,encryptionSecret:p,name:d,reaper:u,sharedCache:_=!1,sortComparator:l,startBackup:m,store:y,type:g,valueFormatting:S}=e;this._disableCacheInvalidation=h,S&&(this._valueFormatting=S),i.isString(p)&&(this._encryptionSecret=p),this._name=d,i.isFunction(u)&&(this._reaper=this._initializeReaper(u)),this._sharedCache=_,i.isFunction(l)&&(c._sortComparator=l),this._type=g,this._addControllerEventListeners(),Promise.resolve(y({name:d})).then((async e=>{if(this._maxHeapSize=e.maxHeapSize,o){if("map"===e.type)throw new a.ArgsError("@cachemap/core expected store.type not to be 'map' when backupStore is true.");i.isNumber(n)&&(this._backupInterval=n),this._backupStore=e,this._persistedStore=!0,this._store=new t.MapStore({maxHeapSize:e.maxHeapSize,name:d}),await this._backupStoreEntriesToStore(),this._ready=!0,this._releaseQueuedRequests(),m&&this._startBackup()}else this._persistedStore="map"!==e.type,this._store=e,await this._retreiveMetadataFromStore(),this._ready=!0,this._releaseQueuedRequests()}))}async clear(){return this._clear()}async delete(e,t={}){const r=[];if(i.isString(e)||r.push(new a.ArgsError("@cachemap/core expected key to be a string.")),i.isPlainObject(t)||r.push(new a.ArgsError("@cachemap/core expected options to be a plain object.")),r.length>0)throw new a.GroupedError("@cachemap/core delete argument validation errors.",r);return this._delete(e,t)}get emitter(){return this._emitter}async entries(e){if(e&&!i.isArray(e))throw new a.ArgsError("@cachemap/core expected keys to be an array.");return(await this._entries(e)).sort((([e],[t])=>e<t?-1:e>t?1:0))}async export(e={}){const t=[];if(i.isPlainObject(e)||t.push(new a.ArgsError("@cachemap/core expected options to be an plain object.")),e.keys&&!i.isArray(e.keys)&&t.push(new a.ArgsError("@cachemap/core expected options.keys to be an array.")),t.length>0)throw new a.GroupedError("@cachemap/core export argument validation errors.",t);const{entries:r,metadata:s}=await this._export(e);return{entries:r.sort((([e],[t])=>e<t?-1:e>t?1:0)),metadata:s.sort(((e,t)=>e.key<t.key?-1:e.key>t.key?1:0))}}async get(e,t={}){const r=[];if(i.isString(e)||r.push(new a.ArgsError("@cachemap/core expected key to be a string.")),i.isPlainObject(t)||r.push(new a.ArgsError("@cachemap/core expected options to be a plain object.")),r.length>0)throw new a.GroupedError("@cachemap/core get argument validation errors.",r);return this._get(e,t)}async has(e,t={}){const r=[];if(i.isString(e)||r.push(new a.ArgsError("@cachemap/core expected key to be a string.")),i.isPlainObject(t)||r.push(new a.ArgsError("@cachemap/core expected opts to be a plain object.")),r.length>0)throw new a.GroupedError("@cachemap/core has argument validation errors.",r);return this._has(e,t)}async import(e){if(!i.isPlainObject(e))throw new a.ArgsError("@cachemap/core expected options to be a plain object.");const{entries:t,metadata:r}=e,s=[];if(i.isArray(t)||s.push(new a.ArgsError("@cachemap/core expected entries to be an array.")),i.isArray(r)||s.push(new a.ArgsError("@cachemap/core expected metadata to be an array.")),s.length>0)throw new a.GroupedError("@cachemap/core has argument validation errors.",s);return this._import(e)}get metadata(){return this._metadata}get name(){return this._name}get reaper(){return this._reaper}async set(e,t,r={}){const s=[];if(i.isString(e)||s.push(new a.ArgsError("@cachemap/core expected key to be a string.")),i.isPlainObject(r)||s.push(new a.ArgsError("@cachemap/core expected options to be a plain object.")),a.isJsonValue(t)||s.push(new a.ArgsError("@cachemap/core expected value to be JSON serializable.")),s.length>0)throw new a.GroupedError("@cachemap/core set argument validation errors.",s);return this._set(e,t,r)}async size(){return this._size()}startBackup(){this._startBackup()}stopBackup(){this._stopBackup()}get storeType(){return this._store?.type??"none"}get type(){return this._type}get usedHeapSize(){return this._usedHeapSize}_addControllerEventListeners(){e.instance.on(a.constants.CLEAR,this._handleClearEvent),e.instance.on(a.constants.START_REAPER,this._handleStartReaperEvent),e.instance.on(a.constants.STOP_REAPER,this._handleStopReaperEvent),e.instance.on(a.constants.START_BACKUP,this._handleStartBackupEvent),e.instance.on(a.constants.STOP_BACKUP,this._handleStopBackupEvent)}async _addMetadata(e,t,a,r){return this._metadata.push({accessedCount:0,added:Date.now(),cacheability:a,key:e,lastAccessed:Date.now(),lastUpdated:Date.now(),size:t,tags:r?[r]:[],updatedCount:0}),this._sortMetadata(),this._updateHeapSize(),this._backupMetadata()}_addRequestToQueue(e,...t){return new Promise((a=>{this._requestQueue.push([a,e,t])}))}async _backupMetadata(){if(!this._store||!this._persistedStore)return;return(this._backupStore??this._store).set(a.constants.METADATA,a.prepareSetEntry(a.dehydrateMetadata(this._metadata),this._valueFormatting,this._encryptionSecret))}async _backupStoreEntriesToStore(){if(!this._backupStore||!this._store)throw new a.PositionError("@cachemap/core expected backupStoreEntriesToStore to be called after setting the backupStore and store.");this._metadata=[];const e=await this._backupStore.get(a.constants.METADATA);if(e){const t=a.prepareGetEntry(e,this._valueFormatting,this._encryptionSecret);if(t.length>0){const e=t.map((e=>e.key));await this._store.import(await this._backupStore.entries(e)),this._metadata=a.rehydrateMetadata(t)}}}_calcReductionChunk(){const e=Math.round(.2*this._maxHeapSize);let t,a=0;for(let r=this._metadata.length-1;r>=0;r-=1)if(a+=this._metadata[r].size,a>e){t=r;break}return t}async _clear(){return this._ready&&this._store?(await this._store.clear(),this._metadata=[],this._processing=[],this._updateHeapSize(),this._backupMetadata()):this._addRequestToQueue(a.constants.CLEAR)}async _delete(e,t={}){if(!this._ready||!this._store)return this._addRequestToQueue(a.constants.DELETE,e,t);const r=t.hashKey?n.Md5.hashStr(e):e;return!!await this._store.delete(r)&&(await this._deleteMetadata(r),!0)}async _deleteMetadata(e){const t=this._metadata.findIndex((t=>t.key===e));if(-1!==t)return this._metadata.splice(t,1),this._sortMetadata(),this._updateHeapSize(),this._backupMetadata()}async _entries(e){if(!this._ready||!this._store)return this._addRequestToQueue(a.constants.ENTRIES,e);const t=e??this._metadata.map((e=>e.key));return(await this._store.entries(t)).map((([e,t])=>[e,a.prepareGetEntry(t,this._valueFormatting,this._encryptionSecret)]))}async _export({filterByValue:e,keys:t,tag:a}){let r,s=this._metadata;a?(s=this._metadata.filter((e=>e.tags.includes(a))),r=s.map((e=>e.key))):t&&(s=this._metadata.filter((e=>t.includes(e.key))),r=t);let n=await this._entries(r);if(e){const t=i.castArray(e);n=n.filter((([,e])=>t.every((({comparator:t,keyChain:a})=>i.get(e,a)===t)))),s=s.filter((e=>n.some((([t])=>t===e.key))))}return{entries:n,metadata:s}}async _get(e,t){if(!this._ready||!this._store)return this._addRequestToQueue(a.constants.GET,e,t);const r=t.hashKey?n.Md5.hashStr(e):e,s=await this._store.get(r);return s?(await this._updateMetadata(r),a.prepareGetEntry(s,this._valueFormatting,this._encryptionSecret)):void 0}_getCacheability(e){const t=this._getMetadataEntry(e);return t?t.cacheability:void 0}_getMetadataEntry(e){return this._metadata.find((t=>t.key===e))}async _has(e,t){if(!this._ready||!this._store)return this._addRequestToQueue(a.constants.HAS,e,t);const r=t.hashKey?n.Md5.hashStr(e):e;return!!await this._store.has(r)&&(t.deleteExpired&&this._hasCacheEntryExpired(r)?(await this.delete(r),!1):this._getCacheability(r)??!1)}_hasCacheEntryExpired(e){if(this._disableCacheInvalidation)return!1;const t=this._getCacheability(e);return!!t&&!t.checkTTL()}async _import(e){if(!this._ready||!this._store)return this._addRequestToQueue(a.constants.IMPORT,e);let t=[];this._metadata.length>0&&(t=this._metadata.filter((t=>!e.metadata.some((e=>t.key===e.key)))));const r=e.entries.map((([e,t])=>[e,a.prepareSetEntry(t,this._valueFormatting,this._encryptionSecret)]));await this._store.import(r),this._metadata=a.rehydrateMetadata([...t,...e.metadata]),this._sortMetadata(),await this._backupMetadata(),this._updateHeapSize()}_initializeReaper(e){return e({deleteCallback:async(e,t)=>{this.emitter.emit(this.events.ENTRY_DELETED,{deleted:await this._delete(e),key:e,tags:t})},metadataCallback:()=>this._metadata})}_processed(e){this._processing=this._processing.filter((t=>t!==e))}_reduceHeapSize(){const e=this._calcReductionChunk();e&&this._reaper&&this._reaper.cull(this._metadata.slice(e))}async _releaseQueuedRequests(){for(const[e,t,a]of this._requestQueue)e(await this[t](...a));this._requestQueue=[]}async _retreiveMetadataFromStore(){if(!this._store||!this._persistedStore)return;const e=await this._store.get(a.constants.METADATA);e&&(this._metadata=a.rehydrateMetadata(a.prepareGetEntry(e,this._valueFormatting,this._encryptionSecret)))}async _set(e,t,s){if(!this._ready||!this._store)return this._addRequestToQueue(a.constants.SET,e,t,s);const i=new r.Cacheability({headers:s.cacheHeaders}),{cacheControl:o}=i.metadata;if(o.noStore||this._sharedCache&&o.private)return;const c=s.hashKey?n.Md5.hashStr(e):e,h=this._processing.includes(c);h||this._processing.push(c);try{const e=await this._store.has(c)||h,r=a.prepareSetEntry(t,this._valueFormatting,this._encryptionSecret);await this._store.set(c,r),await(e?this._updateMetadata(c,a.sizeOf(r),i,s.tag):this._addMetadata(c,a.sizeOf(r),i,s.tag)),this._processed(c)}catch(e){throw this._processed(c),e}}async _size(){return this._ready&&this._store?this._store.size():this._addRequestToQueue(a.constants.SIZE)}_sortMetadata(){this._metadata.sort(c._sortComparator)}_startBackup(){this._backupIntervalID=setInterval((()=>{this._storeEntriesToBackupStore()}),this._backupInterval)}_stopBackup(){this._backupIntervalID&&clearInterval(this._backupIntervalID)}async _storeEntriesToBackupStore(){if(!this._backupStore||!this._store)return;await this._backupStore.clear();const e=this._metadata.map((e=>e.key));this._backupStore.import(await this._store.entries(e))}_updateHeapSize(){this._usedHeapSize=this._metadata.reduce(((e,t)=>e+t.size),0),!this._disableCacheInvalidation&&this._usedHeapSize>this._maxHeapSize&&this._reduceHeapSize()}async _updateMetadata(e,t,a,r){const s=this._getMetadataEntry(e);if(s)return t?(s.size=t,s.lastUpdated=Date.now(),s.updatedCount+=1):(s.accessedCount+=1,s.lastAccessed=Date.now()),a&&(s.cacheability=a),i.isUndefined(r)||s.tags.push(r),this._sortMetadata(),this._updateHeapSize(),this._backupMetadata()}}exports.Core=c,Object.keys(a).forEach((function(e){"default"===e||Object.prototype.hasOwnProperty.call(exports,e)||Object.defineProperty(exports,e,{enumerable:!0,get:function(){return a[e]}})})),Object.keys(o).forEach((function(e){"default"===e||Object.prototype.hasOwnProperty.call(exports,e)||Object.defineProperty(exports,e,{enumerable:!0,get:function(){return o[e]}})}));
//# sourceMappingURL=index.cjs.map