yq-store
Version:
A high-performance, persistent and in-memory key-value store with TTL support, built for Node.js applications.
136 lines (131 loc) • 23.7 kB
JavaScript
import h from"node:path";import O from"node:fs/promises";import d from"node:fs/promises";import x from"node:path";import I from"node:crypto";function A(r){return I.createHash("sha256").update(r).digest("hex").substring(0,16)}function S(r){return`${A(r)}.bin`}function p(r,e){return e?`${e}:${r}`:r}function P(r){return r.replace(/[\/\\]/g,"_").replace(/[<>:"|?*]/g,"_").replace(/^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/i,"$1_").replace(/[. ]+$/,"")||"default"}function l(r,e,t=!1){if(e){let i=t?A(e):P(e);return x.join(r,i)}return r}function D(r){if(Buffer.isBuffer(r))return"buffer";if(Array.isArray(r))return"array";if(typeof r=="string")return"string";if(typeof r=="object"&&r!==null)return"object";if(typeof r=="number"||typeof r=="boolean"||typeof r=="bigint")return"string";throw new Error("Unsupported data type. Only objects, arrays, strings, numbers, booleans, and buffers are supported.")}function N(r){let e=JSON.stringify(r),t=Buffer.from(e,"utf8"),i=Buffer.from("yq-cacher-key-2024","utf8");for(let a=0;a<t.length;a++)t[a]^=i[a%i.length];return t}function R(r){let e=Buffer.from("yq-cacher-key-2024","utf8");for(let i=0;i<r.length;i++)r[i]^=e[i%e.length];let t=r.toString("utf8");return JSON.parse(t)}async function m(r){try{await d.mkdir(r,{recursive:!0})}catch(e){if(e.code!=="EEXIST")throw e}}async function C(r,e){let t=`${r}.tmp`;try{let i=x.dirname(r);await m(i),await d.writeFile(t,e),await d.rename(t,r)}catch(i){try{await d.unlink(t)}catch{}throw i}}async function L(r){try{return await d.access(r),!0}catch{return!1}}async function f(r){try{return await d.unlink(r),!0}catch{return!1}}async function y(r,e,t=!0){if(e)try{let i=l(r,e,t);try{await d.access(i)}catch{return}(await d.readdir(i)).length===0&&await d.rmdir(i)}catch{}}var E=class{db;dbFilePath;cacheDir;isInitialized=!1;options;emitter;statements;hotCache=new Map;hotCacheHead=null;hotCacheTail=null;maxHotCacheSize;pendingAccessUpdates=new Map;accessFlushTimer;accessFlushInterval=1e3;maxPendingUpdates=100;constructor(e,t){this.options={...this.getDefaultOptions(),...e},this.emitter=t,this.cacheDir=this.options.cacheDir||"./cache",this.dbFilePath=h.join(this.cacheDir,"meta.yqc"),this.maxHotCacheSize=this.options.hotCacheSize??1e3}getDefaultOptions(){return{cacheDir:"./cache",ttl:0,eviction:!1,maxItems:1e6,evictionInterval:6e4,softDelete:!0,hotCacheSize:1e3,keepAlive:!1,sqlite:{journalMode:"WAL",synchronous:"NORMAL",cacheSize:-1e5,tempStore:"memory",foreignKeys:!1,busyTimeout:5e3}}}isBunRuntime(){return typeof Bun<"u"&&!!Bun?.version}async initialize(){if(this.isBunRuntime()){if(this.db)return this.db;await m(h.dirname(this.dbFilePath)),await m(this.cacheDir);let{Database:e}=await import("bun:sqlite");return this.db=new e(this.dbFilePath),this.configurePragmas(),this.createTables(),this.prepareStatements(),this.startAccessFlushTimer(),this.isInitialized=!0,this.db}return new Promise(async(e,t)=>{try{await m(h.dirname(this.dbFilePath)),await m(this.cacheDir);let{DatabaseSync:i}=await import("node:sqlite");this.db=new i(this.dbFilePath),this.configurePragmas(),this.createTables(),this.prepareStatements(),this.startAccessFlushTimer(),this.isInitialized=!0,e(this.db)}catch(i){t(i)}})}configurePragmas(){if(!this.db)return;let e=this.options.sqlite||{};this.db.exec(`PRAGMA journal_mode = ${e.journalMode||"WAL"};`),this.db.exec(`PRAGMA synchronous = ${e.synchronous||"NORMAL"};`),this.db.exec(`PRAGMA cache_size = ${e.cacheSize||-1e5};`),this.db.exec(`PRAGMA temp_store = ${e.tempStore||"memory"};`),this.db.exec(`PRAGMA foreign_keys = ${e.foreignKeys?"ON":"OFF"};`),this.db.exec("PRAGMA mmap_size = 268435456;"),this.db.exec("PRAGMA page_size = 4096;"),e.busyTimeout&&this.db.exec(`PRAGMA busy_timeout = ${e.busyTimeout};`)}createTables(){if(!this.db)throw new Error("Database not initialized");this.db.exec(`
CREATE TABLE IF NOT EXISTS file_metadata (
id TEXT PRIMARY KEY,
key TEXT NOT NULL,
namespace TEXT,
file_name TEXT NOT NULL,
created_at INTEGER NOT NULL,
expires_at INTEGER,
is_deleted INTEGER NOT NULL DEFAULT 0,
last_accessed INTEGER NOT NULL DEFAULT 0,
data_size INTEGER NOT NULL DEFAULT 0,
data_type TEXT NOT NULL,
is_encrypted_namespace INTEGER NOT NULL DEFAULT 0
) WITHOUT ROWID;
-- Composite index for active record queries (main query pattern)
CREATE INDEX IF NOT EXISTS idx_file_active
ON file_metadata(is_deleted, expires_at, id)
WHERE is_deleted = 0;
-- Namespace-scoped active records
CREATE INDEX IF NOT EXISTS idx_file_ns_active
ON file_metadata(namespace, is_deleted, expires_at, key)
WHERE is_deleted = 0;
-- LRU eviction index
CREATE INDEX IF NOT EXISTS idx_file_lru
ON file_metadata(last_accessed)
WHERE is_deleted = 0;
-- Expiration cleanup index
CREATE INDEX IF NOT EXISTS idx_file_expiry
ON file_metadata(expires_at)
WHERE is_deleted = 0 AND expires_at IS NOT NULL;
`)}prepareStatements(){if(!this.db)throw new Error("Database not initialized");if(this.isBunRuntime()){let e=this.db;this.statements={insertOrReplace:e.prepare(`
INSERT OR REPLACE INTO file_metadata
(id, key, namespace, file_name, created_at, expires_at, is_deleted, last_accessed, data_size, data_type, is_encrypted_namespace)
VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?)
`),selectById:e.prepare(`
SELECT id, key, namespace, file_name, created_at, expires_at, is_deleted, last_accessed, data_size, data_type, is_encrypted_namespace
FROM file_metadata
WHERE id = ? AND is_deleted = 0 AND (expires_at IS NULL OR expires_at > ?)
`),updateLastAccessed:e.prepare(`
UPDATE file_metadata SET last_accessed = ? WHERE id = ?
`),softDelete:e.prepare(`
UPDATE file_metadata SET is_deleted = 1 WHERE id = ?
`),hardDelete:e.prepare(`
DELETE FROM file_metadata WHERE id = ?
`),selectAllKeys:e.prepare(`
SELECT key FROM file_metadata
WHERE is_deleted = 0 AND (expires_at IS NULL OR expires_at > ?)
ORDER BY key
`),selectKeysByNamespace:e.prepare(`
SELECT key FROM file_metadata
WHERE namespace = ? AND is_deleted = 0 AND (expires_at IS NULL OR expires_at > ?)
ORDER BY key
`),selectFileInfoByNamespace:e.prepare(`
SELECT key, namespace, file_name, is_encrypted_namespace FROM file_metadata
WHERE namespace = ? AND is_deleted = 0
`),countActive:e.prepare(`
SELECT COUNT(*) as count FROM file_metadata
WHERE is_deleted = 0 AND (expires_at IS NULL OR expires_at > ?)
`),countDeleted:e.prepare(`
SELECT COUNT(*) as count FROM file_metadata WHERE is_deleted = 1
`),countExpired:e.prepare(`
SELECT COUNT(*) as count FROM file_metadata
WHERE is_deleted = 0 AND expires_at IS NOT NULL AND expires_at <= ?
`),countAll:e.prepare(`
SELECT COUNT(*) as count FROM file_metadata
`),countActiveEntries:e.prepare(`
SELECT COUNT(*) as count FROM file_metadata WHERE is_deleted = 0
`),selectExpiredEntries:e.prepare(`
SELECT key, namespace, file_name, is_encrypted_namespace FROM file_metadata
WHERE is_deleted = 0 AND expires_at IS NOT NULL AND expires_at <= ?
`),deleteExpired:e.prepare(`
DELETE FROM file_metadata
WHERE is_deleted = 0 AND expires_at IS NOT NULL AND expires_at <= ?
`),selectLRUEntries:e.prepare(`
SELECT key, namespace, file_name, is_encrypted_namespace, id FROM file_metadata
WHERE is_deleted = 0
ORDER BY last_accessed ASC
LIMIT ?
`),batchUpdateAccessed:e.prepare(`
UPDATE file_metadata SET last_accessed = ? WHERE id = ?
`)}}else{let e=this.db;this.statements={insertOrReplace:e.prepare(`
INSERT OR REPLACE INTO file_metadata
(id, key, namespace, file_name, created_at, expires_at, is_deleted, last_accessed, data_size, data_type, is_encrypted_namespace)
VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?)
`),selectById:e.prepare(`
SELECT id, key, namespace, file_name, created_at, expires_at, is_deleted, last_accessed, data_size, data_type, is_encrypted_namespace
FROM file_metadata
WHERE id = ? AND is_deleted = 0 AND (expires_at IS NULL OR expires_at > ?)
`),updateLastAccessed:e.prepare(`
UPDATE file_metadata SET last_accessed = ? WHERE id = ?
`),softDelete:e.prepare(`
UPDATE file_metadata SET is_deleted = 1 WHERE id = ?
`),hardDelete:e.prepare(`
DELETE FROM file_metadata WHERE id = ?
`),selectAllKeys:e.prepare(`
SELECT key FROM file_metadata
WHERE is_deleted = 0 AND (expires_at IS NULL OR expires_at > ?)
ORDER BY key
`),selectKeysByNamespace:e.prepare(`
SELECT key FROM file_metadata
WHERE namespace = ? AND is_deleted = 0 AND (expires_at IS NULL OR expires_at > ?)
ORDER BY key
`),selectFileInfoByNamespace:e.prepare(`
SELECT key, namespace, file_name, is_encrypted_namespace FROM file_metadata
WHERE namespace = ? AND is_deleted = 0
`),countActive:e.prepare(`
SELECT COUNT(*) as count FROM file_metadata
WHERE is_deleted = 0 AND (expires_at IS NULL OR expires_at > ?)
`),countDeleted:e.prepare(`
SELECT COUNT(*) as count FROM file_metadata WHERE is_deleted = 1
`),countExpired:e.prepare(`
SELECT COUNT(*) as count FROM file_metadata
WHERE is_deleted = 0 AND expires_at IS NOT NULL AND expires_at <= ?
`),countAll:e.prepare(`
SELECT COUNT(*) as count FROM file_metadata
`),countActiveEntries:e.prepare(`
SELECT COUNT(*) as count FROM file_metadata WHERE is_deleted = 0
`),selectExpiredEntries:e.prepare(`
SELECT key, namespace, file_name, is_encrypted_namespace FROM file_metadata
WHERE is_deleted = 0 AND expires_at IS NOT NULL AND expires_at <= ?
`),deleteExpired:e.prepare(`
DELETE FROM file_metadata
WHERE is_deleted = 0 AND expires_at IS NOT NULL AND expires_at <= ?
`),selectLRUEntries:e.prepare(`
SELECT key, namespace, file_name, is_encrypted_namespace, id FROM file_metadata
WHERE is_deleted = 0
ORDER BY last_accessed ASC
LIMIT ?
`),batchUpdateAccessed:e.prepare(`
UPDATE file_metadata SET last_accessed = ? WHERE id = ?
`)}}}startAccessFlushTimer(){this.accessFlushTimer&&clearInterval(this.accessFlushTimer),this.accessFlushTimer=setInterval(()=>{this.flushAccessUpdates()},this.accessFlushInterval),!this.options.keepAlive&&this.accessFlushTimer.unref&&this.accessFlushTimer.unref()}flushAccessUpdates(){if(this.pendingAccessUpdates.size===0||!this.db||!this.statements)return;let e=Array.from(this.pendingAccessUpdates.entries());if(this.pendingAccessUpdates.clear(),this.isBunRuntime()){let t=this.db;t.exec("BEGIN IMMEDIATE");try{for(let[i,a]of e)this.statements.batchUpdateAccessed.run(a,i);t.exec("COMMIT")}catch(i){t.exec("ROLLBACK"),this.options.debug&&console.warn("Failed to flush access updates:",i)}}else{let t=this.db;t.exec("BEGIN IMMEDIATE");try{for(let[i,a]of e)this.statements.batchUpdateAccessed.run(a,i);t.exec("COMMIT")}catch(i){t.exec("ROLLBACK"),this.options.debug&&console.warn("Failed to flush access updates:",i)}}}queueAccessUpdate(e){this.pendingAccessUpdates.set(e,Date.now()),this.pendingAccessUpdates.size>=this.maxPendingUpdates&&this.flushAccessUpdates()}addToHotCache(e,t,i){if(this.maxHotCacheSize<=0)return;if(this.hotCache.has(e)){this.moveToFrontOfHotCache(e);let s=this.hotCache.get(e);s.value=t,s.entry=i;return}for(;this.hotCache.size>=this.maxHotCacheSize&&this.hotCacheTail;)this.evictFromHotCache();let a={value:t,entry:i,prev:null,next:this.hotCacheHead};if(this.hotCacheHead){let s=this.hotCache.get(this.hotCacheHead);s&&(s.prev=e)}this.hotCache.set(e,a),this.hotCacheHead=e,this.hotCacheTail||(this.hotCacheTail=e)}getFromHotCache(e){let t=this.hotCache.get(e);return t?t.entry.expiresAt&&t.entry.expiresAt<=Date.now()?(this.removeFromHotCache(e),null):(this.moveToFrontOfHotCache(e),{value:t.value,entry:t.entry}):null}moveToFrontOfHotCache(e){if(this.hotCacheHead===e)return;let t=this.hotCache.get(e);if(t){if(t.prev){let i=this.hotCache.get(t.prev);i&&(i.next=t.next)}if(t.next){let i=this.hotCache.get(t.next);i&&(i.prev=t.prev)}if(this.hotCacheTail===e&&(this.hotCacheTail=t.prev),t.prev=null,t.next=this.hotCacheHead,this.hotCacheHead){let i=this.hotCache.get(this.hotCacheHead);i&&(i.prev=e)}this.hotCacheHead=e}}removeFromHotCache(e){let t=this.hotCache.get(e);if(t){if(t.prev){let i=this.hotCache.get(t.prev);i&&(i.next=t.next)}else this.hotCacheHead=t.next;if(t.next){let i=this.hotCache.get(t.next);i&&(i.prev=t.prev)}else this.hotCacheTail=t.prev;this.hotCache.delete(e)}}evictFromHotCache(){this.hotCacheTail&&this.removeFromHotCache(this.hotCacheTail)}clearHotCache(){this.hotCache.clear(),this.hotCacheHead=null,this.hotCacheTail=null}async close(){if(this.flushAccessUpdates(),this.accessFlushTimer&&(clearInterval(this.accessFlushTimer),this.accessFlushTimer=void 0),this.clearHotCache(),this.isBunRuntime()){if(this.db)try{this.db.close(),this.db=void 0}catch(e){this.options.debug&&console.warn("Warning: Error closing Bun SQLite database:",e),this.db=void 0}this.isInitialized=!1,this.statements=void 0,this.emitter.emit("close");return}return new Promise((e,t)=>{try{this.db&&(this.db.close(),this.db=void 0),this.isInitialized=!1,this.statements=void 0,this.emitter.emit("close"),e()}catch(i){t(i)}})}async set(e,t,i,a){if(!this.isInitialized||!this.statements)throw new Error("Storage not initialized");let s=Date.now(),n=i?s+i*1e3:this.options.ttl?s+this.options.ttl*1e3:null,c=D(t),o=S(e),u=l(this.cacheDir,a,this.options.encryptNamespace),F=h.join(u,o);await m(u);let T=N(t);await C(F,T);let v=p(e,a),b=this.options.encryptNamespace?1:0;this.statements.insertOrReplace.run(v,e,a||null,o,s,n,s,T.length,c,b);let w={key:e,namespace:a,fileName:o,createdAt:s,expiresAt:n??void 0,isDeleted:!1,lastAccessed:s,dataSize:T.length,dataType:c,isEncryptedNamespace:!!b};this.addToHotCache(v,t,w),this.emitter.emit("set",e,t)}async get(e,t){if(!this.isInitialized||!this.statements)return null;let i=Date.now(),a=p(e,t),s=this.getFromHotCache(a);if(s)return this.queueAccessUpdate(a),s.entry;let n=this.statements.selectById.get(a,i);return n?(this.queueAccessUpdate(a),{key:n.key,namespace:n.namespace,fileName:n.file_name,createdAt:n.created_at,expiresAt:n.expires_at,isDeleted:!!n.is_deleted,lastAccessed:i,dataSize:n.data_size,dataType:n.data_type,isEncryptedNamespace:!!n.is_encrypted_namespace}):null}async getValue(e,t){let i=p(e,t),a=this.getFromHotCache(i);if(a)return this.queueAccessUpdate(i),a.value;let s=await this.get(e,t);if(!s)return null;let n=l(this.cacheDir,s.namespace,s.isEncryptedNamespace),c=h.join(n,s.fileName);if(!await L(c))return await this.delete(e,t),null;let o=await O.readFile(c),u=R(o);return this.addToHotCache(i,u,s),u}async has(e,t){if(!this.isInitialized||!this.statements)return!1;let i=p(e,t);if(this.hotCache.has(i)){let s=this.hotCache.get(i);if(!s.entry.expiresAt||s.entry.expiresAt>Date.now())return!0;this.removeFromHotCache(i)}let a=this.statements.selectById.get(i,Date.now());return a!=null}async delete(e,t){if(!this.isInitialized||!this.statements)return!1;let i=p(e,t);this.removeFromHotCache(i),this.pendingAccessUpdates.delete(i);let a=await this.get(e,t);if(!a)return!1;if(this.options.softDelete)this.statements.softDelete.run(i);else{let s=l(this.cacheDir,a.namespace,a.isEncryptedNamespace),n=h.join(s,a.fileName);await f(n),this.statements.hardDelete.run(i),await y(this.cacheDir,a.namespace,a.isEncryptedNamespace)}return this.emitter.emit("delete",e),!0}async keys(){return!this.isInitialized||!this.statements?[]:this.statements.selectAllKeys.all(Date.now()).map(t=>t.key)}async keysByNamespace(e){return!this.isInitialized||!this.statements?[]:this.statements.selectKeysByNamespace.all(e,Date.now()).map(i=>i.key)}async clear(){if(!(!this.isInitialized||!this.db))if(this.clearHotCache(),this.pendingAccessUpdates.clear(),this.options.softDelete)this.db.exec("UPDATE file_metadata SET is_deleted = 1");else{this.db.exec("DELETE FROM file_metadata");try{await O.rm(this.cacheDir,{recursive:!0,force:!0}),await m(this.cacheDir)}catch{}}}async clearNamespace(e){if(!this.isInitialized||!this.statements||!this.db)return;let t=await this.keysByNamespace(e);for(let a of t){let s=p(a,e);this.removeFromHotCache(s),this.pendingAccessUpdates.delete(s)}let i=this.statements.selectFileInfoByNamespace.all(e);for(let a of i)try{let s=l(this.cacheDir,a.namespace||void 0,!!a.is_encrypted_namespace),n=h.join(s,a.file_name);await f(n)}catch(s){this.options.debug&&console.warn(`Failed to delete file for key ${a.key}:`,s)}this.isBunRuntime()?this.db.prepare("DELETE FROM file_metadata WHERE namespace = ?").run(e):this.db.prepare("DELETE FROM file_metadata WHERE namespace = ?").run(e),await y(this.cacheDir,e,this.options.encryptNamespace);for(let a of t)this.emitter.emit("delete",a)}async getSize(e="active"){if(!this.isInitialized||!this.statements)return 0;let t=Date.now(),i;switch(e){case"active":i=this.statements.countActive.get(t);break;case"deleted":i=this.statements.countDeleted.get();break;case"expired":i=this.statements.countExpired.get(t);break;case"all":i=this.statements.countAll.get();break}return i?.count||0}async clearExpiredEntries(){if(!this.isInitialized||!this.statements)return 0;let e=Date.now(),t=this.statements.selectExpiredEntries.all(e),i=new Set;for(let s of t){let n=p(s.key,s.namespace||void 0);this.removeFromHotCache(n),this.pendingAccessUpdates.delete(n);try{let c=l(this.cacheDir,s.namespace||void 0,!!s.is_encrypted_namespace),o=h.join(c,s.file_name);await f(o),s.namespace&&i.add(s.namespace)}catch(c){this.options.debug&&console.warn(`Failed to delete expired file for key ${s.key}:`,c)}}let a=this.statements.deleteExpired.run(e);for(let s of i)await y(this.cacheDir,s,this.options.encryptNamespace);for(let s of t)this.emitter.emit("expire",s.key);return Number(a.changes||0)}async evictLRUIfNeeded(){if(!this.options.eviction||!this.options.maxItems||!this.statements)return 0;this.flushAccessUpdates();let t=this.statements.countActiveEntries.get().count;if(t<=this.options.maxItems)return 0;let i=t-this.options.maxItems,a=this.statements.selectLRUEntries.all(i),s=new Set,n=0;for(let c of a)try{this.removeFromHotCache(c.id),this.pendingAccessUpdates.delete(c.id);let o=l(this.cacheDir,c.namespace||void 0,!!c.is_encrypted_namespace),u=h.join(o,c.file_name);await f(u),c.namespace&&s.add(c.namespace),this.statements.hardDelete.run(c.id),n++,this.emitter.emit("evict",c.key)}catch(o){this.options.debug&&console.warn(`Failed to evict entry ${c.key}:`,o)}for(let c of s)await y(this.cacheDir,c,this.options.encryptNamespace);return n}async batch(e){if(!this.isInitialized||!this.db)throw new Error("Storage not initialized");if(this.isBunRuntime()){let t=this.db;t.exec("BEGIN IMMEDIATE");try{for(let i of e)i.type==="put"?await this.set(i.key,i.value,i.ttlSeconds,i.namespace):i.type==="del"&&await this.delete(i.key,i.namespace);t.exec("COMMIT")}catch(i){throw t.exec("ROLLBACK"),i}}else{let t=this.db;t.exec("BEGIN IMMEDIATE");try{for(let i of e)i.type==="put"?await this.set(i.key,i.value,i.ttlSeconds,i.namespace):i.type==="del"&&await this.delete(i.key,i.namespace);t.exec("COMMIT")}catch(i){throw t.exec("ROLLBACK"),i}}}getHotCacheStats(){return{size:this.hotCache.size,maxSize:this.maxHotCacheSize,hitRate:0}}getPendingUpdatesCount(){return this.pendingAccessUpdates.size}};import{EventEmitter as U}from"node:events";var g=class{emitter=new U;addEventListener(e,t){return this.emitter.on(e,t),this}on(e,t){return this.addEventListener(e,t)}removeEventListener(e,t){return this.emitter.removeListener(e,t),this}off(e,t){return this.removeEventListener(e,t)}emit(e,...t){return this.emitter.emit(e,...t)}};var _=class r extends g{storageManager;options;isInitialized=!1;evictionTimer;constructor(e={}){super(),this.options={cacheDir:"./cache",encryptNamespace:!1,...e},this.storageManager=new E(this.options,this)}static async create(e={}){let t=new r(e);return await t.initialize(),t}async initialize(){this.isInitialized||(await this.storageManager.initialize(),this.isInitialized=!0,this.options.eviction&&this.options.maxItems&&this.startEvictionTimer(),this.emit("ready"))}startEvictionTimer(){this.evictionTimer&&clearInterval(this.evictionTimer);let e=this.options.evictionInterval||6e4;this.options.eviction&&(this.evictionTimer=setInterval(async()=>{try{await this.storageManager.evictLRUIfNeeded()}catch(t){this.options.debug&&console.warn("LRU eviction failed:",t)}},e),!this.options.keepAlive&&this.evictionTimer.unref&&this.evictionTimer.unref())}async set(e,t,i,a){let s,n;typeof i=="number"?(s=i,n=a):typeof i=="string"&&(n=i),s===void 0&&this.options.ttl&&(s=this.options.ttl),await this.storageManager.set(e,t,s,n),this.emit("set",e,t)}async get(e,t){return await this.storageManager.getValue(e,t)}async has(e,t){return await this.storageManager.has(e,t)}async delete(e,t){if(!this.isInitialized)throw new Error("YqCacher not initialized. Use YqCacher.create() instead of new YqCacher()");return await this.storageManager.delete(e,t)}async keys(e){return e!==void 0?this.storageManager.keysByNamespace(e):await this.storageManager.keys()}async clear(e){if(e!==void 0)return this.storageManager.clearNamespace(e);await this.storageManager.clear()}async size(){return await this.storageManager.getSize()}async clearExpired(){return await this.storageManager.clearExpiredEntries()}async batch(e){await this.storageManager.batch(e)}async getStats(){let e=await this.size(),t=await this.storageManager.getSize("expired"),i=this.storageManager.getHotCacheStats(),a=this.storageManager.getPendingUpdatesCount();return{totalEntries:e,totalSize:e,expiredEntries:t,hotCache:i,pendingUpdates:a}}async close(){this.isInitialized&&(this.evictionTimer&&(clearInterval(this.evictionTimer),this.evictionTimer=void 0),await this.storageManager.close(),this.isInitialized=!1,this.emit("close"))}addEventListener(e,t){return super.addEventListener(e,t)}removeEventListener(e,t){return super.removeEventListener(e,t)}};export{_ as YqCacher};