Last active
April 1, 2025 17:39
-
-
Save mikaelvesavuori/361b64a3edec772d614ce85d1096d1bc to your computer and use it in GitHub Desktop.
MikroDB
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env node | |
// MikroDB - See LICENSE file for copyright and license details. | |
var W=class extends Error{constructor(i){super(i),this.name="ValidationError",this.message=i||"Validation did not pass",this.cause={statusCode:400}}};import{existsSync as ie,readFileSync as ne}from"node:fs";var x=class{config={};options=[];validators=[];autoValidate=!0;constructor(i){let e=i?.configFilePath,t=i?.args||[],r=i?.config||{};this.options=i?.options||[],this.validators=i?.validators||[],i?.autoValidate!==void 0&&(this.autoValidate=i.autoValidate),this.config=this.createConfig(e,t,r)}deepMerge(i,e){let t={...i};for(let r in e)e[r]!==void 0&&(e[r]!==null&&typeof e[r]=="object"&&!Array.isArray(e[r])&&r in i&&i[r]!==null&&typeof i[r]=="object"&&!Array.isArray(i[r])?t[r]=this.deepMerge(i[r],e[r]):e[r]!==void 0&&(t[r]=e[r]));return t}setValueAtPath(i,e,t){let r=e.split("."),s=i;for(let o=0;o<r.length-1;o++){let a=r[o];!(a in s)||s[a]===null?s[a]={}:typeof s[a]!="object"&&(s[a]={}),s=s[a]}let n=r[r.length-1];s[n]=t}getValueAtPath(i,e){let t=e.split("."),r=i;for(let s of t){if(r==null)return;r=r[s]}return r}createConfig(i,e=[],t={}){let r={};for(let a of this.options)a.defaultValue!==void 0&&this.setValueAtPath(r,a.path,a.defaultValue);let s={};if(i&&ie(i))try{let a=ne(i,"utf8");s=JSON.parse(a),console.log(`Loaded configuration from ${i}`)}catch(a){console.error(`Error reading config file: ${a instanceof Error?a.message:String(a)}`)}let n=this.parseCliArgs(e),o=this.deepMerge({},r);return o=this.deepMerge(o,s),o=this.deepMerge(o,t),o=this.deepMerge(o,n),o}parseCliArgs(i){let e={},t=i[0]?.endsWith("node")||i[0]?.endsWith("node.exe")?2:0;for(;t<i.length;){let r=i[t++],s=this.options.find(n=>n.flag===r);if(s)if(s.isFlag)this.setValueAtPath(e,s.path,!0);else if(t<i.length&&!i[t].startsWith("-")){let n=i[t++];if(s.parser)try{n=s.parser(n)}catch(o){console.error(`Error parsing value for ${s.flag}: ${o instanceof Error?o.message:String(o)}`);continue}if(s.validator){let o=s.validator(n);if(o!==!0&&typeof o=="string"){console.error(`Invalid value for ${s.flag}: ${o}`);continue}if(o===!1){console.error(`Invalid value for ${s.flag}`);continue}}this.setValueAtPath(e,s.path,n)}else console.error(`Missing value for option ${r}`)}return e}validate(){for(let i of this.validators){let e=this.getValueAtPath(this.config,i.path),t=i.validator(e,this.config);if(t===!1)throw new W(i.message);if(typeof t=="string")throw new W(t)}}get(){return this.autoValidate&&this.validate(),this.config}getValue(i,e){let t=this.getValueAtPath(this.config,i);return t!==void 0?t:e}setValue(i,e){if(typeof e=="object"&&e!==null&&!Array.isArray(e)){let t=this.getValueAtPath(this.config,i)||{};if(typeof t=="object"&&!Array.isArray(t)){let r=this.deepMerge(t,e);this.setValueAtPath(this.config,i,r);return}}this.setValueAtPath(this.config,i,e)}getHelpText(){let i=`Available configuration options: | |
`;for(let e of this.options)i+=`${e.flag}${e.isFlag?"":" <value>"} | |
`,e.description&&(i+=` ${e.description} | |
`),e.defaultValue!==void 0&&(i+=` Default: ${JSON.stringify(e.defaultValue)} | |
`),i+=` | |
`;return i}};var V={int:i=>{let e=i.trim();if(!/^[+-]?\d+$/.test(e))throw new Error(`Cannot parse "${i}" as an integer`);let t=Number.parseInt(e,10);if(Number.isNaN(t))throw new Error(`Cannot parse "${i}" as an integer`);return t},float:i=>{let e=i.trim();if(!/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/.test(e)){if(e==="Infinity"||e==="-Infinity")return e==="Infinity"?Number.POSITIVE_INFINITY:Number.NEGATIVE_INFINITY;throw new Error(`Cannot parse "${i}" as a number`)}let t=Number.parseFloat(e);if(Number.isNaN(t))throw new Error(`Cannot parse "${i}" as a number`);return t},boolean:i=>{let e=i.trim().toLowerCase();if(["true","yes","1","y"].includes(e))return!0;if(["false","no","0","n"].includes(e))return!1;throw new Error(`Cannot parse "${i}" as a boolean`)},array:i=>i.split(",").map(e=>e.trim()),json:i=>{try{return JSON.parse(i)}catch{throw new Error(`Cannot parse "${i}" as JSON`)}}};var U=class{requests=new Map;limit;windowMs;constructor(i=100,e=60){this.limit=i,this.windowMs=e*1e3,setInterval(()=>this.cleanup(),this.windowMs)}getLimit(){return this.limit}isAllowed(i){let e=Date.now(),t=i||"unknown",r=this.requests.get(t);return(!r||r.resetTime<e)&&(r={count:0,resetTime:e+this.windowMs},this.requests.set(t,r)),r.count++,r.count<=this.limit}getRemainingRequests(i){let e=Date.now(),t=i||"unknown",r=this.requests.get(t);return!r||r.resetTime<e?this.limit:Math.max(0,this.limit-r.count)}getResetTime(i){let e=Date.now(),t=i||"unknown",r=this.requests.get(t);return!r||r.resetTime<e?Math.floor((e+this.windowMs)/1e3):Math.floor(r.resetTime/1e3)}cleanup(){let i=Date.now();for(let[e,t]of this.requests.entries())t.resetTime<i&&this.requests.delete(e)}};import{URL as oe}from"node:url";var _=class{routes=[];globalMiddlewares=[];pathPatterns=new Map;use(i){return this.globalMiddlewares.push(i),this}register(i,e,t,r=[]){return this.routes.push({method:i,path:e,handler:t,middlewares:r}),this.pathPatterns.set(e,this.createPathPattern(e)),this}get(i,...e){let t=e.pop();return this.register("GET",i,t,e)}post(i,...e){let t=e.pop();return this.register("POST",i,t,e)}put(i,...e){let t=e.pop();return this.register("PUT",i,t,e)}delete(i,...e){let t=e.pop();return this.register("DELETE",i,t,e)}patch(i,...e){let t=e.pop();return this.register("PATCH",i,t,e)}options(i,...e){let t=e.pop();return this.register("OPTIONS",i,t,e)}match(i,e){for(let t of this.routes){if(t.method!==i)continue;let r=this.pathPatterns.get(t.path);if(!r)continue;let s=r.pattern.exec(e);if(!s)continue;let n={};return r.paramNames.forEach((o,a)=>{n[o]=s[a+1]||""}),{route:t,params:n}}return null}createPathPattern(i){let e=[],t=i.replace(/\/:[^/]+/g,r=>{let s=r.slice(2);return e.push(s),"/([^/]+)"}).replace(/\/$/,"/?");return{pattern:new RegExp(`^${t}$`),paramNames:e}}async handle(i,e){let t=i.method||"GET",r=new oe(i.url||"/",`http://${i.headers.host}`),s=r.pathname,n=this.match(t,s);if(!n)return null;let{route:o,params:a}=n,l={};r.searchParams.forEach((h,u)=>{l[u]=h});let c={req:i,res:e,params:a,query:l,body:i.body||{},headers:i.headers,path:s,state:{},raw:()=>e,binary:(h,u="application/octet-stream",d=200)=>({statusCode:d,body:h,headers:{"Content-Type":u,"Content-Length":h.length.toString()},isRaw:!0}),text:(h,u=200)=>({statusCode:u,body:h,headers:{"Content-Type":"text/plain"}}),form:(h,u=200)=>({statusCode:u,body:h,headers:{"Content-Type":"application/x-www-form-urlencoded"}}),json:(h,u=200)=>({statusCode:u,body:h,headers:{"Content-Type":"application/json"}}),html:(h,u=200)=>({statusCode:u,body:h,headers:{"Content-Type":"text/html"}}),redirect:(h,u=302)=>({statusCode:u,body:null,headers:{Location:h}}),status:function(h){return{raw:()=>e,binary:(u,d="application/octet-stream")=>({statusCode:h,body:u,headers:{"Content-Type":d,"Content-Length":u.length.toString()},isRaw:!0}),text:u=>({statusCode:h,body:u,headers:{"Content-Type":"text/plain"}}),json:u=>({statusCode:h,body:u,headers:{"Content-Type":"application/json"}}),html:u=>({statusCode:h,body:u,headers:{"Content-Type":"text/html"}}),form:u=>({statusCode:h,body:u,headers:{"Content-Type":"application/x-www-form-urlencoded"}}),redirect:(u,d=302)=>({statusCode:d,body:null,headers:{Location:u}}),status:u=>this.status(u)}}},f=[...this.globalMiddlewares,...o.middlewares];return this.executeMiddlewareChain(c,f,o.handler)}async executeMiddlewareChain(i,e,t){let r=0,s=async()=>{if(r<e.length){let n=e[r++];return n(i,s)}return t(i)};return s()}};var k=()=>({port:Number(process.env.PORT)||3e3,host:process.env.HOST||"0.0.0.0",useHttps:!1,useHttp2:!1,sslCert:"",sslKey:"",sslCa:"",debug:ae(process.env.DEBUG)||!1,rateLimit:{enabled:!0,requestsPerMinute:100},allowedDomains:["*"]});function ae(i){return i==="true"||i===!0}var m=k(),K=i=>({configFilePath:"mikroserve.config.json",args:process.argv,options:[{flag:"--port",path:"port",defaultValue:m.port},{flag:"--host",path:"host",defaultValue:m.host},{flag:"--https",path:"useHttps",defaultValue:m.useHttps,isFlag:!0},{flag:"--http2",path:"useHttp2",defaultValue:m.useHttp2,isFlag:!0},{flag:"--cert",path:"sslCert",defaultValue:m.sslCert},{flag:"--key",path:"sslKey",defaultValue:m.sslKey},{flag:"--ca",path:"sslCa",defaultValue:m.sslCa},{flag:"--ratelimit",path:"rateLimit.enabled",defaultValue:m.rateLimit.enabled,isFlag:!0},{flag:"--rps",path:"rateLimit.requestsPerMinute",defaultValue:m.rateLimit.requestsPerMinute},{flag:"--allowed",path:"allowedDomains",defaultValue:m.allowedDomains,parser:V.array},{flag:"--debug",path:"debug",defaultValue:m.debug,isFlag:!0}],config:i});import{readFileSync as b}from"node:fs";import $ from"node:http";import le from"node:http2";import ce from"node:https";var D=class{config;rateLimiter;router;constructor(i){let e=new x(K(i||{})).get();e.debug&&console.log("Using configuration:",e),this.config=e,this.router=new _;let t=e.rateLimit.requestsPerMinute||k().rateLimit.requestsPerMinute;this.rateLimiter=new U(t,60),e.rateLimit.enabled===!0&&this.use(this.rateLimitMiddleware.bind(this))}use(i){return this.router.use(i),this}get(i,...e){return this.router.get(i,...e),this}post(i,...e){return this.router.post(i,...e),this}put(i,...e){return this.router.put(i,...e),this}delete(i,...e){return this.router.delete(i,...e),this}patch(i,...e){return this.router.patch(i,...e),this}options(i,...e){return this.router.options(i,...e),this}start(){let i=this.createServer(),{port:e,host:t}=this.config;return this.setupGracefulShutdown(i),i.listen(e,t,()=>{let r=i.address(),s=this.config.useHttps||this.config.useHttp2?"https":"http";console.log(`MikroServe running at ${s}://${r.address!=="::"?r.address:"localhost"}:${r.port}`)}),i}createServer(){let i=this.requestHandler.bind(this);if(this.config.useHttp2){if(!this.config.sslCert||!this.config.sslKey)throw new Error("SSL certificate and key paths are required when useHttp2 is true");try{let e={key:b(this.config.sslKey),cert:b(this.config.sslCert),...this.config.sslCa?{ca:b(this.config.sslCa)}:{}};return le.createSecureServer(e,i)}catch(e){throw e.message.includes("key values mismatch")?new Error(`SSL certificate and key do not match: ${e.message}`):e}}else if(this.config.useHttps){if(!this.config.sslCert||!this.config.sslKey)throw new Error("SSL certificate and key paths are required when useHttps is true");try{let e={key:b(this.config.sslKey),cert:b(this.config.sslCert),...this.config.sslCa?{ca:b(this.config.sslCa)}:{}};return ce.createServer(e,i)}catch(e){throw e.message.includes("key values mismatch")?new Error(`SSL certificate and key do not match: ${e.message}`):e}}return $.createServer(i)}async rateLimitMiddleware(i,e){let t=i.req.socket.remoteAddress||"unknown";return i.res.setHeader("X-RateLimit-Limit",this.rateLimiter.getLimit().toString()),i.res.setHeader("X-RateLimit-Remaining",this.rateLimiter.getRemainingRequests(t).toString()),i.res.setHeader("X-RateLimit-Reset",this.rateLimiter.getResetTime(t).toString()),this.rateLimiter.isAllowed(t)?e():{statusCode:429,body:{error:"Too Many Requests",message:"Rate limit exceeded, please try again later"},headers:{"Content-Type":"application/json"}}}async requestHandler(i,e){let t=Date.now(),r=i.method||"UNKNOWN",s=i.url||"/unknown",n=this.config.debug;try{if(this.setCorsHeaders(e,i),this.setSecurityHeaders(e,this.config.useHttps),n&&console.log(`${r} ${s}`),i.method==="OPTIONS"){if(e instanceof $.ServerResponse)e.statusCode=204,e.end();else{let a=e;a.writeHead(204),a.end()}return}try{i.body=await this.parseBody(i)}catch(a){return n&&console.error("Body parsing error:",a.message),this.respond(e,{statusCode:400,body:{error:"Bad Request",message:a.message}})}let o=await this.router.handle(i,e);return o?o._handled?void 0:this.respond(e,o):this.respond(e,{statusCode:404,body:{error:"Not Found",message:"The requested endpoint does not exist"}})}catch(o){return console.error("Server error:",o),this.respond(e,{statusCode:500,body:{error:"Internal Server Error",message:n?o.message:"An unexpected error occurred"}})}finally{n&&this.logDuration(t,r,s)}}logDuration(i,e,t){let r=Date.now()-i;console.log(`${e} ${t} completed in ${r}ms`)}async parseBody(i){return new Promise((e,t)=>{let r=[],s=0,n=1024*1024,o=!1,a=this.config.debug,l=i.headers["content-type"]||"";a&&console.log("Content-Type:",l),i.on("data",c=>{if(s+=c.length,a&&console.log(`Received chunk: ${c.length} bytes, total size: ${s}`),s>n&&!o){o=!0,a&&console.log(`Body size exceeded limit: ${s} > ${n}`),t(new Error("Request body too large"));return}o||r.push(c)}),i.on("end",()=>{if(!o){a&&console.log(`Request body complete: ${s} bytes`);try{if(r.length>0){let c=Buffer.concat(r).toString("utf8");if(l.includes("application/json"))try{e(JSON.parse(c))}catch(f){t(new Error(`Invalid JSON in request body: ${f.message}`))}else if(l.includes("application/x-www-form-urlencoded")){let f={};new URLSearchParams(c).forEach((h,u)=>{f[u]=h}),e(f)}else e(c)}else e({})}catch(c){t(new Error(`Invalid request body: ${c}`))}}}),i.on("error",c=>{o||t(new Error(`Error reading request body: ${c.message}`))})})}setCorsHeaders(i,e){let t=e.headers.origin,{allowedDomains:r=["*"]}=this.config;!t||r.length===0||r.includes("*")?i.setHeader("Access-Control-Allow-Origin","*"):r.includes(t)&&(i.setHeader("Access-Control-Allow-Origin",t),i.setHeader("Vary","Origin")),i.setHeader("Access-Control-Allow-Methods","GET, POST, PUT, DELETE, PATCH, OPTIONS"),i.setHeader("Access-Control-Allow-Headers","Content-Type, Authorization"),i.setHeader("Access-Control-Max-Age","86400")}setSecurityHeaders(i,e=!1){let t={"X-Content-Type-Options":"nosniff","X-Frame-Options":"DENY","Content-Security-Policy":"default-src 'self'; script-src 'self'; object-src 'none'","X-XSS-Protection":"1; mode=block"};if((e||this.config.useHttp2)&&(t["Strict-Transport-Security"]="max-age=31536000; includeSubDomains"),i instanceof $.ServerResponse)Object.entries(t).forEach(([r,s])=>{i.setHeader(r,s)});else{let r=i;Object.entries(t).forEach(([s,n])=>{r.setHeader(s,n)})}}respond(i,e){let t={...e.headers||{}};(s=>typeof s.writeHead=="function"&&typeof s.end=="function")(i)?(i.writeHead(e.statusCode,t),e.body===null||e.body===void 0?i.end():e.isRaw||typeof e.body=="string"?i.end(e.body):i.end(JSON.stringify(e.body))):(console.warn("Unexpected response object type without writeHead/end methods"),i.writeHead?.(e.statusCode,t),e.body===null||e.body===void 0?i.end?.():e.isRaw||typeof e.body=="string"?i.end?.(e.body):i.end?.(JSON.stringify(e.body)))}setupGracefulShutdown(i){let e=t=>{console.log("Shutting down MikroServe server..."),t&&console.error("Error:",t),i.close(()=>{console.log("Server closed successfully"),setImmediate(()=>process.exit(t?1:0))})};process.on("SIGINT",()=>e()),process.on("SIGTERM",()=>e()),process.on("uncaughtException",e),process.on("unhandledRejection",e)}};import{join as Te}from"node:path";import{existsSync as M}from"node:fs";import{readFile as J,unlink as ue,writeFile as X}from"node:fs/promises";var p=()=>Date.now(),z=(i,e)=>e==="D"||i.length===0||i.join(" ")==="null"?null:G(i),G=i=>{try{return JSON.parse(i.join(" "))}catch{return}};function O(i){return i==="true"||i===!0}function q(i){let e=(o,a)=>{if(o){if(a==="json")return G(o)||o;if(a==="number")return Number.parseInt(o,10)}},t=e(i?.filter,"json"),r=e(i?.sort,"json"),s=e(i?.limit,"number"),n=e(i?.offset,"number");if(!(!t&&!r&&!s&&!n))return{filter:t,sort:r,limit:s,offset:n}}var w=class extends Error{constructor(e){super(),this.name="NotFoundError",this.message=e||"Resource not found",this.cause={statusCode:404}}},g=class extends Error{constructor(e){super(),this.name="CheckpointError",this.message=e,this.cause={statusCode:500}}};var A=class{checkpointInterval;defaultCheckpointIntervalMs=10*1e3;isCheckpointing=!1;lastCheckpointTime=0;walFile;checkpointTimer=null;wal;table;constructor(e){let{table:t,wal:r,walFile:s,checkpointIntervalMs:n}=e;this.defaultCheckpointIntervalMs=n||this.defaultCheckpointIntervalMs,this.table=t,this.wal=r,this.walFile=s,this.checkpointInterval=n,this.lastCheckpointTime=p()}async start(){let e=`${this.walFile}.checkpoint`;if(M(e)){console.log("Incomplete checkpoint detected, running recovery...");try{let t=await J(e,"utf8");console.log(`Incomplete checkpoint from: ${new Date(Number.parseInt(t))}`),await this.checkpoint(!0)}catch(t){throw new w(`Error reading checkpoint file: ${t}`)}}this.checkpointTimer=setInterval(async()=>{try{await this.checkpoint()}catch(t){throw new g(`Checkpoint interval failed: ${t}`)}},this.checkpointInterval)}stop(){this.checkpointTimer&&(clearInterval(this.checkpointTimer),this.checkpointTimer=null)}async checkpoint(e=!1){if(this.isCheckpointing)return;let t=p();if(!(!e&&t-this.lastCheckpointTime<this.checkpointInterval)){this.isCheckpointing=!0;try{await this.wal.flushWAL();let r=`${this.walFile}.checkpoint`;await X(r,t.toString(),"utf8");let s=await this.getTablesFromWAL();await this.persistTables(s);try{await X(this.walFile,"","utf8"),process.env.DEBUG==="true"&&console.log("WAL truncated successfully")}catch(n){throw new g(`Failed to truncate WAL: ${n}`)}M(r)&&await ue(r),this.wal.clearPositions(),this.lastCheckpointTime=t,process.env.DEBUG==="true"&&console.log("Checkpoint complete")}catch(r){throw new g(`Checkpoint failed: ${r}`)}finally{this.isCheckpointing=!1}}}async getTablesFromWAL(){let e=new Set;if(!M(this.walFile))return e;try{let t=await J(this.walFile,"utf8");if(!t.trim())return e;let r=t.trim().split(` | |
`);for(let s of r){if(!s.trim())continue;let n=s.split(" ");n.length>=3&&e.add(n[2])}}catch(t){throw new g(`Error reading WAL file: ${t}`)}return e}async persistTables(e){let t=Array.from(e).map(async r=>{try{await this.table.flushTableToDisk(r),console.log(`Checkpointed table "${r}"`)}catch(s){throw new g(`Failed to checkpoint table "${r}": ${s.message}`)}});await Promise.all(t)}};import{existsSync as ee,mkdirSync as we}from"node:fs";import{readFile as ve,writeFile as Ee}from"node:fs/promises";import{join as N}from"node:path";import{EventEmitter as he}from"node:events";var P=class{emitter;targets={};options;constructor(i){this.emitter=new he,i?.maxListeners===0?this.emitter.setMaxListeners(0):this.emitter.setMaxListeners(i?.maxListeners||10),this.options={errorHandler:i?.errorHandler||(e=>console.error(e))}}addTarget(i){return(Array.isArray(i)?i:[i]).map(r=>this.targets[r.name]?(console.error(`Target with name '${r.name}' already exists.`),!1):(this.targets[r.name]={name:r.name,url:r.url,headers:r.headers||{},events:r.events||[]},!0)).every(r=>r===!0)}updateTarget(i,e){if(!this.targets[i])return console.error(`Target with name '${i}' does not exist.`),!1;let t=this.targets[i];return e.url!==void 0&&(t.url=e.url),e.headers&&(t.headers={...t.headers,...e.headers}),e.events&&(t.events=e.events),!0}removeTarget(i){return this.targets[i]?(delete this.targets[i],!0):(console.error(`Target with name '${i}' does not exist.`),!1)}addEventToTarget(i,e){if(!this.targets[i])return console.error(`Target with name '${i}' does not exist.`),!1;let t=Array.isArray(e)?e:[e],r=this.targets[i];return t.forEach(s=>{r.events.includes(s)||r.events.push(s)}),!0}on(i,e){return this.emitter.on(i,e),this}off(i,e){return this.emitter.off(i,e),this}once(i,e){return this.emitter.once(i,e),this}async emit(i,e){let t={success:!0,errors:[]},r=(o,a,l)=>({target:o,event:a,error:l}),s=Object.values(this.targets).filter(o=>o.events.includes(i)||o.events.includes("*"));s.filter(o=>!o.url).forEach(o=>{try{this.emitter.emit(i,e)}catch(a){let l=a instanceof Error?a:new Error(String(a));t.errors.push({target:o.name,event:i,error:l}),this.options.errorHandler(l,i,e),t.success=!1}});let n=s.filter(o=>o.url);if(n.length>0){let o=n.map(async a=>{try{let l=await fetch(a.url,{method:"POST",headers:{"Content-Type":"application/json",...a.headers},body:JSON.stringify({eventName:i,data:e})});if(!l.ok){let c=`HTTP error! Status: ${l.status}: ${l.statusText}`,f=new Error(c);t.errors.push(r(a.name,i,f)),this.options.errorHandler(f,i,e),t.success=!1}}catch(l){let c=l instanceof Error?l:new Error(String(l));t.errors.push(r(a.name,i,c)),this.options.errorHandler(c,i,e),t.success=!1}});await Promise.allSettled(o)}return t}async handleIncomingEvent(i){try{let{eventName:e,data:t}=typeof i=="string"?JSON.parse(i):i;process.nextTick(()=>{try{this.emitter.emit(e,t)}catch(r){this.options.errorHandler(r instanceof Error?r:new Error(String(r)),e,t)}})}catch(e){throw this.options.errorHandler(e instanceof Error?e:new Error(String(e)),"parse_event"),e}}createMiddleware(){return async(i,e,t)=>{if(i.method!=="POST"){t&&t();return}if(i.body)try{await this.handleIncomingEvent(i.body),e.statusCode=202,e.end()}catch(r){e.statusCode=400,e.end(JSON.stringify({error:"Invalid event format"})),t&&t(r)}else{let r="";i.on("data",s=>r+=s.toString()),i.on("end",async()=>{try{await this.handleIncomingEvent(r),e.statusCode=202,e.end()}catch(s){e.statusCode=400,e.end(JSON.stringify({error:"Invalid event format"})),t&&t(s)}})}}}};var I=class{cacheLimit;tableAccessTimes=new Map;constructor(e={}){this.cacheLimit=e.cacheLimit??20}trackTableAccess(e){this.tableAccessTimes.set(e,p())}findTablesForEviction(e){if(e<=this.cacheLimit)return[];let t=e-this.cacheLimit,r=Array.from(this.tableAccessTimes.entries()).sort((s,n)=>s[1]-n[1]).slice(0,t).map(([s])=>s);for(let s of r)this.tableAccessTimes.delete(s);return r}removeTable(e){this.tableAccessTimes.delete(e)}findExpiredItems(e){let t=p(),r=[];for(let[s,n]of e.entries())n.x&&n.x<t&&r.push([s,n]);return r}clear(){this.tableAccessTimes.clear()}};import{createCipheriv as fe,createDecipheriv as pe,randomBytes as de,scryptSync as me}from"node:crypto";var v=class{algo="aes-256-gcm";KEY_LENGTH=32;IV_LENGTH=12;generateKey(e,t){return me(`${t}#${e}`,t,this.KEY_LENGTH)}generateIV(){return de(this.IV_LENGTH)}encrypt(e,t,r){if(t.length!==this.KEY_LENGTH)throw new Error(`Invalid key length: ${t.length} bytes. Expected: ${this.KEY_LENGTH} bytes`);if(r.length!==this.IV_LENGTH)throw new Error(`Invalid IV length: ${r.length} bytes. Expected: ${this.IV_LENGTH} bytes`);let s=fe(this.algo,t,r),n=Buffer.concat([s.update(e,"utf8"),s.final()]),o=s.getAuthTag();return{iv:r,encrypted:n,authTag:o}}decrypt(e,t){let{iv:r,encrypted:s,authTag:n}=e;if(t.length!==this.KEY_LENGTH)throw new Error(`Invalid key length: ${t.length} bytes. Expected: ${this.KEY_LENGTH} bytes`);if(r.length!==this.IV_LENGTH)throw new Error(`Invalid IV length: ${r.length} bytes. Expected: ${this.IV_LENGTH} bytes`);let o=pe(this.algo,t,r);return o.setAuthTag(n),Buffer.concat([o.update(s),o.final()]).toString("utf8")}serialize(e){return Buffer.concat([Buffer.from([1]),Buffer.from([e.iv.length]),e.iv,Buffer.from([e.authTag.length]),e.authTag,e.encrypted])}deserialize(e){let t=0,r=e[t++];if(r!==1)throw new Error(`Unsupported encryption format version: ${r}`);let s=e[t++],n=e.subarray(t,t+s);t+=s;let o=e[t++],a=e.subarray(t,t+o);t+=o;let l=e.subarray(t);return{iv:n,authTag:a,encrypted:l}}toHex(e){return e.toString("hex")}toUtf8(e){return e.toString("utf8")}toBuffer(e){return Buffer.from(e,"hex")}};var E=class{readTableFromBinaryBuffer(e){if(e.length<8||e[0]!==77||e[1]!==68||e[2]!==66||e[3]!==1)throw new Error("Invalid table file format");let t=e.readUInt32LE(4),r=new Map,s=8,n=p();for(let o=0;o<t&&s+26<=e.length;o++){let a=e.readUInt16LE(s);s+=2;let l=e.readUInt32LE(s);s+=4;let c=e.readUInt32LE(s);s+=4;let f=Number(e.readBigUInt64LE(s));s+=8;let h=Number(e.readBigUInt64LE(s));if(s+=8,s+a+l>e.length)break;if(h&&h<=n){s+=a+l;continue}let u=e.toString("utf8",s,s+a);s+=a;let d=e.slice(s,s+l);s+=l;let y=this.decodeValue(d);r.set(u,{value:y,version:c,timestamp:f,expiration:h||null})}return r}toBinaryBuffer(e){let t=[],r=Buffer.from([77,68,66,1]);t.push(r);let s=Array.from(e.entries()).filter(([o])=>typeof o=="string"),n=Buffer.alloc(4);n.writeUInt32LE(s.length,0),t.push(n);for(let[o,a]of s){if(o===null||typeof o!="string")continue;let l=Buffer.from(o),c=this.encodeValue(a.value),f=Buffer.alloc(2);f.writeUInt16LE(l.length,0),t.push(f);let h=Buffer.alloc(4);h.writeUInt32LE(c.length,0),t.push(h);let u=Buffer.alloc(4);u.writeUInt32LE(a.version||0,0),t.push(u);let d=Buffer.alloc(8);d.writeBigUInt64LE(BigInt(a.timestamp||0),0),t.push(d);let y=Buffer.alloc(8);y.writeBigUInt64LE(BigInt(a.expiration||0),0),t.push(y),t.push(l),t.push(c)}return Buffer.concat(t)}encodeValue(e){if(e==null)return Buffer.from([0]);if(typeof e=="boolean")return Buffer.from([1,e?1:0]);if(typeof e=="number"){if(Number.isInteger(e)&&e>=-2147483648&&e<=2147483647){let r=Buffer.alloc(5);return r[0]=2,r.writeInt32LE(e,1),r}let t=Buffer.alloc(9);return t[0]=3,t.writeDoubleLE(e,1),t}if(typeof e=="string"){let t=Buffer.from(e,"utf8"),r=Buffer.alloc(5+t.length);return r[0]=4,r.writeUInt32LE(t.length,1),t.copy(r,5),r}if(Array.isArray(e)){let t=[],r=Buffer.alloc(5);r[0]=5,r.writeUInt32LE(e.length,1),t.push(r);for(let s of e)t.push(this.encodeValue(s));return Buffer.concat(t)}if(typeof e=="object"){if(e instanceof Date){let n=Buffer.alloc(9);return n[0]=7,n.writeBigInt64LE(BigInt(e.getTime()),1),n}let t=Object.keys(e),r=[],s=Buffer.alloc(5);s[0]=6,s.writeUInt32LE(t.length,1),r.push(s);for(let n of t){let o=Buffer.from(n,"utf8"),a=Buffer.alloc(2);a.writeUInt16LE(o.length,0),r.push(a),r.push(o),r.push(this.encodeValue(e[n]))}return Buffer.concat(r)}return this.encodeValue(String(e))}decodeValue(e){if(e.length===0)return null;let t=e[0];switch(t){case 0:return null;case 1:return e[1]===1;case 2:return e.readInt32LE(1);case 3:return e.readDoubleLE(1);case 4:{let r=e.readUInt32LE(1);return e.toString("utf8",5,5+r)}case 5:{let r=e.readUInt32LE(1),s=new Array(r),n=5;for(let o=0;o<r;o++){let{value:a,bytesRead:l}=this.decodeValueWithSize(e,n);s[o]=a,n+=l}return s}case 6:{let r=e.readUInt32LE(1),s={},n=5;for(let o=0;o<r;o++){let a=e.readUInt16LE(n);n+=2;let l=e.toString("utf8",n,n+a);n+=a;let{value:c,bytesRead:f}=this.decodeValueWithSize(e,n);s[l]=c,n+=f}return s}case 7:return new Date(Number(e.readBigInt64LE(1)));default:return console.warn(`Unknown type byte: ${t}`),null}}decodeValueWithSize(e,t=0){if(t>=e.length)return{value:null,bytesRead:0};let r=e[t];switch(r){case 0:return{value:null,bytesRead:1};case 1:return{value:e[t+1]===1,bytesRead:2};case 2:return{value:e.readInt32LE(t+1),bytesRead:5};case 3:return{value:e.readDoubleLE(t+1),bytesRead:9};case 4:{let s=e.readUInt32LE(t+1);return{value:e.toString("utf8",t+5,t+5+s),bytesRead:5+s}}case 5:{let s=e.readUInt32LE(t+1),n=new Array(s),o=t+5;for(let a=0;a<s;a++){let l=this.decodeValueWithSize(e,o);n[a]=l.value,o+=l.bytesRead}return{value:n,bytesRead:o-t}}case 6:{let s=e.readUInt32LE(t+1),n={},o=t+5;for(let a=0;a<s;a++){let l=e.readUInt16LE(o);o+=2;let c=e.toString("utf8",o,o+l);o+=l;let f=this.decodeValueWithSize(e,o);n[c]=f.value,o+=f.bytesRead}return{value:n,bytesRead:o-t}}case 7:return{value:new Date(Number(e.readBigInt64LE(t+1))),bytesRead:9};default:return console.warn(`Unknown type byte: ${r} at offset ${t}`),{value:null,bytesRead:1}}}};var L=class{async query(e,t,r=50){let s=new Map;for(let[n,o]of e.entries())if((!t||(typeof t=="function"?t(o.value):this.evaluateFilter(o.value,t)))&&(s.set(n,o.value),r&&s.size>=r))break;return Array.from(s.values())}evaluateCondition(e,t){if(!t||typeof t!="object"||!t.operator)return e===t;let{operator:r,value:s}=t;switch(r){case"eq":return e===s;case"neq":return e!==s;case"gt":return e>s;case"gte":return e>=s;case"lt":return e<s;case"lte":return e<=s;case"in":return Array.isArray(s)&&s.includes(e);case"nin":return Array.isArray(s)&&!s.includes(e);case"like":return typeof e=="string"&&typeof s=="string"&&e.toLowerCase().includes(s.toLowerCase());case"between":return Array.isArray(s)&&s.length===2&&e>=s[0]&&e<=s[1];case"regex":try{let n=new RegExp(s);return typeof e=="string"&&n.test(e)}catch(n){return console.error("Invalid regex pattern:",n),!1}case"contains":return Array.isArray(e)&&e.includes(s);case"containsAll":return Array.isArray(e)&&Array.isArray(s)&&s.every(n=>e.includes(n));case"containsAny":return Array.isArray(e)&&Array.isArray(s)&&s.some(n=>e.includes(n));case"size":return Array.isArray(e)&&e.length===s;default:return!1}}evaluateFilter(e,t){if(e==null)return!1;if("$or"in t)return t.$or.some(r=>this.evaluateFilter(e,r));for(let[r,s]of Object.entries(t))if(!r.startsWith("$"))if(r.includes(".")){let n=r.split("."),o=e;for(let a of n)if(o=o?.[a],o==null)return!1;if(!this.evaluateCondition(o,s))return!1}else if(s&&typeof s=="object"&&!("operator"in s)){let n=e[r];if(n==null||!this.evaluateFilter(n,s))return!1}else{let n=e[r];if(!this.evaluateCondition(n,s))return!1}return!0}};import{existsSync as Y,statSync as H,writeFileSync as ge}from"node:fs";import{appendFile as ye,readFile as Q}from"node:fs/promises";var B=class{walFile;walInterval;walBuffer=[];maxWalBufferEntries=Number.parseInt(process.env.MAX_WAL_BUFFER_ENTRIES||"100");maxWalBufferSize=Number.parseInt(process.env.MAX_WAL_BUFFER_SIZE||(1024*1024*.01).toString());maxWalSizeBeforeCheckpoint=Number.parseInt(process.env.MAX_WAL_BUFFER_SIZE||(1024*1024*.01).toString());lastProcessedEntryCount=new Map;checkpointCallback=null;constructor(e,t){this.walFile=e,this.walInterval=t,this.start()}setCheckpointCallback(e){this.checkpointCallback=e}start(){Y(this.walFile)||ge(this.walFile,"","utf-8"),setInterval(async()=>{try{this.walBuffer.length>0&&await this.flushWAL()}catch(e){console.error("WAL flush interval failed:",e)}},this.walInterval)}checkWalFileExists(){if(!Y(this.walFile))throw new w(`WAL file "${this.walFile}" does not exist`)}async loadWAL(e){this.checkWalFileExists();let t=[];if((H(this.walFile)?.size||0)===0)return t;try{let n=(await Q(this.walFile,"utf8")).trim().split(` | |
`),o=p();for(let a=0;a<n.length;a++){if(!n[a].trim())continue;let c=this.lastProcessedEntryCount.get(e||"")||0;if(e&&a<c)continue;let[f,h,u,d,y,R,...re]=n[a].split(" ");if(e&&u!==e)continue;let se=Number(d.split(":")[1]),F=y==="0"?null:Number(y.split(":")[1]);if(F&&F<o)continue;let j=z(re,h);j!==void 0&&(e&&this.lastProcessedEntryCount.set(e,a+1),h==="W"?t.push({operation:"W",tableName:u,key:R,data:{value:j,version:se,timestamp:o,expiration:F}}):h==="D"&&t.push({operation:"D",tableName:u,key:R}))}return t}catch(s){return console.error(e?`Failed to replay WAL for table "${e}": ${s.message}`:`Failed to replay WAL: ${s.message}`),t}}async hasNewWALEntriesForTable(e){this.checkWalFileExists();try{let t=await Q(this.walFile,"utf8");if(!t.trim())return!1;let r=t.trim().split(` | |
`),s=this.lastProcessedEntryCount.get(e)||0;if(s>=r.length)return!1;for(let n=s;n<r.length;n++){let o=r[n];if(!o.trim())continue;let a=o.split(" ");if(a.length>=3&&a[2]===e)return!0}return!1}catch(t){return console.error(`Error checking WAL for ${e}:`,t),!0}}async flushWAL(){if(this.checkWalFileExists(),this.walBuffer.length===0)return;let e=[...this.walBuffer];this.walBuffer=[];try{await ye(this.walFile,e.join(""),"utf8");let t=H(this.walFile);t.size>this.maxWalSizeBeforeCheckpoint&&(process.env.DEBUG==="true"&&console.log(`WAL size (${t.size}) exceeds limit (${this.maxWalSizeBeforeCheckpoint}), triggering checkpoint`),this.checkpointCallback&&setImmediate(async()=>{try{await this.checkpointCallback()}catch(r){console.error("Error during automatic checkpoint:",r)}}))}catch(t){throw console.error(`Failed to flush WAL: ${t.message}`),this.walBuffer=[...e,...this.walBuffer],t}}async appendToWAL(e,t,r,s,n,o=0){this.checkWalFileExists();let l=`${p()} ${t} ${e} v:${n} x:${o} ${r} ${JSON.stringify(s)} | |
`;this.walBuffer.push(l),this.walBuffer.length>=this.maxWalBufferEntries&&await this.flushWAL(),this.walBuffer.reduce((h,u)=>h+u.length,0)>=this.maxWalBufferSize&&await this.flushWAL();let f=H(this.walFile);f.size>this.maxWalSizeBeforeCheckpoint&&(process.env.DEBUG==="true"&&console.log(`WAL size (${f.size}) exceeds limit (${this.maxWalSizeBeforeCheckpoint}), triggering checkpoint`),this.checkpointCallback&&setImmediate(async()=>{try{await this.checkpointCallback()}catch(h){console.error("Error during automatic checkpoint:",h)}}))}clearPositions(){this.lastProcessedEntryCount.clear()}};var T=()=>({db:{dbName:"mikrodb",databaseDirectory:"mikrodb",walFileName:"wal.log",walInterval:2e3,encryptionKey:"",maxWriteOpsBeforeFlush:100,debug:O(process.env.DEBUG)||!1},events:{},server:{port:Number(process.env.PORT)||3e3,host:process.env.HOST||"0.0.0.0",useHttps:!1,useHttp2:!1,sslCert:"",sslKey:"",sslCa:"",rateLimit:{enabled:!0,requestsPerMinute:100},allowedDomains:["*"],debug:O(process.env.DEBUG)||!1}});import{writeFile as be}from"node:fs/promises";async function Z(i,e,t){let r=new v,n=new E().toBinaryBuffer(e);if(!n){console.log("Buffer is empty, skipping...");return}if(t)try{let o=r.generateKey(t,"salt"),a=n.toString("binary"),l=r.generateIV(),c=r.encrypt(a,o,l);n=r.serialize(c)}catch(o){console.error("Encryption failed:",o)}await be(i,n)}var S=class{databaseDirectory;walFile;activeTable=null;data=new Map;writeBuffer=[];maxWriteOpsBeforeFlush=process.env.MAX_WRITE_OPS_BEFORE_FLUSH?Number.parseInt(process.env.MAX_WRITE_OPS_BEFORE_FLUSH):T().db.maxWriteOpsBeforeFlush;encryptionKey;cache;encryption;persistence;query;wal;mikroEvent;constructor(e,t){let{databaseDirectory:r,walFileName:s,walInterval:n}=e;this.databaseDirectory=r,this.walFile=N(this.databaseDirectory,s),this.encryptionKey=e.encryptionKey?e.encryptionKey:null,ee(this.databaseDirectory)||we(this.databaseDirectory),this.cache=new I,this.encryption=new v,this.persistence=new E,this.query=new L,this.wal=new B(this.walFile,n),this.mikroEvent=new P,this.setupEvents(t)}async start(){await this.applyWALEntries()}setupEvents(e){e?.targets?.forEach(t=>{this.mikroEvent.addTarget({name:t.name,url:t.url,headers:t.headers,events:t.events})}),e?.listeners?.forEach(t=>this.mikroEvent.on(t.event,t.handler))}async setActiveTable(e){this.activeTable!==e&&(this.hasTable(e)||await this.loadTable(e),await this.applyWALEntries(e),await this.evictTablesIfNeeded(),this.activeTable=e)}async applyWALEntries(e){let t=await this.wal.loadWAL(e);if(t.length===0)return;let r=e?[e]:[...new Set(t.map(s=>s.tableName))];for(let s of r){let n=t.filter(o=>o.tableName===s);e&&!this.hasTable(s)?await this.loadTable(s):this.createTable(s);for(let o of n)o.operation==="W"&&o.data?this.setItem(s,o.key,o.data):o.operation==="D"&&await this.deleteItem(s,o.key)}}async loadTable(e){if(this.hasTable(e))return;let t=N(this.databaseDirectory,e);if(!ee(t)){this.createTable(e);return}let r=await ve(t),s=r;if(this.encryptionKey&&r.length>0&&r[0]===1)try{let o=this.encryption.deserialize(r),a=this.encryption.generateKey(this.encryptionKey,"salt"),l=this.encryption.decrypt(o,a);s=Buffer.from(l,"binary")}catch(o){console.error(`Failed to decrypt ${e}:`,o)}let n=this.persistence.readTableFromBinaryBuffer(s);this.data.set(e,n),this.data.size>this.cache.cacheLimit&&setImmediate(()=>this.evictTablesIfNeeded())}async get(e){let{tableName:t}=e,r=e.key,s=e.options;if(await this.setActiveTable(t),!s)return r?this.getItem(t,r)?.value:[...this.getAll(t)];let n=this.getTable(t),o=await this.query.query(n,s.filter,s.limit);if(s.sort&&(o=o.sort(s.sort)),s.offset!=null||s.limit!=null){let a=s.offset||0,l=s.limit?a+s.limit:void 0;o=o.slice(a,l)}return o}async write(e,t={}){let{concurrencyLimit:r=10,flushImmediately:s=!1}=t,n=Array.isArray(e)?e:[e],o=n.length,a=0;for(;a<o;){let l=n.slice(a,a+r),c=l.map(h=>this.writeItem(h,!1));if((await Promise.all(c)).includes(!1))return!1;a+=l.length,this.writeBuffer.length>=this.maxWriteOpsBeforeFlush&&await this.flushWrites()}return(s||o>=0)&&await this.flush(),!0}async writeItem(e,t=!1){let{tableName:r,key:s,value:n,expectedVersion:o=null,expiration:a=0}=e;await this.setActiveTable(r);let{success:l,newVersion:c}=this.getItemVersion(r,s,o);return l?(await this.wal.appendToWAL(r,"W",s,n,c,a),this.setItem(r,s,{value:n,v:c,t:p(),x:a}),this.addToWriteBuffer(r,s),t&&await this.flush(),!0):!1}async delete(e,t,r=null,s=!1){if(await this.setActiveTable(e),!this.hasTable(e)||!this.hasKey(e,t))return console.log(`Key ${t} not found in table ${e}`),!1;let{success:n,currentVersion:o,expiration:a}=this.getItemVersion(e,t,r);return n?(await this.wal.appendToWAL(e,"D",t,null,o,a),await this.deleteItem(e,t),s&&await this.flush(),!0):!1}getItemVersion(e,t,r){let s=this.getItem(e,t),n=s?s.version:0,o=n+1,a=s&&s.expiration||0,l=!0;return r!==null&&n!==r&&(console.log(`Version mismatch for ${e}:${t}. Expected ${r}, found ${n}`),l=!1),{success:l,currentRecord:s,currentVersion:n,newVersion:o,expiration:a}}createTable(e){this.trackTableAccess(e),this.hasTable(e)||this.data.set(e,new Map)}getTable(e){return this.trackTableAccess(e),this.hasTable(e)?this.data.get(e):new Map}async getTableSize(e){return await this.setActiveTable(e),this.trackTableAccess(e),this.data.get(e)?.size}async deleteTable(e){this.trackTableAccess(e),this.data.delete(e);let t="table.deleted",{success:r,errors:s}=await this.mikroEvent.emit(t,{operation:t,table:e});r||console.error("Error when emitting events:",s)}hasTable(e){return this.trackTableAccess(e),this.data.has(e)}hasKey(e,t){return this.trackTableAccess(e),this.data.get(e)?.has(t)}getItem(e,t){this.trackTableAccess(e);let r=this.data.get(e)?.get(t);if(r){if(r?.x!==0&&Date.now()>r?.x){this.deleteItem(e,t);return}return{value:r.value,version:r.v,timestamp:r.t,expiration:r.x}}}getAll(e){this.trackTableAccess(e);let t=this.data.get(e);return t?Array.from(t):[]}setItem(e,t,r){this.trackTableAccess(e),this.createTable(e),this.data.get(e)?.set(t,r)}async deleteItem(e,t){this.data.get(e)?.delete(t);let r="item.deleted",{success:s,errors:n}=await this.mikroEvent.emit(r,{operation:r,table:e,key:t});s||console.error("Error when emitting events:",n)}addToWriteBuffer(e,t){let r=this.getItem(e,t);this.writeBuffer.push(JSON.stringify({tableName:e,key:t,record:r}))}trackTableAccess(e){this.cache.trackTableAccess(e)}async flush(){await this.flushWAL(),await this.flushWrites()}async flushWAL(){await this.wal.flushWAL()}async flushWrites(){if(this.writeBuffer.length!==0)try{let e=new Map,t=[...this.writeBuffer];for(let s of t){let n=JSON.parse(s);e.has(n.tableName)||e.set(n.tableName,new Map),e.get(n.tableName).set(n.key,n.record);let a="item.written",{success:l,errors:c}=await this.mikroEvent.emit(a,{operation:a,table:n.tableName,key:n.key,record:n.record});l||console.error("Error when emitting events:",c)}let r=Array.from(e.entries()).map(async([s])=>{let n=this.getTable(s),o=N(this.databaseDirectory,s);await Z(o,n,this.encryptionKey)});await Promise.all(r),this.writeBuffer=this.writeBuffer.slice(t.length)}catch(e){console.error(`Failed to flush writes: ${e.message}`)}}async flushTableToDisk(e){await this.setActiveTable(e);let t=this.getTable(e);if(t.size!==0){for(let[r,s]of t.entries())this.addToWriteBuffer(e,r);await this.flushWrites()}}async evictTablesIfNeeded(){let e=this.cache.findTablesForEviction(this.data.size);for(let t of e)await this.flushTableToDisk(t),this.data.delete(t)}async cleanupExpiredItems(){for(let[e,t]of this.data.entries()){let r=this.cache.findExpiredItems(t);for(let[s,n]of r){await this.wal.appendToWAL(e,"D",s,null,n.v,n.x),t.delete(s);let o="item.expired",{success:a,errors:l}=await this.mikroEvent.emit(o,{operation:o,table:e,key:s,record:n});a||console.error("Error when emitting events:",l)}}}async dump(e){e&&await this.setActiveTable(e);let t=this.getAll(this.activeTable);await Ee(`${this.databaseDirectory}/${this.activeTable}_dump.json`,JSON.stringify(t),"utf8")}getWAL(){return this.wal}getPersistence(){return this.persistence}};var C=class{table;checkpoint;constructor(e){let t=T(),r=e?.databaseDirectory||t.db.databaseDirectory,s=e?.walFileName||t.db.walFileName,n=e?.walInterval||t.db.walInterval,o=e?.encryptionKey||t.db.encryptionKey,a=e?.maxWriteOpsBeforeFlush||t.db.maxWriteOpsBeforeFlush,l=e?.events||{};e?.debug&&(process.env.DEBUG="true"),e?.maxWriteOpsBeforeFlush&&(process.env.MAX_WRITE_OPS_BEFORE_FLUSH=a.toString()),this.table=new S({databaseDirectory:r,walFileName:s,walInterval:n,encryptionKey:o},l);let c=this.table.getWAL();this.table.getWAL().setCheckpointCallback(()=>this.checkpoint.checkpoint(!0)),this.checkpoint=new A({table:this.table,wal:c,walFile:Te(r,s),checkpointIntervalMs:n}),this.checkpoint.start().catch(f=>console.error("Failed to start checkpoint service:",f))}async start(){await this.table.start(),await this.checkpoint.start()}async get(e){return await this.table.get(e)}async getTableSize(e){return await this.table.getTableSize(e)}async write(e,t){return await this.table.write(e,t)}async delete(e){let{tableName:t,key:r}=e,s=e?.expectedVersion||null;return await this.table.delete(t,r,s)}async deleteTable(e){return await this.table.deleteTable(e)}async close(){await this.flush()}async flush(){await this.table.flush()}async flushWAL(){await this.table.flushWAL()}async dump(e){await this.table.dump(e)}async cleanupExpiredItems(){await this.table.cleanupExpiredItems()}};async function te(i){let e=new C({...i.db,events:i?.events});await e.start();let t=new D(i.server);return t.get("/table",async r=>{let s=r.req.body;if(!s.tableName)return r.json({statusCode:400,body:"tableName is required"});let n=await e.getTableSize(s.tableName);return n||r.json({statusCode:404,body:null}),r.json({statusCode:200,body:n})}),t.post("/get",async r=>{let s=r.req.body;if(!s.tableName)return r.json({statusCode:400,body:"tableName is required"});let n={tableName:s.tableName,key:s.key},o=q(s?.options);o&&(n.options=o);let a=await e.get(n);return a||r.json({statusCode:404,body:null}),r.json({statusCode:200,body:a})}),t.post("/write",async r=>{let s=r.req.body;if(!s.tableName||s.value===void 0)return r.json({statusCode:400,body:"tableName and value are required"});let n={tableName:s.tableName,key:s.key,value:s.value,expectedVersion:s.expectedVersion,expiration:s.expiration},o={concurrencyLimit:s.concurrencyLimit,flushImmediately:s.flushImmediately},a=await e.write(n,o);return r.json({statusCode:200,body:a})}),t.delete("/delete",async r=>{let s=r.params;if(!s.tableName||!s.key)return r.json({statusCode:400,body:"tableName and key are required"});let n={tableName:s.tableName,key:s.key},o=await e.delete(n);return r.json({statusCode:200,body:o})}),t.start(),t}async function xe(){let e=process.argv[1]?.includes("node_modules/.bin/mikrodb"),t=(process.argv[2]||"")==="--force";if(e||t){console.log("\u{1F5C2}\uFE0F Welcome to MikroDB! \u2728");try{let r=T(),s=new x({configFilePath:"mikrodb.config.json",args:process.argv,options:[{flag:"--db",path:"db.dbName",defaultValue:r.db.dbName},{flag:"--dir",path:"db.databaseDirectory",defaultValue:r.db.databaseDirectory},{flag:"--wal",path:"db.walFileName",defaultValue:r.db.walFileName},{flag:"--interval",path:"db.walInterval",defaultValue:r.db.walInterval},{flag:"--encryptionKey",path:"db.encryptionKey",defaultValue:r.db.encryptionKey},{flag:"--maxWrites",path:"db.maxWriteOpsBeforeFlush",defaultValue:r.db.maxWriteOpsBeforeFlush},{flag:"--debug",path:"db.debug",isFlag:!0,defaultValue:r.db.debug},{path:"events",defaultValue:r.events},{flag:"--port",path:"server.port",defaultValue:r.server.port},{flag:"--host",path:"server.host",defaultValue:r.server.host},{flag:"--https",path:"server.useHttps",isFlag:!0,defaultValue:r.server.useHttps},{flag:"--http2",path:"server.useHttp2",isFlag:!0,defaultValue:r.server.useHttp2},{flag:"--cert",path:"server.sslCert",defaultValue:r.server.sslCert},{flag:"--key",path:"server.sslKey",defaultValue:r.server.sslKey},{flag:"--ca",path:"server.sslCa",defaultValue:r.server.sslCa},{flag:"--debug",path:"server.debug",isFlag:!0,defaultValue:r.server.debug}]}).get();te(s)}catch(r){console.error(r)}}}xe();export{C as MikroDB}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment