#!/usr/bin/env node "use strict";var b=require("@electric-sql/pglite");var v=require("net");var m=class{constructor(i,e=!1){this.queue=[];this.processing=!1;this.lastHandlerId=null;this.db=i,this.debug=e}log(i,...e){this.debug&&console.log(`[QueryQueueManager] ${i}`,...e)}async enqueue(i,e,t){return new Promise((s,r)=>{let n={handlerId:i,message:e,resolve:s,reject:r,timestamp:Date.now(),onData:t};this.queue.push(n),this.log(`enqueued query from handler #${i}, queue size: ${this.queue.length}`),this.processing||this.processQueue()})}async processQueue(){if(!(this.processing||this.queue.length===0)){for(this.processing=!0;this.queue.length>0;){let i;if(this.db.isInTransaction()&&this.lastHandlerId){let s=this.queue.findIndex(r=>r.handlerId===this.lastHandlerId);s===-1?(this.log("transaction started, but no query from the same handler id found in queue",this.lastHandlerId),i=null):i=this.queue.splice(s,1)[0]}else i=this.queue.shift();if(!i)break;let e=Date.now()-i.timestamp;this.log(`processing query from handler #${i.handlerId} (waited ${e}ms)`);let t=0;try{await this.db.runExclusive(async()=>await this.db.execProtocolRawStream(i.message,{onRawData:s=>{t+=s.length,i.onData(s)}}))}catch(s){this.log(`query from handler #${i.handlerId} failed:`,s),i.reject(s);return}this.log(`query from handler #${i.handlerId} completed, ${t} bytes`),this.lastHandlerId=i.handlerId,i.resolve(t)}this.processing=!1,this.log("queue processing complete, queue length is",this.queue.length)}}getQueueLength(){return this.queue.length}clearQueueForHandler(i){let e=this.queue.length;this.queue=this.queue.filter(s=>s.handlerId===i?(s.reject(new Error("Handler disconnected")),!1):!0);let t=e-this.queue.length;t>0&&this.log(`cleared ${t} queries for handler #${i}`)}async clearTransactionIfNeeded(i){this.db.isInTransaction()&&this.lastHandlerId===i&&(await this.db.exec("ROLLBACK"),this.lastHandlerId=null,await this.processQueue())}},u=class u extends EventTarget{constructor(e){super();this.socket=null;this.active=!1;this.messageBuffer=Buffer.alloc(0);this.lastActivityTime=Date.now();this.queryQueue=e.queryQueue,this.closeOnDetach=e.closeOnDetach??!1,this.inspect=e.inspect??!1,this.debug=e.debug??!1,this.idleTimeout=e.idleTimeout??0,this.id=u.nextHandlerId++,this.log("constructor: created new handler")}get handlerId(){return this.id}log(e,...t){this.debug&&console.log(`[PGLiteSocketHandler#${this.id}] ${e}`,...t)}async attach(e){if(this.log(`attach: attaching socket from ${e.remoteAddress}:${e.remotePort}`),this.socket)throw new Error("Socket already attached");return this.socket=e,this.active=!0,this.lastActivityTime=Date.now(),e.setNoDelay(!0),this.idleTimeout>0&&this.resetIdleTimer(),this.log("attach: setting up socket event handlers"),e.on("data",t=>{this.lastActivityTime=Date.now(),this.resetIdleTimer(),setImmediate(async()=>{try{await this.handleData(t)}catch(s){this.log("socket on data error: ",s),this.handleError(s)}})}),e.on("error",t=>{setImmediate(()=>this.handleError(t))}),e.on("close",()=>{setImmediate(()=>this.handleClose())}),this.log("attach: socket handler ready"),this}resetIdleTimer(){this.idleTimeout<=0||(this.idleTimer&&clearTimeout(this.idleTimer),this.idleTimer=setTimeout(()=>{let e=Date.now()-this.lastActivityTime;this.log(`idle timeout after ${e}ms`),this.handleError(new Error("Idle timeout"))},this.idleTimeout))}async detach(e){if(this.log(`detach: detaching socket, close=${e??this.closeOnDetach}`),this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=void 0),this.queryQueue.clearQueueForHandler(this.id),await this.queryQueue.clearTransactionIfNeeded(this.id),!this.socket)return this.log("detach: no socket attached, nothing to do"),this;if(this.socket.removeAllListeners("data"),this.socket.removeAllListeners("error"),this.socket.removeAllListeners("close"),(e??this.closeOnDetach)&&this.socket.writable){this.log("detach: closing socket");try{this.socket.end(),this.socket.destroy()}catch(t){this.log("detach: error closing socket:",t)}}return this.socket=null,this.active=!1,this.messageBuffer=Buffer.alloc(0),this.log("detach: handler cleaned up"),this}get isAttached(){return this.socket!==null}async handleData(e){if(!this.socket||!this.active)return this.log("handleData: no active socket, ignoring data"),0;this.log(`handleData: received ${e.length} bytes`),this.messageBuffer=Buffer.concat([this.messageBuffer,e]),this.inspectData("incoming",e);try{let t=0;for(;this.messageBuffer.length>0;){let s=0,r=!1;if(this.messageBuffer.length>=4){let o=this.messageBuffer.readInt32BE(0);if(this.messageBuffer.length>=8){let a=this.messageBuffer.readInt32BE(4);(a===196608||a===196608)&&(s=o,r=this.messageBuffer.length>=s)}!r&&this.messageBuffer.length>=5&&(s=1+this.messageBuffer.readInt32BE(1),r=this.messageBuffer.length>=s)}if(!r||s===0){this.log(`handleData: incomplete message, buffering ${this.messageBuffer.length} bytes`);break}let n=this.messageBuffer.slice(0,s);if(this.messageBuffer=this.messageBuffer.slice(s),this.log(`handleData: processing message of ${n.length} bytes`),!this.active||!this.socket){this.log("handleData: socket no longer active, stopping processing");break}let c;if(await this.queryQueue.enqueue(this.id,new Uint8Array(n),o=>{this.log(`handleData: received ${o.length} bytes from PGlite`),this.inspectData("outgoing",o),o.length>0&&this.socket&&this.socket.writable&&this.active&&(this.log("handleData: writing response to socket"),this.socket?.writable?this.socket.write(Buffer.from(o),a=>{a?(this.log("handleData: error writing to socket:",a),c=a):this.log(`handleData: socket sent: ${o.length} bytes`)}):this.log("handleData: socket no longer writable")),t+=o.length}),c)throw c}return this.dispatchEvent(new CustomEvent("data",{detail:{incoming:e.length,outgoing:t}})),t}catch(t){throw this.log("handleData: error processing data:",t),t}}handleError(e){if(!this.active){this.log("handleError: handler not active, ignoring error");return}e.message?.includes("ECONNRESET")?this.log("handleError: client disconnected (ECONNRESET) - normal behavior"):e.message?.includes("Idle timeout")?this.log("handleError: connection idle timeout"):this.log("handleError:",e),this.active=!1,this.dispatchEvent(new CustomEvent("error",{detail:e})),this.detach(!0)}handleClose(){this.log("handleClose: socket closed"),this.active=!1,this.dispatchEvent(new CustomEvent("close")),this.detach(!1)}inspectData(e,t){if(this.inspect){console.log("-".repeat(75)),console.log(e==="incoming"?"-> incoming":"<- outgoing",t.length,"bytes");for(let s=0;s=32&&a<=126?String.fromCharCode(a):"."}console.log(`${s.toString(16).padStart(8,"0")} ${n} ${c}`)}}}};u.nextHandlerId=1;var p=u,d=class extends EventTarget{constructor(e){super();this.server=null;this.active=!1;this.handlers=new Set;this.db=e.db,e.path?this.path=e.path:(typeof e.port=="number"?this.port=e.port??e.port:this.port=5432,this.host=e.host||"127.0.0.1"),this.inspect=e.inspect??!1,this.debug=e.debug??!1,this.idleTimeout=e.idleTimeout??0,this.maxConnections=e.maxConnections??1,this.queryQueue=new m(this.db,this.debug),this.log(`constructor: created server on ${this.getServerConn()}`),this.log(`constructor: max connections: ${this.maxConnections}`),this.idleTimeout>0&&this.log(`constructor: idle timeout: ${this.idleTimeout}ms`)}log(e,...t){this.debug&&console.log(`[PGLiteSocketServer] ${e}`,...t)}async start(){if(this.log(`start: starting server on ${this.getServerConn()}`),this.server)throw new Error("Socket server already started");return await this.db.waitReady,this.active=!0,this.server=(0,v.createServer)(e=>{setImmediate(()=>this.handleConnection(e))}),this.server.maxConnections=this.maxConnections,new Promise((e,t)=>{if(!this.server)return t(new Error("Server not initialized"));if(this.server.on("error",s=>{this.log("start: server error:",s),this.dispatchEvent(new CustomEvent("error",{detail:s})),this.active||t(s)}),this.path)this.server.listen(this.path,()=>{this.log(`start: server listening on ${this.getServerConn()}`),this.dispatchEvent(new CustomEvent("listening",{detail:{path:this.path}})),e()});else{let s=this.server;s.listen(this.port,this.host,()=>{let r=s.address();if(r===null||typeof r!="object")throw Error("Expected address info");this.port=r.port,this.log(`start: server listening on ${this.getServerConn()}`),this.dispatchEvent(new CustomEvent("listening",{detail:{port:this.port,host:this.host}})),e()})}})}getServerConn(){return this.path?this.path:`${this.host}:${this.port}`}async stop(){this.log("stop: stopping server"),this.active=!1,this.log(`stop: detaching ${this.handlers.size} handlers`);for(let e of this.handlers)e.detach(!0);return this.handlers.clear(),this.server?new Promise(e=>{if(!this.server)return e();this.server.close(()=>{this.log("stop: server closed"),this.server=null,this.dispatchEvent(new CustomEvent("close")),e()})}):(this.log("stop: server not running, nothing to do"),Promise.resolve())}async handleConnection(e){let t={clientAddress:e.remoteAddress||"unknown",clientPort:e.remotePort||0};if(this.log(`handleConnection: new connection from ${t.clientAddress}:${t.clientPort}`),this.log(`handleConnection: active connections: ${this.handlers.size}, queued queries: ${this.queryQueue.getQueueLength()}`),!this.active){this.log("handleConnection: server not active, closing connection");try{e.end()}catch(r){this.log("handleConnection: error closing socket:",r)}return}if(this.handlers.size>=this.maxConnections){this.log("handleConnection: max connections reached, rejecting"),e.write(Buffer.from(`Too many connections `)),e.end();return}let s=new p({queryQueue:this.queryQueue,closeOnDetach:!0,inspect:this.inspect,debug:this.debug,idleTimeout:this.idleTimeout});this.handlers.add(s),s.addEventListener("error",r=>{let n=r.detail;n?.message?.includes("ECONNRESET")?this.log(`handler #${s.handlerId}: client disconnected (ECONNRESET)`):n?.message?.includes("Idle timeout")?this.log(`handler #${s.handlerId}: idle timeout`):this.log(`handler #${s.handlerId}: error:`,n)}),s.addEventListener("close",()=>{this.log(`handler #${s.handlerId}: closed`),this.handlers.delete(s),this.log(`handleConnection: active connections: ${this.handlers.size}`)});try{await s.attach(e),this.dispatchEvent(new CustomEvent("connection",{detail:t}))}catch(r){this.log("handleConnection: error attaching socket:",r),this.handlers.delete(s),this.dispatchEvent(new CustomEvent("error",{detail:r}));try{e.end()}catch(n){this.log("handleConnection: error closing socket:",n)}}}getStats(){return{activeConnections:this.handlers.size,queuedQueries:this.queryQueue.getQueueLength(),maxConnections:this.maxConnections}}};var y=require("util"),w=require("child_process"),l=(0,y.parseArgs)({options:{db:{type:"string",short:"d",default:"memory://",help:"Database path (relative or absolute). Use memory:// for in-memory database."},port:{type:"string",short:"p",default:"5432",help:"Port to listen on"},host:{type:"string",short:"h",default:"127.0.0.1",help:"Host to bind to"},path:{type:"string",short:"u",default:void 0,help:"unix socket to bind to. Takes precedence over host:port"},debug:{type:"string",short:"v",default:"0",help:"Debug level (0-5)"},extensions:{type:"string",short:"e",default:void 0,help:"Comma-separated list of extensions to load (e.g., vector,pgcrypto,postgis etc.)"},run:{type:"string",short:"r",default:void 0,help:"Command to run after server starts"},"include-database-url":{type:"boolean",default:!1,help:"Include DATABASE_URL in the environment of the subprocess"},"shutdown-timeout":{type:"string",default:"5000",help:"Timeout in milliseconds for graceful subprocess shutdown (default: 5000)"},"max-connections":{type:"string",short:"m",default:"1",help:"Maximum concurrent connections (default: 1)"},help:{type:"boolean",short:"?",default:!1,help:"Show help"}}}),x=`PGlite Socket Server Usage: pglite-server [options] Options: -d, --db=PATH Database path (default: memory://) -p, --port=PORT Port to listen on (default: 5432) -h, --host=HOST Host to bind to (default: 127.0.0.1) -u, --path=UNIX Unix socket to bind to (default: undefined). Takes precedence over host:port -v, --debug=LEVEL Debug level 0-5 (default: 0) -e, --extensions=LIST Comma-separated list of extensions to load Formats: vector, pgcrypto (built-in/contrib) @org/package/path:exportedName (npm package) -r, --run=COMMAND Command to run after server starts --include-database-url Include DATABASE_URL in subprocess environment --shutdown-timeout=MS Timeout for graceful subprocess shutdown in ms (default: 5000) -m, --max-connections=N Maximum concurrent connections (default is no concurrency: 1) `,g=class{constructor(i){this.db=null;this.server=null;this.subprocessManager=null;this.config=i}static parseConfig(){let i=l.values.extensions;return{dbPath:l.values.db,port:parseInt(l.values.port,10),host:l.values.host,path:l.values.path,debugLevel:parseInt(l.values.debug,10),extensionNames:i?i.split(",").map(e=>e.trim()):void 0,runCommand:l.values.run,includeDatabaseUrl:l.values["include-database-url"],shutdownTimeout:parseInt(l.values["shutdown-timeout"],10),maxConnections:parseInt(l.values["max-connections"],10)}}createDatabaseUrl(){let{host:i,port:e,path:t}=this.config;if(t){let s=t.endsWith("/.s.PGSQL.5432")?t.slice(0,-13):t;return`postgresql://postgres:postgres@/postgres?host=${encodeURIComponent(s)}`}else return`postgresql://postgres:postgres@${i}:${e}/postgres`}async importExtensions(){if(!this.config.extensionNames?.length)return;let i={},e=["vector","live","pg_hashids","pg_ivm","pg_uuidv7","pgtap","age","pg_textsearch"];for(let t of this.config.extensionNames){let s=null;try{if(t.includes(":")){let[r,n]=t.split(":");if(!r||!n)throw new Error(`Invalid extension format '${t}'. Expected: package/path:exportedName`);s=(await import(r))[n],s&&(i[n]=s,console.log(`Imported extension '${n}' from '${r}'`))}else if(e.includes(t))s=(await import(`@electric-sql/pglite/${t}`))[t],s&&(i[t]=s,console.log(`Imported extension: ${t}`));else{try{s=(await import(`@electric-sql/pglite/contrib/${t}`))[t]}catch{s=(await import(`@electric-sql/pglite-${t}`))[t]}s&&(i[t]=s,console.log(`Imported extension: ${t}`))}}catch(r){throw console.error(`Failed to import extension '${t}':`,r),new Error(`Failed to import extension '${t}'`)}}return Object.keys(i).length>0?i:void 0}async initializeDatabase(){console.log(`Initializing PGLite with database: ${this.config.dbPath}`),console.log(`Debug level: ${this.config.debugLevel}`);let i=await this.importExtensions();this.db=new b.PGlite(this.config.dbPath,{debug:this.config.debugLevel,extensions:i}),await this.db.waitReady,console.log("PGlite database initialized")}setupServerEventHandlers(){if(!this.server||!this.subprocessManager)throw new Error("Server or subprocess manager not initialized");this.server.addEventListener("listening",i=>{let e=i.detail;if(console.log(`PGLiteSocketServer listening on ${JSON.stringify(e)}`),this.config.runCommand&&this.subprocessManager){let t=this.createDatabaseUrl();this.subprocessManager.spawn(this.config.runCommand,t,this.config.includeDatabaseUrl)}}),this.server.addEventListener("connection",i=>{let{clientAddress:e,clientPort:t}=i.detail;console.log(`Client connected from ${e}:${t}`)}),this.server.addEventListener("error",i=>{let e=i.detail;console.error("Socket server error:",e)})}setupSignalHandlers(){process.on("SIGINT",()=>this.shutdown()),process.on("SIGTERM",()=>this.shutdown())}async start(){try{if(await this.initializeDatabase(),!this.db)throw new Error("Database initialization failed");this.server=new d({db:this.db,port:this.config.port,host:this.config.host,path:this.config.path,inspect:this.config.debugLevel>0,maxConnections:this.config.maxConnections}),this.subprocessManager=new f(i=>{this.shutdown(i)}),this.setupServerEventHandlers(),this.setupSignalHandlers(),await this.server.start()}catch(i){throw console.error("Failed to start PGLiteSocketServer:",i),i}}async shutdown(i=0){console.log(` Shutting down PGLiteSocketServer...`),this.subprocessManager&&this.subprocessManager.terminate(this.config.shutdownTimeout),this.server&&await this.server.stop(),this.db&&await this.db.close(),console.log("Server stopped"),process.exit(i)}},f=class{constructor(i){this.childProcess=null;this.onExit=i}get process(){return this.childProcess}spawn(i,e,t){console.log(`Running command: ${i}`);let s={...process.env};t&&(s.DATABASE_URL=e,console.log(`Setting DATABASE_URL=${e}`));let r=i.trim().split(/\s+/);this.childProcess=(0,w.spawn)(r[0],r.slice(1),{env:s,stdio:"inherit"}),this.childProcess.on("error",n=>{console.error("Error running command:",n),console.log("Subprocess failed to start, shutting down..."),this.onExit(1)}),this.childProcess.on("close",n=>{console.log(`Command exited with code ${n}`),this.childProcess=null,n!==null&&n!==0&&(console.log(`Child process failed with exit code ${n}, shutting down...`),this.onExit(n))})}terminate(i){this.childProcess&&(console.log("Terminating child process..."),this.childProcess.kill("SIGTERM"),setTimeout(()=>{this.childProcess&&!this.childProcess.killed&&(console.log("Force killing child process..."),this.childProcess.kill("SIGKILL"))},i))}};async function C(){l.values.help&&(console.log(x),process.exit(0));try{let h=g.parseConfig();await new g(h).start()}catch(h){console.error("Unhandled error:",h),process.exit(1)}}C(); //# sourceMappingURL=server.cjs.map