yq-store
Version:
A high-performance, persistent and in-memory key-value store with TTL support, built for Node.js applications.
156 lines (153 loc) • 29.1 kB
JavaScript
"use strict";var P=Object.create;var y=Object.defineProperty;var O=Object.getOwnPropertyDescriptor;var M=Object.getOwnPropertyNames;var D=Object.getPrototypeOf,I=Object.prototype.hasOwnProperty;var w=(d,e)=>{for(var t in e)y(d,t,{get:e[t],enumerable:!0})},S=(d,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of M(e))!I.call(d,r)&&r!==t&&y(d,r,{get:()=>e[r],enumerable:!(s=O(e,r))||s.enumerable});return d};var m=(d,e,t)=>(t=d!=null?P(D(d)):{},S(e||!d||!d.__esModule?y(t,"default",{value:d,enumerable:!0}):t,d)),F=d=>S(y({},"__esModule",{value:!0}),d);var W={};w(W,{TypedEventEmitter:()=>E,YqStore:()=>h,default:()=>h});module.exports=F(W);function f(){process.env.NODE_NO_WARNINGS="1",process.removeAllListeners("warning"),process.on("warning",e=>{e.name==="ExperimentalWarning"&&e.message.includes("SQLite is an experimental feature")||e.message.includes("ExperimentalWarning")});let d=process.emitWarning;process.emitWarning=function(e,...t){let s=typeof e=="string"?e:e.message;if(!(s.includes("ExperimentalWarning")&&s.includes("SQLite")||s.includes("ExperimentalWarning")))return d.call(this,e,...t)}}f();var b=m(require("node:path"),1),R=m(require("node:fs/promises"),1);var _=require("node:events"),E=class{emitter=new _.EventEmitter;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 u=class{constructor(e,t,s,r=!0){this.dbDir=e;this.dbName=t;this.emitter=s,this.softDelete=r}emitter;softDelete};var v=class extends u{db;dbFilePath;isInitialized=!1;config;isMemoryMode;maxEntries;maxMemory;constructor(e,t,s,r=!0,i){if(super(e,t,s,r),this.config=i||{},this.isMemoryMode=i?.type==="memory",this.isMemoryMode?(this.maxEntries=i?.maxEntries||5e4,this.maxMemory=i?.maxMemory||100*1024*1024):(this.maxEntries=i?.maxEntries||1e6,this.maxMemory=i?.maxMemory||0),this.isMemoryMode)this.dbFilePath=":memory:";else{let a=this.config.dbFileName||`${t}.yqs`;this.dbFilePath=b.default.resolve(e,a)}}async initialize(){return this.isBunRuntime()?new Promise(async(e,t)=>{try{this.isMemoryMode||(await R.default.mkdir(b.default.dirname(this.dbFilePath),{recursive:!0}),this.dbFilePath=b.default.resolve(this.dbFilePath));let s=(await import("bun:sqlite")).Database;this.db=new s(this.dbFilePath);let r=this.config?.sqlite||{};if(this.isMemoryMode?(this.db.exec("PRAGMA journal_mode=MEMORY;"),this.db.exec("PRAGMA synchronous=OFF;"),this.db.exec("PRAGMA cache_size=10000;"),this.db.exec("PRAGMA temp_store=memory;")):(this.db.exec(`PRAGMA journal_mode=${r.journalMode||"WAL"};`),this.db.exec(`PRAGMA synchronous=${r.synchronous||"NORMAL"};`),this.db.exec(`PRAGMA cache_size=${r.cacheSize||-1e5};`),this.db.exec(`PRAGMA temp_store=${r.tempStore||"memory"};`)),this.db.exec(`PRAGMA foreign_keys=${r.foreignKeys?"ON":"OFF"};`),r.busyTimeout&&this.db.exec(`PRAGMA busy_timeout=${r.busyTimeout};`),!this.isMemoryMode){let i=this.config?.vacuum||{};if(i.enabled!==!1){let a=i.mode||"incremental";a!=="none"&&this.db.exec(`PRAGMA auto_vacuum=${a==="full"?"FULL":"INCREMENTAL"};`)}}this.db.exec(`
CREATE TABLE IF NOT EXISTS kv_store (
key TEXT PRIMARY KEY,
value BLOB NOT NULL,
created_at INTEGER NOT NULL,
expires_at INTEGER,
is_deleted INTEGER DEFAULT 0,
version INTEGER NOT NULL DEFAULT 1,
last_accessed INTEGER DEFAULT (strftime('%s', 'now') * 1000),
format TEXT NOT NULL DEFAULT 'binary',
compression TEXT NOT NULL DEFAULT 'none'
);
CREATE INDEX IF NOT EXISTS idx_kv_expires_at ON kv_store(expires_at);
CREATE INDEX IF NOT EXISTS idx_kv_created_at ON kv_store(created_at);
CREATE INDEX IF NOT EXISTS idx_kv_is_deleted ON kv_store(is_deleted);
CREATE INDEX IF NOT EXISTS idx_kv_last_accessed ON kv_store(last_accessed);
`),this.db.exec(`
CREATE TRIGGER IF NOT EXISTS lru_eviction
AFTER INSERT ON kv_store
WHEN (SELECT COUNT(*) FROM kv_store WHERE is_deleted = 0) > ${this.maxEntries}
BEGIN
DELETE FROM kv_store
WHERE key IN (
SELECT key FROM kv_store
WHERE is_deleted = 0
ORDER BY last_accessed ASC
LIMIT 1
);
END;
`),this.isInitialized=!0,e(this.db)}catch(s){t(s)}}):this.db}isBunRuntime(){return typeof Bun<"u"&&!!Bun?.version}async close(){return new Promise((e,t)=>{try{this.db&&(this.db.close(),this.db=void 0),this.isInitialized=!1,e()}catch(s){t(s)}})}valueToBinary(e){let t=JSON.stringify(e);return new TextEncoder().encode(t)}binaryToValue(e){let t=new TextDecoder().decode(e);return JSON.parse(t)}async set(e,t,s){return new Promise((r,i)=>{try{if(!this.isInitialized)throw new Error("Storage not initialized");let a=Date.now(),o=s?a+s*1e3:null,n=this.valueToBinary(t);this.db.prepare(`
INSERT OR REPLACE INTO kv_store
(key, value, created_at, expires_at, is_deleted, version, last_accessed)
VALUES (?, ?, ?, ?, 0, COALESCE((SELECT version + 1 FROM kv_store WHERE key = ?), 1), ?)
`).run(e,n,a,o,e,a),this.emitter.emit("set",e,t),r()}catch(a){i(a)}})}async get(e){return new Promise((t,s)=>{try{if(!this.isInitialized){t(null);return}let r=Date.now();if(this.db.prepare(`
SELECT key FROM kv_store
WHERE key = ? AND expires_at IS NOT NULL AND expires_at <= ?
`).get(e,r)){this.db.prepare("DELETE FROM kv_store WHERE key = ?").run(e),this.emitter.emit("expire",e),t(null);return}let n=this.db.prepare(`
SELECT key, value, created_at, expires_at, is_deleted, version
FROM kv_store
WHERE key = ? AND is_deleted = 0
`).get(e);if(n){this.db.prepare("UPDATE kv_store SET last_accessed = ? WHERE key = ?").run(r,e);let l=n;t({key:l.key,value:this.binaryToValue(l.value),createdAt:l.created_at,expiresAt:l.expires_at||void 0,isDeleted:!1,version:l.version})}else t(null)}catch(r){s(r)}})}async delete(e){return new Promise(async(t,s)=>{try{if(!this.isInitialized){t(!1);return}let r=await this.has(e);if(!r){t(!1);return}this.softDelete?this.db.prepare(`
UPDATE kv_store SET is_deleted = 1 WHERE key = ? AND is_deleted = 0
`).run(e):this.db.prepare("DELETE FROM kv_store WHERE key = ?").run(e);let i=await this.has(e);r&&!i?(this.emitter.emit("delete",e),t(!0)):t(!1)}catch(r){s(r)}})}async has(e){return new Promise((t,s)=>{try{if(!this.isInitialized){t(!1);return}let r=Date.now(),a=this.db.prepare(`
SELECT 1 FROM kv_store
WHERE key = ? AND is_deleted = 0
AND (expires_at IS NULL OR expires_at > ?)
`).get(e,r);t(!!a)}catch(r){s(r)}})}async keys(e={}){return new Promise((t,s)=>{try{if(!this.isInitialized){t([]);return}let r=Date.now(),i=`
SELECT key FROM kv_store
WHERE is_deleted = 0 AND (expires_at IS NULL OR expires_at > ?)
`,a=[r];e.prefix&&(i+=" AND key LIKE ?",a.push(e.prefix+"%")),e.suffix&&(i+=" AND key LIKE ?",a.push("%"+e.suffix));let n=this.db.prepare(i).all(...a);t(n.map(c=>c.key))}catch(r){s(r)}})}async getSize(e="active",t={}){return new Promise((s,r)=>{try{if(!this.isInitialized){s(0);return}let i="",a=[],o=Date.now();switch(e){case"active":i="SELECT COUNT(*) as Total FROM kv_store WHERE is_deleted = 0 AND (expires_at IS NULL OR expires_at > ?)",a.push(o);break;case"deleted":i="SELECT COUNT(*) as Total FROM kv_store WHERE is_deleted = 1";break;case"expired":i="SELECT COUNT(*) as Total FROM kv_store WHERE expires_at IS NOT NULL AND expires_at <= ?",a.push(o);break;case"all":i="SELECT COUNT(*) as Total FROM kv_store";break}t.prefix&&(i+=" AND key LIKE ?",a.push(t.prefix+"%")),t.suffix&&(i+=" AND key LIKE ?",a.push("%"+t.suffix));let n=this.db.prepare(i).get(...a);s(n.Total)}catch(i){r(i)}})}async clear(){return new Promise((e,t)=>{try{if(!this.isInitialized||!this.db){e();return}this.softDelete?this.db.exec("UPDATE kv_store SET is_deleted = 1 WHERE is_deleted = 0"):this.db.exec("DELETE FROM kv_store"),e()}catch(s){t(s)}})}async batch(e){return new Promise((t,s)=>{try{if(!this.isInitialized||!this.db){t();return}this.db.transaction(()=>{let i=Date.now();for(let a of e)if(a.type==="put"){let o=a.ttlSeconds?i+a.ttlSeconds*1e3:null,n=this.valueToBinary(a.value);this.db.prepare(`
INSERT OR REPLACE INTO kv_store
(key, value, created_at, expires_at, is_deleted, version, last_accessed)
VALUES (?, ?, ?, ?, 0, COALESCE((SELECT version + 1 FROM kv_store WHERE key = ?), 1), ?)
`).run(a.key,n,i,o,a.key,i),this.emitter.emit("set",a.key,a.value)}else a.type==="del"&&(this.softDelete?this.db.prepare(`
UPDATE kv_store SET is_deleted = 1 WHERE key = ? AND is_deleted = 0
`).run(a.key):this.db.prepare("DELETE FROM kv_store WHERE key = ?").run(a.key),this.emitter.emit("delete",a.key))})(),t()}catch(r){s(r)}})}async getIncludingDeleted(e){return new Promise((t,s)=>{try{if(!this.isInitialized){t(null);return}let i=this.db.prepare(`
SELECT key, value, created_at, expires_at, is_deleted, version
FROM kv_store WHERE key = ?
`).get(e);if(i){let a=i;t({key:a.key,value:a.is_deleted?null:this.binaryToValue(a.value),createdAt:a.created_at,expiresAt:a.expires_at||void 0,isDeleted:!!a.is_deleted,version:a.version})}else t(null)}catch(r){s(r)}})}async getAllKeysIncludingDeleted(){return new Promise((e,t)=>{try{if(!this.isInitialized){e([]);return}let r=this.db.prepare("SELECT key FROM kv_store").all();e(r.map(i=>i.key))}catch(s){t(s)}})}async clearSoftDeletedEntries(){return new Promise((e,t)=>{try{if(!this.isInitialized){e(0);return}let s=this.db.prepare("SELECT COUNT(*) as count FROM kv_store WHERE is_deleted = 1"),i=s.get()?.count||0;this.db.prepare("DELETE FROM kv_store WHERE is_deleted = 1").run();let n=s.get()?.count||0;e(i-n)}catch(s){t(s)}})}async clearExpiredEntries(){return new Promise((e,t)=>{try{if(!this.isInitialized){e(0);return}let s=Date.now(),i=this.db.prepare(`
SELECT key FROM kv_store
WHERE expires_at IS NOT NULL AND expires_at <= ?
`).all(s),o=this.db.prepare(`
DELETE FROM kv_store
WHERE expires_at IS NOT NULL AND expires_at <= ?
`).run(s);for(let n of i)this.emitter.emit("expire",n.key);e(Number(o.changes)||0)}catch(s){t(s)}})}async evictLRUEntries(e){return new Promise(async(t,s)=>{try{if(!this.isInitialized){t(0);return}let r=await this.getSize("active"),i=e||this.maxEntries;if(r<=i){t(0);return}let a=r-i,n=this.db.prepare(`
SELECT key FROM kv_store
WHERE is_deleted = 0
ORDER BY last_accessed ASC
LIMIT ?
`).all(a),c=this.db.prepare("DELETE FROM kv_store WHERE key = ?"),l=0;for(let T of n){let p=T.key;c.run(p),this.emitter.emit("evict",p),l++}t(l)}catch(r){s(r)}})}async evictExpiredEntries(){return new Promise(async(e,t)=>{try{let s=await this.clearExpiredEntries(),r=await this.evictLRUEntries();e(s+r)}catch(s){t(s)}})}getIndexFilePath(){return this.dbFilePath+".index"}getDataFilePath(){return this.dbFilePath}async getAllEntries(e={}){return new Promise((t,s)=>{try{if(!this.isInitialized){t([]);return}let r=Date.now(),i=`
SELECT key, value, created_at, expires_at, is_deleted, version
FROM kv_store
WHERE is_deleted = 0 AND (expires_at IS NULL OR expires_at > ?)
`,a=[r];e.prefix&&(i+=" AND key LIKE ?",a.push(e.prefix+"%")),e.suffix&&(i+=" AND key LIKE ?",a.push("%"+e.suffix)),e.limit&&(i+=" LIMIT ?",a.push(e.limit));let n=this.db.prepare(i).all(...a);t(n.map(c=>({key:c.key,value:this.binaryToValue(c.value),createdAt:c.created_at,expiresAt:c.expires_at||void 0,isDeleted:!!c.is_deleted,version:c.version})))}catch(r){s(r)}})}};var g=m(require("node:path"),1),k=m(require("node:fs/promises"),1),x=class extends u{db;dbFilePath;isInitialized=!1;config;isMemoryMode;maxEntries;maxMemory;constructor(e,t,s,r=!0,i){if(super(e,t,s,r),this.config=i||{},this.isMemoryMode=i?.type==="memory",this.isMemoryMode?(this.maxEntries=i?.maxEntries||5e4,this.maxMemory=i?.maxMemory||100*1024*1024):(this.maxEntries=i?.maxEntries||1e6,this.maxMemory=i?.maxMemory||0),this.isMemoryMode)this.dbFilePath=":memory:";else{let a=this.config.dbFileName||`${t}.yqs`;this.dbFilePath=g.default.resolve(e,a)}}async initialize(){return this.isBunRuntime()?this.db:new Promise(async(e,t)=>{try{this.isMemoryMode||(await k.default.mkdir(g.default.dirname(this.dbFilePath),{recursive:!0}),this.dbFilePath=g.default.resolve(this.dbFilePath));let s=(await import("node:sqlite")).DatabaseSync;this.db=new s(this.dbFilePath);let r=this.config?.sqlite||{};if(this.isMemoryMode?(this.db.exec("PRAGMA journal_mode=MEMORY;"),this.db.exec("PRAGMA synchronous=OFF;"),this.db.exec("PRAGMA cache_size=10000;"),this.db.exec("PRAGMA temp_store=memory;")):(this.db.exec(`PRAGMA journal_mode=${r.journalMode||"WAL"};`),this.db.exec(`PRAGMA synchronous=${r.synchronous||"NORMAL"};`),this.db.exec(`PRAGMA cache_size=${r.cacheSize||-1e5};`),this.db.exec(`PRAGMA temp_store=${r.tempStore||"memory"};`)),this.db.exec(`PRAGMA foreign_keys=${r.foreignKeys?"ON":"OFF"};`),r.busyTimeout&&this.db.exec(`PRAGMA busy_timeout=${r.busyTimeout};`),!this.isMemoryMode){let i=this.config?.vacuum||{};if(i.enabled!==!1){let a=i.mode||"incremental";a!=="none"&&this.db.exec(`PRAGMA auto_vacuum=${a==="full"?"FULL":"INCREMENTAL"};`)}}this.db.exec(`
CREATE TABLE IF NOT EXISTS kv_store (
key TEXT PRIMARY KEY,
value BLOB NOT NULL,
created_at INTEGER NOT NULL,
expires_at INTEGER,
is_deleted INTEGER DEFAULT 0,
version INTEGER NOT NULL DEFAULT 1,
last_accessed INTEGER DEFAULT (strftime('%s', 'now') * 1000),
format TEXT NOT NULL DEFAULT 'binary',
compression TEXT NOT NULL DEFAULT 'none'
);
CREATE INDEX IF NOT EXISTS idx_kv_expires_at ON kv_store(expires_at);
CREATE INDEX IF NOT EXISTS idx_kv_created_at ON kv_store(created_at);
CREATE INDEX IF NOT EXISTS idx_kv_is_deleted ON kv_store(is_deleted);
CREATE INDEX IF NOT EXISTS idx_kv_last_accessed ON kv_store(last_accessed);
`),this.db.exec(`
CREATE TRIGGER IF NOT EXISTS lru_eviction
AFTER INSERT ON kv_store
WHEN (SELECT COUNT(*) FROM kv_store WHERE is_deleted = 0) > ${this.maxEntries}
BEGIN
DELETE FROM kv_store
WHERE key IN (
SELECT key FROM kv_store
WHERE is_deleted = 0
ORDER BY last_accessed ASC
LIMIT 1
);
END;
`),this.isInitialized=!0,e(this.db)}catch(s){t(s)}})}isBunRuntime(){return typeof Bun<"u"&&!!Bun?.version}async close(){return new Promise((e,t)=>{try{this.db&&(this.db.close(),this.db=void 0),this.isInitialized=!1,e()}catch(s){t(s)}})}valueToBinary(e){let t=JSON.stringify(e);return new TextEncoder().encode(t)}binaryToValue(e){let t=new TextDecoder().decode(e);return JSON.parse(t)}async set(e,t,s){return new Promise((r,i)=>{try{if(!this.isInitialized)throw new Error("Storage not initialized");let a=Date.now(),o=s?a+s*1e3:null,n=this.valueToBinary(t);this.db.prepare(`
INSERT OR REPLACE INTO kv_store
(key, value, created_at, expires_at, is_deleted, version, last_accessed)
VALUES (?, ?, ?, ?, 0, COALESCE((SELECT version + 1 FROM kv_store WHERE key = ?), 1), ?)
`).run(e,n,a,o,e,a),this.emitter.emit("set",e,t),r()}catch(a){i(a)}})}async get(e){return new Promise((t,s)=>{try{if(!this.isInitialized){t(null);return}let r=Date.now();if(this.db.prepare(`
SELECT key FROM kv_store
WHERE key = ? AND expires_at IS NOT NULL AND expires_at <= ?
`).get(e,r)){this.db.prepare("DELETE FROM kv_store WHERE key = ?").run(e),this.emitter.emit("expire",e),t(null);return}let n=this.db.prepare(`
SELECT key, value, created_at, expires_at, is_deleted, version
FROM kv_store
WHERE key = ? AND is_deleted = 0
`).get(e);n?(this.db.prepare("UPDATE kv_store SET last_accessed = ? WHERE key = ?").run(r,e),t({key:n.key,value:this.binaryToValue(n.value),createdAt:n.created_at,expiresAt:n.expires_at||void 0,isDeleted:!1,version:n.version})):t(null)}catch(r){s(r)}})}async delete(e){return new Promise(async(t,s)=>{try{if(!this.isInitialized){t(!1);return}let r=await this.has(e);if(!r){t(!1);return}this.softDelete?this.db.prepare(`
UPDATE kv_store SET is_deleted = 1 WHERE key = ? AND is_deleted = 0
`).run(e):this.db.prepare("DELETE FROM kv_store WHERE key = ?").run(e);let i=await this.has(e);r&&!i?(this.emitter.emit("delete",e),t(!0)):t(!1)}catch(r){s(r)}})}async has(e){return new Promise((t,s)=>{try{if(!this.isInitialized){t(!1);return}let r=Date.now(),a=this.db.prepare(`
SELECT 1 FROM kv_store
WHERE key = ? AND is_deleted = 0
AND (expires_at IS NULL OR expires_at > ?)
`).get(e,r);t(!!a)}catch(r){s(r)}})}async keys(e={}){return new Promise((t,s)=>{try{if(!this.isInitialized){t([]);return}let r=Date.now(),i=`
SELECT key FROM kv_store
WHERE is_deleted = 0 AND (expires_at IS NULL OR expires_at > ?)
`,a=[r];e.prefix&&(i+=" AND key LIKE ?",a.push(e.prefix+"%")),e.suffix&&(i+=" AND key LIKE ?",a.push("%"+e.suffix));let n=this.db.prepare(i).all(...a);t(n.map(c=>c.key))}catch(r){s(r)}})}async getSize(e="active",t={}){return new Promise((s,r)=>{try{if(!this.isInitialized){s(0);return}let i="",a=[],o=Date.now();switch(e){case"active":i="SELECT COUNT(*) as Total FROM kv_store WHERE is_deleted = 0 AND (expires_at IS NULL OR expires_at > ?)",a.push(o);break;case"deleted":i="SELECT COUNT(*) as Total FROM kv_store WHERE is_deleted = 1";break;case"expired":i="SELECT COUNT(*) as Total FROM kv_store WHERE expires_at IS NOT NULL AND expires_at <= ?",a.push(o);break;case"all":i="SELECT COUNT(*) as Total FROM kv_store";break}t.prefix&&(i+=" AND key LIKE ?",a.push(t.prefix+"%")),t.suffix&&(i+=" AND key LIKE ?",a.push("%"+t.suffix));let n=this.db.prepare(i).get(...a);s(n.Total)}catch(i){r(i)}})}async clear(){return new Promise((e,t)=>{try{if(!this.isInitialized||!this.db){e();return}this.softDelete?this.db.exec("UPDATE kv_store SET is_deleted = 1 WHERE is_deleted = 0"):this.db.exec("DELETE FROM kv_store"),e()}catch(s){t(s)}})}async batch(e){return new Promise((t,s)=>{try{if(!this.isInitialized||!this.db){t();return}this.db.exec("BEGIN TRANSACTION");try{let r=Date.now();for(let i of e)if(i.type==="put"){let a=i.ttlSeconds?r+i.ttlSeconds*1e3:null,o=this.valueToBinary(i.value);this.db.prepare(`
INSERT OR REPLACE INTO kv_store
(key, value, created_at, expires_at, is_deleted, version, last_accessed)
VALUES (?, ?, ?, ?, 0, COALESCE((SELECT version + 1 FROM kv_store WHERE key = ?), 1), ?)
`).run(i.key,o,r,a,i.key,r),this.emitter.emit("set",i.key,i.value)}else i.type==="del"&&(this.softDelete?this.db.prepare(`
UPDATE kv_store SET is_deleted = 1 WHERE key = ? AND is_deleted = 0
`).run(i.key):this.db.prepare("DELETE FROM kv_store WHERE key = ?").run(i.key),this.emitter.emit("delete",i.key));this.db.exec("COMMIT"),t()}catch(r){this.db.exec("ROLLBACK"),s(r)}}catch(r){s(r)}})}async getIncludingDeleted(e){return new Promise((t,s)=>{try{if(!this.isInitialized){t(null);return}let i=this.db.prepare(`
SELECT key, value, created_at, expires_at, is_deleted, version
FROM kv_store WHERE key = ?
`).get(e);t(i?{key:i.key,value:i.is_deleted?null:this.binaryToValue(i.value),createdAt:i.created_at,expiresAt:i.expires_at||void 0,isDeleted:!!i.is_deleted,version:i.version}:null)}catch(r){s(r)}})}async getAllKeysIncludingDeleted(){return new Promise((e,t)=>{try{if(!this.isInitialized){e([]);return}let r=this.db.prepare("SELECT key FROM kv_store").all();e(r.map(i=>i.key))}catch(s){t(s)}})}async clearSoftDeletedEntries(){return new Promise((e,t)=>{try{if(!this.isInitialized){e(0);return}let s=this.db.prepare("SELECT COUNT(*) as count FROM kv_store WHERE is_deleted = 1"),i=s.get()?.count||0;this.db.prepare("DELETE FROM kv_store WHERE is_deleted = 1").run();let n=s.get()?.count||0;e(i-n)}catch(s){t(s)}})}async clearExpiredEntries(){return new Promise((e,t)=>{try{if(!this.isInitialized){e(0);return}let s=Date.now(),i=this.db.prepare(`
SELECT key FROM kv_store
WHERE expires_at IS NOT NULL AND expires_at <= ?
`).all(s),o=this.db.prepare(`
DELETE FROM kv_store
WHERE expires_at IS NOT NULL AND expires_at <= ?
`).run(s);for(let n of i)this.emitter.emit("expire",n.key);e(Number(o.changes)||0)}catch(s){t(s)}})}async evictLRUEntries(e){return new Promise(async(t,s)=>{try{if(!this.isInitialized){t(0);return}let r=await this.getSize("active"),i=e||this.maxEntries;if(r<=i){t(0);return}let a=r-i,n=this.db.prepare(`
SELECT key FROM kv_store
WHERE is_deleted = 0
ORDER BY last_accessed ASC
LIMIT ?
`).all(a),c=this.db.prepare("DELETE FROM kv_store WHERE key = ?"),l=0;for(let T of n){let p=T.key;c.run(p),this.emitter.emit("evict",p),l++}t(l)}catch(r){s(r)}})}async evictExpiredEntries(){return new Promise(async(e,t)=>{try{let s=await this.clearExpiredEntries(),r=await this.evictLRUEntries();e(s+r)}catch(s){t(s)}})}getIndexFilePath(){return this.dbFilePath+".index"}getDataFilePath(){return this.dbFilePath}async getAllEntries(e={}){return new Promise((t,s)=>{try{if(!this.isInitialized){t([]);return}let r=Date.now(),i=`
SELECT key, value, created_at, expires_at, is_deleted, version
FROM kv_store
WHERE is_deleted = 0 AND (expires_at IS NULL OR expires_at > ?)
`,a=[r];e.prefix&&(i+=" AND key LIKE ?",a.push(e.prefix+"%")),e.suffix&&(i+=" AND key LIKE ?",a.push("%"+e.suffix)),e.limit&&(i+=" LIMIT ?",a.push(e.limit));let n=this.db.prepare(i).all(...a);t(n.map(c=>({key:c.key,value:this.binaryToValue(c.value),createdAt:c.created_at,expiresAt:c.expires_at||void 0,isDeleted:!!c.is_deleted,version:c.version})))}catch(r){s(r)}})}};function C(){return typeof Bun<"u"&&!!Bun?.version}var A=C()?v:x;var N=m(require("node:os"),1),L=m(require("node:crypto"),1);function U(d){return L.default.createHash("md5").update(d).digest("hex")}var h=class d{options;compactionTimer;evictionTimer;tombstoneCount=0;isCompacting=!1;isClosing=!1;emitter=new E;storageManager;dbName="store_storage";constructor(e){let t=N.default.tmpdir();this.options={compactionInterval:e?.compactionInterval??36e5,ttl:e?.ttl??0,softDelete:e?.softDelete??!0,storage:{type:e?.storage?.type??"persistence",maxEntries:e?.storage?.maxEntries,maxMemory:e?.storage?.maxMemory,persistence:e?.storage?.persistence},logging:e?.logging??{enabled:!1},debug:{enabled:e?.debug?.enabled??!1,timing:e?.debug?.timing??!1,sqlLogging:e?.debug?.sqlLogging??!1}},this.options.storage?.persistence?.dbDir?t=this.options.storage.persistence.dbDir:this.dbName=U(process.cwd())+"_"+this.dbName,this.options.storage?.persistence?.dbFileName&&(this.dbName=this.options.storage.persistence.dbFileName),this.storageManager=new A(t,this.dbName,this.emitter,this.options.softDelete,this.options.storage)}static async create(e){let t=new d(e);return await t.initialize(),t}async initialize(){try{await this.storageManager.initialize(),this.emitter.emit("db:ready"),await this.storageManager.clearExpiredEntries(),this.options.compactionInterval>0&&(this.compactionTimer=setInterval(()=>this.compact(),this.options.compactionInterval)),this.evictionTimer=setInterval(()=>this.evictExpiredEntries(),3e4),this.emitter.emit("ready")}catch(e){throw this.emitter.emit("error",e),e}}on(e,t){return this.emitter.on(e,t),this}off(e,t){return this.emitter.off(e,t),this}addEventListener(e,t){return this.on(e,t)}removeEventListener(e,t){return this.off(e,t)}async has(e){return await this.storageManager.has(e)}async get(e){let t=await this.storageManager.get(e);return t?t.value:null}async set(e,t,s){await this.storageManager.set(e,t,s),await this.checkAndTriggerCompaction(),await this.checkAndTriggerEviction(),this.emitter.emit("set",e,t)}async delete(e){let t=await this.storageManager.delete(e);return t&&this.emitter.emit("delete",e),t}async getSize(e,t){return e===void 0?this.storageManager.getSize():typeof e=="string"?this.storageManager.getSize(e,t):this.storageManager.getSize(void 0,e)}async listKeys(e={}){return await this.storageManager.keys(e)}async forEach(e,t={}){let s=0,r=t.limit??1/0,i=await this.storageManager.keys();for(let a of i){if(s>=r)break;if(t.prefix&&!a.startsWith(t.prefix)||t.suffix&&!a.endsWith(t.suffix))continue;let o=await this.get(a);o!==null&&(e(a,o),s++)}}async list(e={}){let t=[],s=e.limit??100,i=((e.page??1)-1)*s,a=0,o=0,n=await this.storageManager.keys();for(let c of n){if(a>=s)break;if(e.prefix&&!c.startsWith(e.prefix)||e.suffix&&!c.endsWith(e.suffix))continue;if(o<i){o++;continue}let l=await this.get(c);l!==null&&(t.push({key:c,value:l}),a++)}return t}async clearSoftDeletedEntries(){return await this.storageManager.clearSoftDeletedEntries()}async clearExpiredEntries(){return await this.storageManager.clearExpiredEntries()}async compact(){if(!this.isCompacting){this.isCompacting=!0,this.emitter.emit("compact:start");try{this.options.softDelete&&await this.storageManager.clearSoftDeletedEntries(),this.tombstoneCount=0,this.emitter.emit("compact:end",{newSize:0,keysProcessed:0})}catch(e){this.emitter.emit("error",e)}finally{this.isCompacting=!1}}}async checkAndTriggerCompaction(){if(this.isCompacting||this.options.compactionInterval===0)return;let t=(await this.storageManager.keys()).length;((t>0?this.tombstoneCount/t:0)>.5||this.tombstoneCount>100)&&(this.tombstoneCount=0,setImmediate(()=>{this.isClosing||this.compact().catch(()=>{})}))}async batch(e){await this.storageManager.batch(e)}createTransaction(){let e=[],t=!1,s=!1,r=this.batch.bind(this);return{put(i,a,o){if(t||s)throw new Error("Transaction has already been committed or rolled back");e.push({type:"put",key:i,value:a,ttlSeconds:o})},del(i){if(t||s)throw new Error("Transaction has already been committed or rolled back");e.push({type:"del",key:i})},async commit(){if(t||s)throw new Error("Transaction has already been committed or rolled back");await r(e),t=!0},rollback(){if(t||s)throw new Error("Transaction has already been committed or rolled back");e.length=0,s=!0}}}async close(){this.isClosing=!0,this.compactionTimer&&clearInterval(this.compactionTimer),this.evictionTimer&&clearInterval(this.evictionTimer),await this.storageManager.close(),this.emitter.emit("close")}async checkAndTriggerEviction(){let e=await this.storageManager.getSize("active"),t=this.options.storage?.maxEntries;t&&e>t&&setImmediate(()=>{this.isClosing||this.evictExpiredEntries().catch(()=>{})})}async evictExpiredEntries(){try{let e=await this.storageManager.evictExpiredEntries();e>0&&this.emitter.emit("evict",e)}catch(e){this.emitter.emit("error",e)}}};f();0&&(module.exports={TypedEventEmitter,YqStore});
/**
* yq-store - A high-performance, zero-dependency, persistent Key-Value store for Node.js
*
* @author Olajide Mathew Ogundare <git@yuniq.solutions>
* @license MIT
*/