UNPKG

@cachemap/core

Version:
3 lines (2 loc) 13.6 kB
"use strict";require("core-js/modules/es.array.push.js");var e=require("@cachemap/controller"),t=require("@cachemap/map"),a=require("@cachemap/utils"),s=require("cacheability"),r=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 r.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 s=[];if(i.isPlainObject(e)||s.push(new a.ArgsError("@cachemap/core expected options to be a plain object.")),i.isString(e.name)||s.push(new a.ArgsError("@cachemap/core expected options.name to be a string.")),i.isFunction(e.store)||s.push(new a.ArgsError("@cachemap/core expected options.store to be a function.")),i.isString(e.type)||s.push(new a.ArgsError("@cachemap/core expected options.type to be a string.")),e.valueFormatting!==a.ValueFormat.Ecrypt||e.encryptionSecret||s.push(new a.ArgsError('@cachemap/core expected encryptionSecret to be set when valueFormatting is "encrypt"')),s.length>0)throw new a.GroupedError("@cachemap/core constructor argument validation errors.",s);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 s=[];if(i.isString(e)||s.push(new a.ArgsError("@cachemap/core expected key to be a string.")),i.isPlainObject(t)||s.push(new a.ArgsError("@cachemap/core expected options to be a plain object.")),s.length>0)throw new a.GroupedError("@cachemap/core delete argument validation errors.",s);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:s,metadata:r}=await this._export(e);return{entries:s.sort((([e],[t])=>e<t?-1:e>t?1:0)),metadata:r.sort(((e,t)=>e.key<t.key?-1:e.key>t.key?1:0))}}async get(e,t={}){const s=[];if(i.isString(e)||s.push(new a.ArgsError("@cachemap/core expected key to be a string.")),i.isPlainObject(t)||s.push(new a.ArgsError("@cachemap/core expected options to be a plain object.")),s.length>0)throw new a.GroupedError("@cachemap/core get argument validation errors.",s);return this._get(e,t)}getMetadataEntry(e,t={}){const a=t.hashKey?n.Md5.hashStr(e):e;return this._getMetadataEntry(a)}async has(e,t={}){const s=[];if(i.isString(e)||s.push(new a.ArgsError("@cachemap/core expected key to be a string.")),i.isPlainObject(t)||s.push(new a.ArgsError("@cachemap/core expected opts to be a plain object.")),s.length>0)throw new a.GroupedError("@cachemap/core has argument validation errors.",s);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:s}=e,r=[];if(i.isArray(t)||r.push(new a.ArgsError("@cachemap/core expected entries to be an array.")),i.isArray(s)||r.push(new a.ArgsError("@cachemap/core expected metadata to be an array.")),r.length>0)throw new a.GroupedError("@cachemap/core has argument validation errors.",r);return this._import(e)}get metadata(){return this._metadata}get name(){return this._name}get reaper(){return this._reaper}async set(e,t,s={}){const r=[];if(i.isString(e)||r.push(new a.ArgsError("@cachemap/core expected key to be a string.")),i.isPlainObject(s)||r.push(new a.ArgsError("@cachemap/core expected options to be a plain object.")),a.isJsonValue(t)||r.push(new a.ArgsError("@cachemap/core expected value to be JSON serializable.")),r.length>0)throw new a.GroupedError("@cachemap/core set argument validation errors.",r);return this._set(e,t,s)}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,s,r){return this._metadata.push({accessedCount:0,added:Date.now(),cacheability:a,extensions:r,key:e,lastAccessed:Date.now(),lastUpdated:Date.now(),size:t,tags:s?[s]:[],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 s=this._metadata.length-1;s>=0;s-=1)if(a+=this._metadata[s].size,a>e){t=s;break}return t}_cleanupTag(e){for(const t of this._metadata)t.tags.includes(e)&&(t.tags=t.tags.filter((t=>t!==e)))}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 s=t.hashKey?n.Md5.hashStr(e):e;return!!await this._store.delete(s)&&(await this._deleteMetadata(s),!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({cleanupTag:e,filterByValue:t,keys:a,tag:s}){let r,n=[...this._metadata];s?(n=this._metadata.filter((e=>e.tags.includes(s))),r=n.map((e=>e.key)),e&&this._cleanupTag(s)):a&&(n=this._metadata.filter((e=>a.includes(e.key))),r=a);let o=await this._entries(r);if(t){const e=i.castArray(t);o=o.filter((([,t])=>e.every((({comparator:e,keyChain:a})=>i.get(t,a)===e)))),n=n.filter((e=>o.some((([t])=>t===e.key))))}return{entries:o,metadata:n}}async _get(e,t){if(!this._ready||!this._store)return this._addRequestToQueue(a.constants.GET,e,t);const s=t.hashKey?n.Md5.hashStr(e):e;if(this._hasCacheEntryExpired(s))return void(t.deleteExpired&&await this._delete(s));const r=await this._store.get(s);return r?(await this._updateMetadata(s),a.prepareGetEntry(r,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 s=t.hashKey?n.Md5.hashStr(e):e;if(this._hasCacheEntryExpired(s))return t.deleteExpired&&await this._delete(s),!1;return!!await this._store.has(s)&&(this._getCacheability(s)??!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 s=e.entries.map((([e,t])=>[e,a.prepareSetEntry(t,this._valueFormatting,this._encryptionSecret)]));await this._store.import(s),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,r){if(!this._ready||!this._store)return this._addRequestToQueue(a.constants.SET,e,t,r);const i=new s.Cacheability(r.cacheOptions),{cacheControl:o}=i.metadata;if(o.noStore||this._sharedCache&&o.private)return;const c=r.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,s=a.prepareSetEntry(t,this._valueFormatting,this._encryptionSecret);await this._store.set(c,s),await(e?this._updateMetadata(c,a.sizeOf(s),i,r.tag,r.extensions):this._addMetadata(c,a.sizeOf(s),i,r.tag,r.extensions)),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;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,s,r){const n=this._getMetadataEntry(e);if(n)return t?(n.size=t,n.lastUpdated=Date.now(),n.updatedCount+=1):(n.accessedCount+=1,n.lastAccessed=Date.now()),a&&(n.cacheability=a),i.isUndefined(s)||n.tags.push(s),r&&(n.extensions=Object.assign(n.extensions??{},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