TreasureTrails/node_modules/@electric-sql/pglite-socket/dist/scripts/server.cjs

19 lines
16 KiB
JavaScript
Raw Normal View History

2026-03-18 09:02:21 -05:00
#!/usr/bin/env node
"use strict";var f=require("@electric-sql/pglite");var m=require("net"),P=6e4,u=class u extends EventTarget{constructor(e){super();this.socket=null;this.active=!1;this.db=e.db,this.closeOnDetach=e.closeOnDetach??!1,this.inspect=e.inspect??!1,this.debug=e.debug??!1,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.log("attach: waiting for PGlite to be ready"),await this.db.waitReady,this.log("attach: acquiring exclusive lock on PGlite instance"),await new Promise(t=>{this.db.runExclusive(()=>(t(),new Promise((i,s)=>{this.resolveLock=i,this.rejectLock=s})))}),this.log("attach: setting up socket event handlers"),e.on("data",async t=>{try{let i=await this.handleData(t);this.log(`socket on data sent: ${i} bytes`)}catch(i){this.log("socket on data error: ",i)}}),e.on("error",t=>this.handleError(t)),e.on("close",()=>this.handleClose()),this}detach(e){return this.log(`detach: detaching socket, close=${e??this.closeOnDetach}`),this.socket?(this.socket.removeAllListeners("data"),this.socket.removeAllListeners("error"),this.socket.removeAllListeners("close"),(e??this.closeOnDetach)&&this.socket.writable&&(this.log("detach: closing socket"),this.socket.end(),this.socket.destroy()),this.log("detach: releasing exclusive lock on PGlite instance"),this.resolveLock?.(),this.socket=null,this.active=!1,this):(this.log("detach: no socket attached, nothing to do"),this)}get isAttached(){return this.socket!==null}async handleData(e){if(!this.socket||!this.active)return this.log("handleData: no active socket, ignoring data"),new Promise((t,i)=>i("no active socket"));this.log(`handleData: received ${e.length} bytes`),this.inspectData("incoming",e);try{this.log("handleData: sending data to PGlite for processing");let t=await this.db.execProtocolRaw(new Uint8Array(e));if(this.log(`handleData: received ${t.length} bytes from PGlite`),this.inspectData("outgoing",t),this.socket&&this.socket.writable&&this.active){if(t.length<=0)return this.log("handleData: cowardly refusing to send empty packet"),new Promise((s,o)=>o("no data"));let i=new Promise((s,o)=>{this.log("handleData: writing response to socket"),this.socket?this.socket.write(Buffer.from(t),c=>{c?o(`Error while writing to the socket ${c.toString()}`):s(t.length)}):o("No socket")});return this.dispatchEvent(new CustomEvent("data",{detail:{incoming:e.length,outgoing:t.length}})),i}else return this.log("handleData: socket no longer writable or active, discarding response"),new Promise((i,s)=>s("No socket, not active or not writeable"))}catch(t){return this.log("handleData: error processing data:",t),this.handleError(t),new Promise((i,s)=>s(`Error while processing data ${t.toString()}`))}}handleError(e){this.log("handleError:",e),this.dispatchEvent(new CustomEvent("error",{detail:e})),this.log("handleError: rejecting exclusive lock on PGlite instance"),this.rejectLock?.(e),this.resolveLock=void 0,this.rejectLock=void 0,this.detach(!0)}handleClose(){this.log("handleClose: socket closed"),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 i=0;i<t.length;i+=16){let s=Math.min(16,t.length-i),o="";for(let a=0;a<16;a++)if(a<s){let h=t[i+a];o+=h.toString(16).padStart(2,"0")+" "}else o+=" ";let c="";for(let a=0;a<s;a++){let h=t[i+a];c+=h>=32&&h<=126?String.fromCharCode(h):"."}console.log(`${i.toString(16).padStart(8,"0")} ${o} ${c}`)}}}};u.nextHandlerId=1;var p=u,d=class extends EventTarget{constructor(e){super();this.server=null;this.active=!1;this.activeHandler=null;this.connectionQueue=[];this.handlerCount=0;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||
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)
`,g=class{constructor(n){this.db=null;this.server=null;this.subprocessManager=null;this.config=n}static parseConfig(){let n=r.values.extensions;return{dbPath:r.values.db,port:parseInt(r.values.port,10),host:r.values.host,path:r.values.path,debugLevel:parseInt(r.values.debug,10),extensionNames:n?n.split(",").map(e=>e.trim()):void 0,runCommand:r.values.run,includeDatabaseUrl:r.values["include-database-url"],shutdownTimeout:parseInt(r.values["shutdown-timeout"],10)}}createDatabaseUrl(){let{host:n,port:e,path:t}=this.config;if(t){let i=t.endsWith("/.s.PGSQL.5432")?t.slice(0,-13):t;return`postgresql://postgres:postgres@/postgres?host=${encodeURIComponent(i)}`}else return`postgresql://postgres:postgres@${n}:${e}/postgres`}async importExtensions(){if(!this.config.extensionNames?.length)return;let n={},e=["vector","live","pg_hashids","pg_ivm","pg_uuidv7","pgtap"];for(let t of this.config.extensionNames){let i=null;try{if(t.includes(":")){let[s,o]=t.split(":");if(!s||!o)throw new Error(`Invalid extension format '${t}'. Expected: package/path:exportedName`);i=(await import(s))[o],i&&(n[o]=i,console.log(`Imported extension '${o}' from '${s}'`))}else if(e.includes(t))i=(await import(`@electric-sql/pglite/${t}`))[t],i&&(n[t]=i,console.log(`Imported extension: ${t}`));else{try{i=(await import(`@electric-sql/pglite/contrib/${t}`))[t]}catch{i=(await import(`@electric-sql/pglite-${t}`))[t]}i&&(n[t]=i,console.log(`Imported extension: ${t}`))}}catch(s){throw console.error(`Failed to import extension '${t}':`,s),new Error(`Failed to import extension '${t}'`)}}return Object.keys(n).length>0?n:void 0}async initializeDatabase(){console.log(`Initializing PGLite with database: ${this.config.dbPath}`),console.log(`Debug level: ${this.config.debugLevel}`);let n=await this.importExtensions();this.db=new f.PGlite(this.config.dbPath,{debug:this.config.debugLevel,extensions:n}),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",n=>{let e=n.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",n=>{let{clientAddress:e,clientPort:t}=n.detail;console.log(`Client connected from ${e}:${t}`)}),this.server.addEventListener("error",n=>{let e=n.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}),this.subprocessManager=new v(n=>{this.shutdown(n)}),this.setupServerEventHandlers(),this.setupSignalHandlers(),await this.server.start()}catch(n){throw console.error("Failed to start PGLiteSocketServer:",n),n}}async shutdown(n=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(n)}},v=class{constructor(n){this.childProcess=null;this.onExit=n}get process(){return this.childProcess}spawn(n,e,t){console.log(`Running command: ${n}`);let i={...process.env};t&&(i.DATABASE_URL=e,console.log(`Setting DATABASE_URL=${e}`));let s=n.trim().split(/\s+/);this.childProcess=(0,w.spawn)(s[0],s.slice(1),{env:i,stdio:"inherit"}),this.childProcess.on("error",o=>{console.error("Error running command:",o),console.log("Subprocess failed to start, shutting down..."),this.onExit(1)}),this.childProcess.on("close",o=>{console.log(`Command exited with code ${o}`),this.childProcess=null,o!==null&&o!==0&&(console.log(`Child process failed with exit code ${o}, shutting down...`),this.onExit(o))})}terminate(n){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"))},n))}};async function S(){r.values.help&&(console.log(k),process.exit(0));try{let l=g.parseConfig();await new g(l).start()}catch(l){console.error("Unhandled error:",l),process.exit(1)}}S();
//# sourceMappingURL=server.cjs.map