@cachemap/core
Version:
The Cachemap core module.
3 lines (2 loc) • 12.5 kB
JavaScript
import"core-js/modules/es.array.push.js";import{instance as e}from"@cachemap/controller";import{MapStore as t}from"@cachemap/map";import{ValueFormat as a,ArgsError as s,GroupedError as i,isJsonValue as r,constants as n,prepareSetEntry as h,dehydrateMetadata as o,PositionError as c,prepareGetEntry as p,rehydrateMetadata as _,sizeOf as d}from"@cachemap/utils";export*from"@cachemap/utils";import{Cacheability as u}from"cacheability";import{EventEmitter as m}from"eventemitter3";import{isString as l,isPlainObject as y,isFunction as S,isNumber as g,isArray as w,castArray as k,get as b,isUndefined as E}from"lodash-es";import{Md5 as f}from"ts-md5";export*from"@cachemap/types";class v{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})=>{(l(e)&&e===this._name||l(t)&&t===this._type)&&this._clear()},this._handleStartReaperEvent=({name:e,type:t})=>{(l(e)&&e===this._name||l(t)&&t===this._type)&&this._reaper?.start()},this._handleStopReaperEvent=({name:e,type:t})=>{(l(e)&&e===this._name||l(t)&&t===this._type)&&this._reaper?.stop()},this._handleStartBackupEvent=({name:e,type:t})=>{(l(e)&&e===this._name||l(t)&&t===this._type)&&this._startBackup()},this._handleStopBackupEvent=({name:e,type:t})=>{(l(e)&&e===this._name||l(t)&&t===this._type)&&this._stopBackup()},this._backupInterval=1e4,this._emitter=new m,this._maxHeapSize=4194304,this._metadata=[],this._persistedStore=!0,this._processing=[],this._ready=!1,this._requestQueue=[],this._usedHeapSize=0,this._valueFormatting=a.String;const r=[];if(y(e)||r.push(new s("@cachemap/core expected options to be a plain object.")),l(e.name)||r.push(new s("@cachemap/core expected options.name to be a string.")),S(e.store)||r.push(new s("@cachemap/core expected options.store to be a function.")),l(e.type)||r.push(new s("@cachemap/core expected options.type to be a string.")),e.valueFormatting!==a.Ecrypt||e.encryptionSecret||r.push(new s('@cachemap/core expected encryptionSecret to be set when valueFormatting is "encrypt"')),r.length>0)throw new i("@cachemap/core constructor argument validation errors.",r);const{backupInterval:n,backupStore:h,disableCacheInvalidation:o=!1,encryptionSecret:c,name:p,reaper:_,sharedCache:d=!1,sortComparator:u,startBackup:w,store:k,type:b,valueFormatting:E}=e;this._disableCacheInvalidation=o,E&&(this._valueFormatting=E),l(c)&&(this._encryptionSecret=c),this._name=p,S(_)&&(this._reaper=this._initializeReaper(_)),this._sharedCache=d,S(u)&&(v._sortComparator=u),this._type=b,this._addControllerEventListeners(),Promise.resolve(k({name:p})).then((async e=>{if(this._maxHeapSize=e.maxHeapSize,h){if("map"===e.type)throw new s("@cachemap/core expected store.type not to be 'map' when backupStore is true.");g(n)&&(this._backupInterval=n),this._backupStore=e,this._persistedStore=!0,this._store=new t({maxHeapSize:e.maxHeapSize,name:p}),await this._backupStoreEntriesToStore(),this._ready=!0,this._releaseQueuedRequests(),w&&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 a=[];if(l(e)||a.push(new s("@cachemap/core expected key to be a string.")),y(t)||a.push(new s("@cachemap/core expected options to be a plain object.")),a.length>0)throw new i("@cachemap/core delete argument validation errors.",a);return this._delete(e,t)}get emitter(){return this._emitter}async entries(e){if(e&&!w(e))throw new s("@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(y(e)||t.push(new s("@cachemap/core expected options to be an plain object.")),e.keys&&!w(e.keys)&&t.push(new s("@cachemap/core expected options.keys to be an array.")),t.length>0)throw new i("@cachemap/core export argument validation errors.",t);const{entries:a,metadata:r}=await this._export(e);return{entries:a.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 a=[];if(l(e)||a.push(new s("@cachemap/core expected key to be a string.")),y(t)||a.push(new s("@cachemap/core expected options to be a plain object.")),a.length>0)throw new i("@cachemap/core get argument validation errors.",a);return this._get(e,t)}getMetadataEntry(e,t={}){const a=t.hashKey?f.hashStr(e):e;return this._getMetadataEntry(a)}async has(e,t={}){const a=[];if(l(e)||a.push(new s("@cachemap/core expected key to be a string.")),y(t)||a.push(new s("@cachemap/core expected opts to be a plain object.")),a.length>0)throw new i("@cachemap/core has argument validation errors.",a);return this._has(e,t)}async import(e){if(!y(e))throw new s("@cachemap/core expected options to be a plain object.");const{entries:t,metadata:a}=e,r=[];if(w(t)||r.push(new s("@cachemap/core expected entries to be an array.")),w(a)||r.push(new s("@cachemap/core expected metadata to be an array.")),r.length>0)throw new i("@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,a={}){const n=[];if(l(e)||n.push(new s("@cachemap/core expected key to be a string.")),y(a)||n.push(new s("@cachemap/core expected options to be a plain object.")),r(t)||n.push(new s("@cachemap/core expected value to be JSON serializable.")),n.length>0)throw new i("@cachemap/core set argument validation errors.",n);return this._set(e,t,a)}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.on(n.CLEAR,this._handleClearEvent),e.on(n.START_REAPER,this._handleStartReaperEvent),e.on(n.STOP_REAPER,this._handleStopReaperEvent),e.on(n.START_BACKUP,this._handleStartBackupEvent),e.on(n.STOP_BACKUP,this._handleStopBackupEvent)}async _addMetadata(e,t,a,s,i){return this._metadata.push({accessedCount:0,added:Date.now(),cacheability:a,extensions:i,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(n.METADATA,h(o(this._metadata),this._valueFormatting,this._encryptionSecret))}async _backupStoreEntriesToStore(){if(!this._backupStore||!this._store)throw new c("@cachemap/core expected backupStoreEntriesToStore to be called after setting the backupStore and store.");this._metadata=[];const e=await this._backupStore.get(n.METADATA);if(e){const t=p(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=_(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(n.CLEAR)}async _delete(e,t={}){if(!this._ready||!this._store)return this._addRequestToQueue(n.DELETE,e,t);const a=t.hashKey?f.hashStr(e):e;return!!await this._store.delete(a)&&(await this._deleteMetadata(a),!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(n.ENTRIES,e);const t=e??this._metadata.map((e=>e.key));return(await this._store.entries(t)).map((([e,t])=>[e,p(t,this._valueFormatting,this._encryptionSecret)]))}async _export({cleanupTag:e,filterByValue:t,keys:a,tag:s}){let i,r=[...this._metadata];s?(r=this._metadata.filter((e=>e.tags.includes(s))),i=r.map((e=>e.key)),e&&this._cleanupTag(s)):a&&(r=this._metadata.filter((e=>a.includes(e.key))),i=a);let n=await this._entries(i);if(t){const e=k(t);n=n.filter((([,t])=>e.every((({comparator:e,keyChain:a})=>b(t,a)===e)))),r=r.filter((e=>n.some((([t])=>t===e.key))))}return{entries:n,metadata:r}}async _get(e,t){if(!this._ready||!this._store)return this._addRequestToQueue(n.GET,e,t);const a=t.hashKey?f.hashStr(e):e;if(this._hasCacheEntryExpired(a))return void(t.deleteExpired&&await this._delete(a));const s=await this._store.get(a);return s?(await this._updateMetadata(a),p(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(n.HAS,e,t);const a=t.hashKey?f.hashStr(e):e;if(this._hasCacheEntryExpired(a))return t.deleteExpired&&await this._delete(a),!1;return!!await this._store.has(a)&&(this._getCacheability(a)??!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(n.IMPORT,e);let t=[];this._metadata.length>0&&(t=this._metadata.filter((t=>!e.metadata.some((e=>t.key===e.key)))));const a=e.entries.map((([e,t])=>[e,h(t,this._valueFormatting,this._encryptionSecret)]));await this._store.import(a),this._metadata=_([...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(n.METADATA);e&&(this._metadata=_(p(e,this._valueFormatting,this._encryptionSecret)))}async _set(e,t,a){if(!this._ready||!this._store)return this._addRequestToQueue(n.SET,e,t,a);const s=new u(a.cacheOptions),{cacheControl:i}=s.metadata;if(i.noStore||this._sharedCache&&i.private)return;const r=a.hashKey?f.hashStr(e):e,o=this._processing.includes(r);o||this._processing.push(r);try{const e=await this._store.has(r)||o,i=h(t,this._valueFormatting,this._encryptionSecret);await this._store.set(r,i),await(e?this._updateMetadata(r,d(i),s,a.tag,a.extensions):this._addMetadata(r,d(i),s,a.tag,a.extensions)),this._processed(r)}catch(e){throw this._processed(r),e}}async _size(){return this._ready&&this._store?this._store.size():this._addRequestToQueue(n.SIZE)}_sortMetadata(){this._metadata.sort(v._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,i){const r=this._getMetadataEntry(e);if(r)return t?(r.size=t,r.lastUpdated=Date.now(),r.updatedCount+=1):(r.accessedCount+=1,r.lastAccessed=Date.now()),a&&(r.cacheability=a),E(s)||r.tags.push(s),i&&(r.extensions=Object.assign(r.extensions??{},i)),this._sortMetadata(),this._updateHeapSize(),this._backupMetadata()}}export{v as Core};
//# sourceMappingURL=index.mjs.map