UNPKG

@electric-sql/pglite-socket

Version:

A socket implementation for PGlite enabling remote connections

2 lines 9.99 kB
"use strict";var u=Object.defineProperty;var m=Object.getOwnPropertyDescriptor;var f=Object.getOwnPropertyNames;var b=Object.prototype.hasOwnProperty;var k=(s,o)=>{for(var e in o)u(s,e,{get:o[e],enumerable:!0})},w=(s,o,e,t)=>{if(o&&typeof o=="object"||typeof o=="function")for(let i of f(o))!b.call(s,i)&&i!==e&&u(s,i,{get:()=>o[i],enumerable:!(t=m(o,i))||t.enumerable});return s};var E=s=>w(u({},"__esModule",{value:!0}),s);var P={};k(P,{CONNECTION_QUEUE_TIMEOUT:()=>p,PGLiteSocketHandler:()=>l,PGLiteSocketServer:()=>g});module.exports=E(P);var v=require("net"),p=6e4,d=class d 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=d.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,n)=>{this.resolveLock=i,this.rejectLock=n})))}),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((n,r)=>r("no data"));let i=new Promise((n,r)=>{this.log("handleData: writing response to socket"),this.socket?this.socket.write(Buffer.from(t),a=>{a?r(`Error while writing to the socket ${a.toString()}`):n(t.length)}):r("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,n)=>n("No socket, not active or not writeable"))}catch(t){return this.log("handleData: error processing data:",t),this.handleError(t),new Promise((i,n)=>n(`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 n=Math.min(16,t.length-i),r="";for(let c=0;c<16;c++)if(c<n){let h=t[i+c];r+=h.toString(16).padStart(2,"0")+" "}else r+=" ";let a="";for(let c=0;c<n;c++){let h=t[i+c];a+=h>=32&&h<=126?String.fromCharCode(h):"."}console.log(`${i.toString(16).padStart(8,"0")} ${r} ${a}`)}}}};d.nextHandlerId=1;var l=d,g=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:(this.port=e.port||5432,this.host=e.host||"127.0.0.1"),this.inspect=e.inspect??!1,this.debug=e.debug??!1,this.connectionQueueTimeout=e.connectionQueueTimeout??p,this.log(`constructor: created server on ${this.host}:${this.port}`),this.log(`constructor: connection queue timeout: ${this.connectionQueueTimeout}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 this.active=!0,this.server=(0,v.createServer)(e=>this.handleConnection(e)),new Promise((e,t)=>{if(!this.server)return t(new Error("Server not initialized"));this.server.on("error",i=>{this.log("start: server error:",i),this.dispatchEvent(new CustomEvent("error",{detail:i})),t(i)}),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()}):this.server.listen(this.port,this.host,()=>{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(){return this.log("stop: stopping server"),this.active=!1,this.log(`stop: clearing connection queue (${this.connectionQueue.length} connections)`),this.connectionQueue.forEach(e=>{clearTimeout(e.timeoutId),e.socket.writable&&(this.log(`stop: closing queued connection from ${e.clientInfo.clientAddress}:${e.clientInfo.clientPort}`),e.socket.end())}),this.connectionQueue=[],this.activeHandler&&(this.log(`stop: detaching active handler #${this.activeHandlerId}`),this.activeHandler.detach(!0),this.activeHandler=null),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())}get activeHandlerId(){return this.activeHandler?.handlerId??null}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.active){this.log("handleConnection: server not active, closing connection"),e.end();return}if(!this.activeHandler||!this.activeHandler.isAttached){this.log("handleConnection: no active handler, attaching socket directly"),this.dispatchEvent(new CustomEvent("connection",{detail:t})),await this.attachSocketToNewHandler(e,t);return}this.log(`handleConnection: active handler #${this.activeHandlerId} exists, queueing connection`),this.enqueueConnection(e,t)}enqueueConnection(e,t){this.log(`enqueueConnection: queueing connection from ${t.clientAddress}:${t.clientPort}, timeout: ${this.connectionQueueTimeout}ms`);let i=setTimeout(()=>{this.log(`enqueueConnection: timeout for connection from ${t.clientAddress}:${t.clientPort}`),this.connectionQueue=this.connectionQueue.filter(n=>n.socket!==e),e.writable&&(this.log("enqueueConnection: closing timed out connection"),e.end()),this.dispatchEvent(new CustomEvent("queueTimeout",{detail:{...t,queueSize:this.connectionQueue.length}}))},this.connectionQueueTimeout);this.connectionQueue.push({socket:e,clientInfo:t,timeoutId:i}),this.log(`enqueueConnection: connection queued, queue size: ${this.connectionQueue.length}`),this.dispatchEvent(new CustomEvent("queuedConnection",{detail:{...t,queueSize:this.connectionQueue.length}}))}processNextInQueue(){if(this.log(`processNextInQueue: processing next connection, queue size: ${this.connectionQueue.length}`),this.connectionQueue.length===0||!this.active){this.log("processNextInQueue: no connections in queue or server not active, nothing to do");return}let e=this.connectionQueue.shift();if(e){if(this.log(`processNextInQueue: processing connection from ${e.clientInfo.clientAddress}:${e.clientInfo.clientPort}`),clearTimeout(e.timeoutId),!e.socket.writable){this.log("processNextInQueue: socket no longer writable, skipping to next connection"),this.processNextInQueue();return}this.attachSocketToNewHandler(e.socket,e.clientInfo).catch(t=>{this.log("processNextInQueue: error attaching socket:",t),this.dispatchEvent(new CustomEvent("error",{detail:t})),this.processNextInQueue()})}}async attachSocketToNewHandler(e,t){this.handlerCount++,this.log(`attachSocketToNewHandler: creating new handler for ${t.clientAddress}:${t.clientPort} (handler #${this.handlerCount})`);let i=new l({db:this.db,closeOnDetach:!0,inspect:this.inspect,debug:this.debug});i.addEventListener("error",n=>{this.log(`handler #${i.handlerId}: error from handler:`,n.detail),this.dispatchEvent(new CustomEvent("error",{detail:n.detail}))}),i.addEventListener("close",()=>{this.log(`handler #${i.handlerId}: closed`),this.activeHandler===i&&(this.log(`handler #${i.handlerId}: was active handler, processing next connection in queue`),this.activeHandler=null,this.processNextInQueue())});try{this.activeHandler=i,this.log(`handler #${i.handlerId}: attaching socket`),await i.attach(e),this.dispatchEvent(new CustomEvent("connection",{detail:t}))}catch(n){throw this.log(`handler #${i.handlerId}: error attaching socket:`,n),this.activeHandler=null,e.writable&&e.end(),n}}};0&&(module.exports={CONNECTION_QUEUE_TIMEOUT,PGLiteSocketHandler,PGLiteSocketServer}); //# sourceMappingURL=index.cjs.map