slimcryptdb
Version:
A minimalist, ultra-secure embedded database for Node.js applications
2 lines • 21.4 kB
JavaScript
const t=require("fs").promises,e=require("fs"),i=require("crypto"),a=require("events"),r=require("zlib"),s=require("util"),n=require("path"),o=s.promisify(r.gzip),c=s.promisify(r.gunzip);class SlimCryptDB{constructor(t,e=null,i={}){this.databaseDir=t,this.encryptionKey=e?Buffer.from(e):this._generateSecureKey(),this.options={encrypt:!0,compression:!0,walEnabled:!0,syncWrites:!0,maxWalSize:104857600,checkpointInterval:3e4,lockTimeout:1e4,walPaddingSize:1024,...i},this.eventEmitter=new a,this.indexes=new Map,this.locks=new Map,this.lockQueue=new Map,this.transactions=new Map,this.schemas=new Map,this.walSequence=0,this.walBuffer=[],this.isCheckpointing=!1,this.checkpointTimer=null,this.isClosed=!1,this.walSalt=null,this.walKey=null,this.walEncrypted=!1,this.initializationPromise=null,this.isInitialized=!1,this.initializationPromise=this._initializeDatabase()}async ensureInitialized(){this.isInitialized||await this.initializationPromise}async ready(){return await this.ensureInitialized(),this.isInitialized}async _initializeDatabase(){try{await t.mkdir(this.databaseDir,{recursive:!0}),await t.mkdir(n.join(this.databaseDir,"wal"),{recursive:!0}),await t.mkdir(n.join(this.databaseDir,"indexes"),{recursive:!0}),await this._initializeWALEncryption(),this.options.walEnabled&&await this._recoverFromWAL(),this.isInitialized=!0,this._startCheckpointScheduler()}catch(t){throw new Error(`Database initialization failed: ${t.message}`)}}async _initializeWALEncryption(){const e=n.join(this.databaseDir,"wal",".salt");try{const i=await t.readFile(e);this.walSalt=i}catch(a){if("ENOENT"!==a.code)throw a;this.walSalt=i.randomBytes(32),this.options.encrypt&&await t.writeFile(e,this.walSalt)}if(this.options.encrypt&&this.encryptionKey&&this.options.walEnabled?(this.walKey=this._deriveWALKey(),this.walEncrypted=!0):this.walEncrypted=!1,this.options.encrypt&&this.options.walEnabled&&!this.walEncrypted)throw new Error("WAL encryption failed to initialize properly")}_generateSecureKey(){return i.randomBytes(32)}_deriveWALKey(){if(!this.walSalt)throw new Error("WAL salt not initialized");if(!this.encryptionKey)throw new Error("Encryption key not provided");if(0===this.encryptionKey.reduce(((t,e)=>t+e),0))throw new Error("Encryption key has been wiped - cannot derive WAL key");try{return i.pbkdf2Sync(this.encryptionKey,this.walSalt,1e5,32,"sha256")}catch(t){throw new Error(`WAL key derivation failed: ${t.message}`)}}_encryptData(t){if(!this.options.encrypt)return JSON.stringify(t);try{const e=JSON.stringify(t),a=i.randomBytes(16),r=i.createCipheriv("aes-256-gcm",this.encryptionKey,a);let s=r.update(e,"utf8");s=Buffer.concat([s,r.final()]);const n=r.getAuthTag();if(16!==n.length)throw new Error(`Invalid authentication tag length: ${n.length}`);return a.toString("hex")+":"+n.toString("hex")+":"+s.toString("hex")}catch(t){throw new Error(`Encryption failed: ${t.message}`)}}_decryptData(t){if(!this.options.encrypt)try{return JSON.parse(t)}catch{throw new Error("Invalid unencrypted data format")}try{const e=t.split(":");if(3!==e.length)throw new Error("Invalid encrypted data format");const a=Buffer.from(e[0],"hex"),r=Buffer.from(e[1],"hex"),s=Buffer.from(e[2],"hex");if(16!==a.length)throw new Error(`Invalid IV length: expected 16, got ${a.length}`);if(16!==r.length)throw new Error(`Invalid authentication tag length: expected 16, got ${r.length}`);if(0===s.length)throw new Error("Empty ciphertext");const n=i.createDecipheriv("aes-256-gcm",this.encryptionKey,a);let o;n.setAuthTag(r);try{o=n.update(s),o=Buffer.concat([o,n.final()])}catch(t){if(t.message.includes("Unsupported state or unable to authenticate data")||t.message.includes("authentication")||t.message.includes("auth")||"ERR_CRYPTO_AUTH_FAILED"===t.code)throw new Error("Authentication failed: data has been tampered with");throw new Error(`Decryption failed: ${t.message}`)}const c=o.toString("utf8");try{return JSON.parse(c)}catch(t){throw new Error("Authentication failed: decrypted data is not valid JSON")}}catch(t){if(t.message.includes("Authentication failed"))throw t;throw new Error(`Decryption failed: ${t.message}`)}}_encryptWALData(t){if(!this.options.walEnabled||!this.options.encrypt)return JSON.stringify(t);if(!this.walEncrypted||!this.walKey||!this.walSalt)throw new Error("WAL encryption not properly initialized - cannot write unencrypted data");try{const e=JSON.stringify(t),a=this.walKey;if(!a)throw new Error("WAL key not available during encryption");const r=i.randomBytes(16),s=Buffer.from(e,"utf8"),n=this._applyWALPaddingBuffer(s),o=i.createCipheriv("aes-256-gcm",a,r);let c=o.update(n);c=Buffer.concat([c,o.final()]);const h=o.getAuthTag();return"WAL:"+r.toString("hex")+":"+h.toString("hex")+":"+c.toString("hex")}catch(t){throw new Error(`WAL encryption failed: ${t.message}`)}}_decryptWALData(t){if(!this.options.encrypt)try{return JSON.parse(t)}catch{throw new Error("Invalid unencrypted WAL data format")}try{if(!t.startsWith("WAL:"))try{return JSON.parse(t)}catch{throw new Error("Invalid WAL data format")}if(!this.walEncrypted||!this.walKey)throw new Error("Cannot decrypt WAL data: encryption not initialized");const e=t.substring(4).split(":");if(3!==e.length)throw new Error("Invalid encrypted WAL data format");const a=Buffer.from(e[0],"hex"),r=Buffer.from(e[1],"hex"),s=Buffer.from(e[2],"hex"),n=this.walKey;if(!n)throw new Error("WAL key not available during decryption");const o=i.createDecipheriv("aes-256-gcm",n,a);o.setAuthTag(r);let c=o.update(s);c=Buffer.concat([c,o.final()]);const h=this._removeWALPaddingBuffer(c).toString("utf8");return JSON.parse(h)}catch(t){throw new Error(`WAL decryption failed: ${t.message}`)}}_applyWALPaddingBuffer(t){const e=this.options.walPaddingSize,a=t.length;let r;r=a>=e-4?Math.ceil((a+4)/e)*e:e;const s=r-a-4,n=i.randomBytes(s),o=Buffer.alloc(4);return o.writeUInt32BE(a,0),Buffer.concat([t,n,o])}_removeWALPaddingBuffer(t){if(t.length<4)throw new Error("Invalid padded buffer: too short");const e=t.readUInt32BE(t.length-4);if(e<0||e>t.length-4)throw new Error(`Invalid original length: ${e}`);return t.slice(0,e)}async _compressData(t){if(!this.options.compression)return t;try{return"string"==typeof t?await o(Buffer.from(t,"utf8")):Buffer.isBuffer(t)?await o(t):t}catch(e){return e.message,t}}async _decompressData(t){if(!this.options.compression)return t;try{return Buffer.isBuffer(t)?(await c(t)).toString("utf8"):t}catch(e){return Buffer.isBuffer(t)?t.toString("utf8"):t}}async _writeWAL(t){if(!this.options.walEnabled||this.isClosed)return;await this.ensureInitialized();const e={sequence:++this.walSequence,timestamp:Date.now(),operation:t,checksum:this._calculateChecksum(t)};this.walBuffer.push(e),this.options.syncWrites&&await this._flushWAL(),1e3*this.walBuffer.length>this.options.maxWalSize&&setImmediate((()=>this._checkpoint()))}async _flushWAL(){if(0===this.walBuffer.length||this.isClosed)return;const e=n.join(this.databaseDir,"wal",`wal-${Date.now()}.log`),i=this.walBuffer.map((t=>this._encryptWALData(t))).join("\n")+"\n";try{await t.mkdir(n.dirname(e),{recursive:!0})}catch(t){}await t.writeFile(e,i,{flag:"a"}),this.walBuffer=[]}async _recoverFromWAL(){if(this.options.encrypt&&this.options.walEnabled&&(!this.walEncrypted||!this.walKey))throw new Error("WAL encryption not properly initialized for recovery");const e=n.join(this.databaseDir,"wal"),i=[];try{await t.access(e)}catch(t){return}const a=(await t.readdir(e)).filter((t=>t.endsWith(".log"))).sort();for(const r of a){if(".salt"===r)continue;const a=n.join(e,r);try{const e=(await t.readFile(a,"utf8")).trim().split("\n").filter(Boolean);for(const t of e)try{const e=this._decryptWALData(t);await this._applyWALEntry(e)}catch(e){e.message,i.push({file:r,entry:t.slice(0,80)+(t.length>80?"...":""),error:e.message})}}catch(t){t.message,i.push({file:r,entry:null,error:t.message})}}i.length>0?(i.length,this.lastWALRecoveryFailures=i):this.lastWALRecoveryFailures=[]}getWALRecoverySummary(){return{failures:this.lastWALRecoveryFailures||[],successCount:void 0===this.lastWALRecoveryFailures?null:this.lastWALRecoveryFailures.length||0}}async _applyWALEntry(t){const{operation:e}=t;if(this._calculateChecksum(e)!==t.checksum)throw new Error("WAL entry checksum mismatch");switch(e.type){case"write":await this._writeDataDirect(e.tableName,e.data);break;case"create_table":await this._createTableDirect(e.tableName,e.schema);break;case"delete_table":await this._deleteTableDirect(e.tableName)}}_calculateChecksum(t){return i.createHash("sha256").update(JSON.stringify(t)).digest("hex")}async _checkpoint(){if(!this.isCheckpointing&&!this.isClosed){this.isCheckpointing=!0;try{await this._flushWAL();const e=n.join(this.databaseDir,"wal"),i=await t.readdir(e),a=Date.now()-864e5;for(const r of i){if(".salt"===r)continue;const i=n.join(e,r);try{(await t.stat(i)).mtime.getTime()<a&&await t.unlink(i)}catch(t){}}}finally{this.isCheckpointing=!1}}}_startCheckpointScheduler(){this.isClosed||(this.checkpointTimer=setInterval((()=>{this.isClosed||this._checkpoint().catch(console.error)}),this.options.checkpointInterval))}_validateSchema(t,e){const i=this.schemas.get(t);return!i||this._validateDataAgainstSchema(e,i)}_normalizeSchema(t,e={}){if(!t)return null;if(t.type&&t.properties)return JSON.parse(JSON.stringify(t));const i={type:"object",properties:JSON.parse(JSON.stringify(t))},a=Array.isArray(e.required)?e.required:Array.isArray(t.required)?t.required:[];return a.length&&(i.required=[...a]),i}_validateDataAgainstSchema(t,e,i=""){if(e.type&&(Array.isArray(t)?"array":typeof t)!==e.type)throw new Error(`Invalid data format: ${i||"data"}`);const a=i||"data";if("number"===e.type&&"number"==typeof t){if("number"==typeof e.minimum&&t<e.minimum)throw new Error(`Invalid data format: ${a}`);if("number"==typeof e.maximum&&t>e.maximum)throw new Error(`Invalid data format: ${a}`)}if("string"===e.type&&"string"==typeof t){if("number"==typeof e.minLength&&t.length<e.minLength)throw new Error(`Invalid data format: ${a}`);if("number"==typeof e.maxLength&&t.length>e.maxLength)throw new Error(`Invalid data format: ${a}`);if(e.pattern&&!new RegExp(e.pattern).test(t))throw new Error(`Invalid data format: ${a}`)}if(Array.isArray(e.enum)&&!e.enum.includes(t))throw new Error(`Invalid data format: ${a}`);if(e.properties&&t&&"object"==typeof t&&!Array.isArray(t))for(const[a,r]of Object.entries(e.properties)){if(e.required&&e.required.includes(a)&&!(a in t))throw new Error(`Missing required property: ${a}`);a in t&&this._validateDataAgainstSchema(t[a],r,i?`${i}.${a}`:a)}return!0}async createTable(t,e=null,i={}){if(this.tableExists(t))throw new Error(`Table ${t} already exists`);const a=e?this._normalizeSchema(e,i):null,r={name:t,schema:a,rows:[],created:Date.now(),version:1};return a&&this.schemas.set(t,a),await this._writeWAL({type:"create_table",tableName:t,schema:a}),await this._createTableDirect(t,r),this.eventEmitter.emit("createTable",t,r),a&&a.properties&&a.properties.id&&await this.createIndex(t,"id_idx",["id"]),t}async _createTableDirect(e,i){const a=n.join(this.databaseDir,`${e}.db`);let r=this._encryptData(i);this.options.compression&&(r=await this._compressData(r)),await t.writeFile(a,r)}async deleteTable(t){if(!this.tableExists(t))throw new Error(`Table ${t} does not exist`);await this._writeWAL({type:"delete_table",tableName:t}),await this._deleteTableDirect(t),this.schemas.delete(t);for(const[e,i]of this.indexes)i.tableName===t&&this.indexes.delete(e);this.eventEmitter.emit("deleteTable",t)}async _deleteTableDirect(e){const i=n.join(this.databaseDir,`${e}.db`);try{await t.unlink(i)}catch(t){if("ENOENT"!==t.code)throw t}}async createIndex(t,e,i,a={}){const r=a.type||"btree",s=a.unique||!1,n=await this.readData(t,{}),o=new Map;for(const t of n){const e=this._buildIndexKey(t,i);if(s&&o.has(e))throw new Error(`Duplicate key violation for unique index: ${e}`);o.has(e)||o.set(e,[]),o.get(e).push(t.id||t)}this.indexes.set(e,{tableName:t,columns:i,type:r,unique:s,data:o}),await this._saveIndex(e),this.eventEmitter.emit("createIndex",t,e)}async dropIndex(e){if(!this.indexes.has(e))throw new Error(`Index ${e} does not exist`);this.indexes.delete(e);const i=n.join(this.databaseDir,"indexes",`${e}.idx`);try{await t.unlink(i)}catch(t){if("ENOENT"!==t.code)throw t}}_buildIndexKey(t,e){return e.map((e=>t[e])).join("::")}async _saveIndex(e){const i=this.indexes.get(e);if(!i)return;const a=n.join(this.databaseDir,"indexes",`${e}.idx`),r={...i,data:Array.from(i.data.entries())};let s=this._encryptData(r);this.options.compression&&(s=await this._compressData(s)),await t.writeFile(a,s)}async startTransaction(t="READ_COMMITTED"){const e=i.randomBytes(16).toString("hex");return this.transactions.set(e,{id:e,operations:[],isolationLevel:t,startTime:Date.now(),locks:new Set,snapshot:new Map}),e}async commitTransaction(t){const e=this.transactions.get(t);if(!e)throw new Error(`Transaction ${t} not found`);try{for(const t of e.operations)await this._applyOperation(t);for(const i of e.locks)this._releaseLock(i,t);this.transactions.delete(t),this.eventEmitter.emit("commitTransaction",t)}catch(e){throw await this.rollbackTransaction(t),e}}async rollbackTransaction(t){const e=this.transactions.get(t);if(e){for(const i of e.locks)this._releaseLock(i,t);this.transactions.delete(t),this.eventEmitter.emit("rollbackTransaction",t)}}async addData(t,e,a=null){if(await this.ensureInitialized(),!a){a=await this.startTransaction();try{const i=await this.addData(t,e,a);return await this.commitTransaction(a),i}catch(t){throw await this.rollbackTransaction(a),t}}this._validateSchema(t,e),e.id||(e.id=i.randomBytes(16).toString("hex"));const r=this.transactions.get(a);return await this._acquireLock(t,a),r.operations.push({type:"add",tableName:t,data:{...e}}),await this._updateIndexesForAdd(t,e),this.eventEmitter.emit("add",t,e),e}async updateData(t,e,i,a=null){if(await this.ensureInitialized(),!a){a=await this.startTransaction();try{const r=await this.updateData(t,e,i,a);return await this.commitTransaction(a),r}catch(t){throw await this.rollbackTransaction(a),t}}this.schemas.has(t)&&this._validateSchema(t,i);const r=this.transactions.get(a);await this._acquireLock(t,a);const s=(await this.readData(t,{})).filter((t=>Object.entries(e).every((([e,i])=>i instanceof RegExp?i.test(t[e]):t[e]===i))));for(const e of s){const a={...e,...i};e.id&&(a.id=e.id),r.operations.push({type:"update",tableName:t,oldData:{...e},newData:a,id:e.id}),await this._updateIndexesForUpdate(t,e,a)}return this.eventEmitter.emit("update",t,s,i),s.length}async deleteData(t,e,i=null){if(await this.ensureInitialized(),!i){i=await this.startTransaction();try{const a=await this.deleteData(t,e,i);return await this.commitTransaction(i),a}catch(t){throw await this.rollbackTransaction(i),t}}const a=this.transactions.get(i);await this._acquireLock(t,i);const r=(await this.readData(t,{})).filter((t=>Object.entries(e).every((([e,i])=>i instanceof RegExp?i.test(t[e]):t[e]===i))));for(const e of r)a.operations.push({type:"delete",tableName:t,data:{...e},id:e.id}),await this._updateIndexesForDelete(t,e);return this.eventEmitter.emit("delete",t,r),r.length}async _updateIndexesForAdd(t,e){for(const[i,a]of this.indexes)if(a.tableName===t){const t=this._buildIndexKey(e,a.columns);if(a.unique&&a.data.has(t))throw new Error(`Unique constraint violation for index ${i}`);a.data.has(t)||a.data.set(t,[]),a.data.get(t).push(e.id)}}async _updateIndexesForUpdate(t,e,i){for(const[a,r]of this.indexes)if(r.tableName===t){const t=this._buildIndexKey(e,r.columns),s=this._buildIndexKey(i,r.columns);if(t!==s){if(r.unique&&r.data.has(s)&&!r.data.get(s).includes(e.id))throw new Error(`Unique constraint violation for index ${a}`);if(r.data.has(t)){const i=r.data.get(t),a=i.indexOf(e.id);-1!==a&&(i.splice(a,1),0===i.length&&r.data.delete(t))}r.data.has(s)||r.data.set(s,[]),r.data.get(s).push(i.id)}}}async _updateIndexesForDelete(t,e){for(const[i,a]of this.indexes)if(a.tableName===t){const t=this._buildIndexKey(e,a.columns);if(a.data.has(t)){const i=a.data.get(t),r=i.indexOf(e.id);-1!==r&&(i.splice(r,1),0===i.length&&a.data.delete(t))}}}async queryData(t,e={}){const{filter:i,sort:a,limit:r,offset:s,join:n}=e;let o=await this._getDataWithIndex(t,i);if(o||(o=await this.readData(t,{})),i&&(o=this._applyFilter(o,i)),n&&(o=await this._applyJoin(o,n)),a&&(o=this._applySort(o,a)),r||s){const t=s||0,e=r?t+r:void 0;o=o.slice(t,e)}return o}tableExists(t){const i=n.join(this.databaseDir,`${t}.db`);return e.existsSync(i)}async _getDataWithIndex(t,e){if(!e||!e.conditions)return null;for(const[i,a]of this.indexes)if(a.tableName===t){const i=e.conditions.find((t=>a.columns.includes(t.column)&&"=="===t.operator));if(i){const e=i.value,r=a.data.get(e)||[];return(await this.readData(t,{})).filter((t=>r.includes(t.id)))}}return null}_applyFilter(t,e){const{operator:i,conditions:a}=e;return t.filter((t=>"and"===i?a.every((e=>this._evaluateCondition(t,e))):"or"!==i||a.some((e=>this._evaluateCondition(t,e)))))}_evaluateCondition(t,e){const{column:i,operator:a,value:r}=e,s=t[i];switch(a){case"==":return s===r;case"!=":return s!==r;case">":return s>r;case">=":return s>=r;case"<":return s<r;case"<=":return s<=r;case"in":return Array.isArray(r)&&r.includes(s);case"like":return new RegExp(r,"i").test(s);case"contains":return new RegExp(r).test(s);default:return!1}}_applySort(t,e){const{column:i,direction:a="asc"}=e;return[...t].sort(((t,e)=>{const r=t[i],s=e[i];return r<s?"asc"===a?-1:1:r>s?"asc"===a?1:-1:0}))}async readData(e,i={}){const a=n.join(this.databaseDir,`${e}.db`);try{let e=await t.readFile(a);this.options.compression&&(e=await this._decompressData(e));return(this._decryptData(e).rows||[]).filter((t=>!Object.keys(i).length||Object.entries(i).every((([e,i])=>i instanceof RegExp?i.test(t[e]):t[e]===i))))}catch(t){if("ENOENT"===t.code)throw new Error(`Table ${e} does not exist`);throw t}}async _writeDataDirect(e,i){const a=n.join(this.databaseDir,`${e}.db`),r={name:e,rows:i,lastModified:Date.now()};let s=this._encryptData(r);this.options.compression&&(s=await this._compressData(s)),await t.writeFile(a,s)}async _acquireLock(t,e,i=null){const a=i||this.options.lockTimeout,r=Date.now();return new Promise(((i,s)=>{(()=>{const n=this.locks.get(t);if(!n||n===e){this.locks.set(t,e);const a=this.transactions.get(e);return a&&a.locks.add(t),void i()}Date.now()-r>=a?s(new Error(`Lock timeout for table ${t}`)):(this.lockQueue.has(t)||this.lockQueue.set(t,[]),this.lockQueue.get(t).push({transactionId:e,resolve:i,reject:s,startTime:r,timeout:a}))})()}))}_releaseLock(t,e){if(this.locks.get(t)===e){this.locks.delete(t);const e=this.lockQueue.get(t);if(e&&e.length>0){const i=e.shift();if(Date.now()-i.startTime<i.timeout){this.locks.set(t,i.transactionId);const e=this.transactions.get(i.transactionId);e&&e.locks.add(t),i.resolve()}else i.reject(new Error(`Lock timeout for table ${t}`));0===e.length&&this.lockQueue.delete(t)}}}async _applyOperation(t){switch(t.type){case"add":await this._applyAddOperation(t);break;case"update":await this._applyUpdateOperation(t);break;case"delete":await this._applyDeleteOperation(t);break;default:throw new Error(`Unknown operation type: ${t.type}`)}t.data&&Buffer.isBuffer(t.data)&&t.data.fill(0)}async _applyAddOperation(t){const{tableName:e,data:i}=t,a=await this.readData(e,{});if(a.some((t=>t.id===i.id)))throw new Error(`Duplicate ID: ${i.id}`);a.push(i),await this._writeWAL({type:"write",tableName:e,data:a}),await this._writeDataDirect(e,a)}async _applyUpdateOperation(t){const{tableName:e,oldData:i,newData:a,id:r}=t,s=await this.readData(e,{}),n=s.findIndex((t=>t.id===r));if(-1===n)throw new Error(`Record with ID ${r} not found`);s[n]=a,await this._writeWAL({type:"write",tableName:e,data:s}),await this._writeDataDirect(e,s)}async _applyDeleteOperation(t){const{tableName:e,data:i,id:a}=t,r=await this.readData(e,{}),s=r.findIndex((t=>t.id===a));if(-1===s)throw new Error(`Record with ID ${a} not found`);r.splice(s,1),await this._writeWAL({type:"write",tableName:e,data:r}),await this._writeDataDirect(e,r)}async getStats(){return{tables:await this._getTableCount(),indexes:this.indexes.size,activeTransactions:this.transactions.size,walSequence:this.walSequence,memoryUsage:process.memoryUsage(),uptime:process.uptime(),locks:this.locks.size,lockQueue:Array.from(this.lockQueue.values()).reduce(((t,e)=>t+e.length),0)}}async _getTableCount(){try{return(await t.readdir(this.databaseDir)).filter((t=>t.endsWith(".db"))).length}catch{return 0}}async close(){this.isClosed||(this.isClosed=!0,this.checkpointTimer&&(clearInterval(this.checkpointTimer),this.checkpointTimer=null),await this._flushWAL(),await this._checkpoint(),this.lockQueue.clear(),this.locks.clear(),this.transactions.clear(),this.encryptionKey&&Buffer.isBuffer(this.encryptionKey)&&this.encryptionKey.fill(0),this.walKey&&Buffer.isBuffer(this.walKey)&&this.walKey.fill(0),this.walSalt&&Buffer.isBuffer(this.walSalt)&&this.walSalt.fill(0),this.encryptionKey=null,this.walKey=null,this.walSalt=null,this.eventEmitter.removeAllListeners())}on(t,e){this.eventEmitter.on(t,e)}removeListener(t,e){this.eventEmitter.removeListener(t,e)}}function h(){return i.randomBytes(32)}module.exports={SlimCryptDB,generateEncryptionKey:h,createSecureDatabase:function(t,e=null,i={}){return new SlimCryptDB(t,e||h(),{encrypt:!0,compression:!0,walEnabled:!0,syncWrites:!0,...i})}};
//# sourceMappingURL=SlimCryptDB.min.js.map