Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@electric-sql/pglite-socket

Package Overview
Dependencies
Maintainers
4
Versions
28
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@electric-sql/pglite-socket - npm Package Compare versions

Comparing version
0.0.20
to
0.0.21
+3
dist/chunk-JGAU6COP.js
import{createServer as f}from"net";var y=6e4,d=class{constructor(s,e=!1){this.queue=[];this.processing=!1;this.lastHandlerId=null;this.db=s,this.debug=e}log(s,...e){this.debug&&console.log(`[QueryQueueManager] ${s}`,...e)}async enqueue(s,e){return new Promise((t,i)=>{let r={handlerId:s,message:e,resolve:t,reject:i,timestamp:Date.now()};this.queue.push(r),this.log(`enqueued query from handler #${s}, 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 s;if(this.db.isInTransaction()&&this.lastHandlerId){let t=this.queue.findIndex(i=>i.handlerId===this.lastHandlerId);t===-1?(this.log("transaction started, but no query from the same handler id found in queue",this.lastHandlerId),s=null):s=this.queue.splice(t,1)[0]}else s=this.queue.shift();if(!s)break;let e=Date.now()-s.timestamp;this.log(`processing query from handler #${s.handlerId} (waited ${e}ms)`);try{let t=await this.db.runExclusive(async()=>await this.db.execProtocolRaw(s.message));this.log(`query from handler #${s.handlerId} completed, ${t.length} bytes`),this.lastHandlerId=s.handlerId,s.resolve(t)}catch(t){this.log(`query from handler #${s.handlerId} failed:`,t),s.reject(t)}}this.processing=!1,this.log("queue processing complete, queue length is",this.queue.length)}}getQueueLength(){return this.queue.length}clearQueueForHandler(s){let e=this.queue.length;this.queue=this.queue.filter(i=>i.handlerId===s?(i.reject(new Error("Handler disconnected")),!1):!0);let t=e-this.queue.length;t>0&&this.log(`cleared ${t} queries for handler #${s}`)}async clearTransactionIfNeeded(s){this.db.isInTransaction()&&this.lastHandlerId===s&&(await this.db.exec("ROLLBACK"),this.lastHandlerId=null,await this.processQueue())}},l=class l 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=l.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(i){this.log("socket on data error: ",i),this.handleError(i)}})}),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 i=0,r=!1;if(this.messageBuffer.length>=4){let n=this.messageBuffer.readInt32BE(0);if(this.messageBuffer.length>=8){let a=this.messageBuffer.readInt32BE(4);(a===196608||a===196608)&&(i=n,r=this.messageBuffer.length>=i)}!r&&this.messageBuffer.length>=5&&(i=1+this.messageBuffer.readInt32BE(1),r=this.messageBuffer.length>=i)}if(!r||i===0){this.log(`handleData: incomplete message, buffering ${this.messageBuffer.length} bytes`);break}let o=this.messageBuffer.slice(0,i);if(this.messageBuffer=this.messageBuffer.slice(i),this.log(`handleData: processing message of ${o.length} bytes`),!this.active||!this.socket){this.log("handleData: socket no longer active, stopping processing");break}let h=await this.queryQueue.enqueue(this.id,new Uint8Array(o));this.log(`handleData: received ${h.length} bytes from PGlite`),this.inspectData("outgoing",h),h.length>0&&this.socket&&this.socket.writable&&this.active&&await new Promise((n,a)=>{this.log("handleData: writing response to socket"),this.socket?.writable?this.socket.write(Buffer.from(h),c=>{c?(this.log("handleData: error writing to socket:",c),a(c)):(this.log(`handleData: socket sent: ${h.length} bytes`),n(h.length))}):(this.log("handleData: socket no longer writable"),n(0))}).catch(n=>{throw this.log("handleData: failed to write to socket:",n),n}),t+=h.length}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 i=0;i<t.length;i+=16){let r=Math.min(16,t.length-i),o="";for(let n=0;n<16;n++)if(n<r){let a=t[i+n];o+=a.toString(16).padStart(2,"0")+" "}else o+=" ";let h="";for(let n=0;n<r;n++){let a=t[i+n];h+=a>=32&&a<=126?String.fromCharCode(a):"."}console.log(`${i.toString(16).padStart(8,"0")} ${o} ${h}`)}}}};l.nextHandlerId=1;var u=l,g=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 d(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=f(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",i=>{this.log("start: server error:",i),this.dispatchEvent(new CustomEvent("error",{detail:i})),this.active||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()});else{let i=this.server;i.listen(this.port,this.host,()=>{let r=i.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 i=new u({queryQueue:this.queryQueue,closeOnDetach:!0,inspect:this.inspect,debug:this.debug,idleTimeout:this.idleTimeout});this.handlers.add(i),i.addEventListener("error",r=>{let o=r.detail;o?.message?.includes("ECONNRESET")?this.log(`handler #${i.handlerId}: client disconnected (ECONNRESET)`):o?.message?.includes("Idle timeout")?this.log(`handler #${i.handlerId}: idle timeout`):this.log(`handler #${i.handlerId}: error:`,o)}),i.addEventListener("close",()=>{this.log(`handler #${i.handlerId}: closed`),this.handlers.delete(i),this.log(`handleConnection: active connections: ${this.handlers.size}`)});try{await i.attach(e),this.dispatchEvent(new CustomEvent("connection",{detail:t}))}catch(r){this.log("handleConnection: error attaching socket:",r),this.handlers.delete(i),this.dispatchEvent(new CustomEvent("error",{detail:r}));try{e.end()}catch(o){this.log("handleConnection: error closing socket:",o)}}}getStats(){return{activeConnections:this.handlers.size,queuedQueries:this.queryQueue.getQueueLength(),maxConnections:this.maxConnections}}};export{y as a,u as b,g as c};
//# sourceMappingURL=chunk-JGAU6COP.js.map
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { PGlite } from '@electric-sql/pglite'\nimport { type Server, type Socket, createServer } from 'net'\n\n// Connection queue timeout in milliseconds\nexport const CONNECTION_QUEUE_TIMEOUT = 60000 // 60 seconds\n\n/**\n * Represents a queued query waiting for PGlite access\n */\ninterface QueuedQuery {\n handlerId: number\n message: Uint8Array\n resolve: (result: Uint8Array) => void\n reject: (error: Error) => void\n timestamp: number\n}\n\n/**\n * Global query queue manager\n * Ensures only one query executes at a time in PGlite\n */\nclass QueryQueueManager {\n private queue: QueuedQuery[] = []\n private processing = false\n private db: PGlite\n private debug: boolean\n private lastHandlerId: null | number = null\n\n constructor(db: PGlite, debug = false) {\n this.db = db\n this.debug = debug\n }\n\n private log(message: string, ...args: any[]): void {\n if (this.debug) {\n console.log(`[QueryQueueManager] ${message}`, ...args)\n }\n }\n\n async enqueue(handlerId: number, message: Uint8Array): Promise<Uint8Array> {\n return new Promise((resolve, reject) => {\n const query: QueuedQuery = {\n handlerId,\n message,\n resolve,\n reject,\n timestamp: Date.now(),\n }\n\n this.queue.push(query)\n this.log(\n `enqueued query from handler #${handlerId}, queue size: ${this.queue.length}`,\n )\n\n // Process queue if not already processing\n if (!this.processing) {\n this.processQueue()\n }\n })\n }\n\n private async processQueue(): Promise<void> {\n if (this.processing || this.queue.length === 0) {\n return\n }\n\n this.processing = true\n\n while (this.queue.length > 0) {\n let query\n\n if (this.db.isInTransaction() && this.lastHandlerId) {\n const i = this.queue.findIndex(\n (q) => q.handlerId === this.lastHandlerId,\n )\n if (i === -1) {\n // we didn't find any other query from the same client!\n this.log(\n `transaction started, but no query from the same handler id found in queue`,\n this.lastHandlerId,\n )\n query = null\n } else {\n query = this.queue.splice(i, 1)[0]\n }\n } else {\n query = this.queue.shift()\n }\n if (!query) break\n\n const waitTime = Date.now() - query.timestamp\n this.log(\n `processing query from handler #${query.handlerId} (waited ${waitTime}ms)`,\n )\n\n try {\n // Execute the query with exclusive access to PGlite\n const result = await this.db.runExclusive(async () => {\n return await this.db.execProtocolRaw(query.message)\n })\n\n this.log(\n `query from handler #${query.handlerId} completed, ${result.length} bytes`,\n )\n this.lastHandlerId = query.handlerId\n query.resolve(result)\n } catch (error) {\n this.log(`query from handler #${query.handlerId} failed:`, error)\n query.reject(error as Error)\n }\n }\n\n this.processing = false\n this.log(`queue processing complete, queue length is`, this.queue.length)\n }\n\n getQueueLength(): number {\n return this.queue.length\n }\n\n clearQueueForHandler(handlerId: number): void {\n const before = this.queue.length\n this.queue = this.queue.filter((q) => {\n if (q.handlerId === handlerId) {\n q.reject(new Error('Handler disconnected'))\n return false\n }\n return true\n })\n const removed = before - this.queue.length\n if (removed > 0) {\n this.log(`cleared ${removed} queries for handler #${handlerId}`)\n }\n }\n\n async clearTransactionIfNeeded(handlerId: number): Promise<void> {\n if (this.db.isInTransaction() && this.lastHandlerId === handlerId) {\n await this.db.exec('ROLLBACK')\n this.lastHandlerId = null\n await this.processQueue()\n }\n }\n}\n\n/**\n * Options for creating a PGLiteSocketHandler\n */\nexport interface PGLiteSocketHandlerOptions {\n /** The query queue manager */\n queryQueue: QueryQueueManager\n /** Whether to close the socket when detached (default: false) */\n closeOnDetach?: boolean\n /** Print the incoming and outgoing data to the console in hex and ascii */\n inspect?: boolean\n /** Enable debug logging of method calls */\n debug?: boolean\n /** Idle timeout in ms (0 to disable, default: 0) */\n idleTimeout?: number\n}\n\n/**\n * Handler for a single socket connection to PGlite\n * Each connection can remain open and send multiple queries\n */\nexport class PGLiteSocketHandler extends EventTarget {\n private queryQueue: QueryQueueManager\n private socket: Socket | null = null\n private active = false\n private closeOnDetach: boolean\n private inspect: boolean\n private debug: boolean\n private readonly id: number\n private messageBuffer: Buffer = Buffer.alloc(0)\n private idleTimer?: NodeJS.Timeout\n private idleTimeout: number\n private lastActivityTime: number = Date.now()\n\n // Static counter for generating unique handler IDs\n private static nextHandlerId = 1\n\n constructor(options: PGLiteSocketHandlerOptions) {\n super()\n this.queryQueue = options.queryQueue\n this.closeOnDetach = options.closeOnDetach ?? false\n this.inspect = options.inspect ?? false\n this.debug = options.debug ?? false\n this.idleTimeout = options.idleTimeout ?? 0\n this.id = PGLiteSocketHandler.nextHandlerId++\n\n this.log('constructor: created new handler')\n }\n\n public get handlerId(): number {\n return this.id\n }\n\n private log(message: string, ...args: any[]): void {\n if (this.debug) {\n console.log(`[PGLiteSocketHandler#${this.id}] ${message}`, ...args)\n }\n }\n\n public async attach(socket: Socket): Promise<PGLiteSocketHandler> {\n this.log(\n `attach: attaching socket from ${socket.remoteAddress}:${socket.remotePort}`,\n )\n\n if (this.socket) {\n throw new Error('Socket already attached')\n }\n\n this.socket = socket\n this.active = true\n this.lastActivityTime = Date.now()\n\n // Set up socket options\n socket.setNoDelay(true)\n\n // Set up idle timeout if configured\n if (this.idleTimeout > 0) {\n this.resetIdleTimer()\n }\n\n // Setup event handlers\n this.log(`attach: setting up socket event handlers`)\n\n socket.on('data', (data) => {\n this.lastActivityTime = Date.now()\n this.resetIdleTimer()\n\n setImmediate(async () => {\n try {\n await this.handleData(data)\n } catch (err) {\n this.log('socket on data error: ', err)\n this.handleError(err as Error)\n }\n })\n })\n\n socket.on('error', (err) => {\n setImmediate(() => this.handleError(err))\n })\n\n socket.on('close', () => {\n setImmediate(() => this.handleClose())\n })\n\n this.log(`attach: socket handler ready`)\n return this\n }\n\n private resetIdleTimer(): void {\n if (this.idleTimeout <= 0) return\n\n if (this.idleTimer) {\n clearTimeout(this.idleTimer)\n }\n\n this.idleTimer = setTimeout(() => {\n const idleTime = Date.now() - this.lastActivityTime\n this.log(`idle timeout after ${idleTime}ms`)\n this.handleError(new Error('Idle timeout'))\n }, this.idleTimeout)\n }\n\n public async detach(close?: boolean): Promise<PGLiteSocketHandler> {\n this.log(`detach: detaching socket, close=${close ?? this.closeOnDetach}`)\n\n if (this.idleTimer) {\n clearTimeout(this.idleTimer)\n this.idleTimer = undefined\n }\n\n // Clear any pending queries for this handler\n this.queryQueue.clearQueueForHandler(this.id)\n\n await this.queryQueue.clearTransactionIfNeeded(this.id)\n\n if (!this.socket) {\n this.log(`detach: no socket attached, nothing to do`)\n return this\n }\n\n // Remove all listeners\n this.socket.removeAllListeners('data')\n this.socket.removeAllListeners('error')\n this.socket.removeAllListeners('close')\n\n // Close the socket if requested\n if (close ?? this.closeOnDetach) {\n if (this.socket.writable) {\n this.log(`detach: closing socket`)\n try {\n this.socket.end()\n this.socket.destroy()\n } catch (err) {\n this.log(`detach: error closing socket:`, err)\n }\n }\n }\n\n this.socket = null\n this.active = false\n this.messageBuffer = Buffer.alloc(0)\n\n this.log(`detach: handler cleaned up`)\n return this\n }\n\n public get isAttached(): boolean {\n return this.socket !== null\n }\n\n private async handleData(data: Buffer): Promise<number> {\n if (!this.socket || !this.active) {\n this.log(`handleData: no active socket, ignoring data`)\n return 0\n }\n\n this.log(`handleData: received ${data.length} bytes`)\n\n // Append to buffer for message reassembly\n this.messageBuffer = Buffer.concat([this.messageBuffer, data])\n\n // Print the incoming data to the console\n this.inspectData('incoming', data)\n\n try {\n let totalProcessed = 0\n\n while (this.messageBuffer.length > 0) {\n // Determine message length\n let messageLength = 0\n let isComplete = false\n\n // Handle startup message (no type byte, just length)\n if (this.messageBuffer.length >= 4) {\n const firstInt = this.messageBuffer.readInt32BE(0)\n\n if (this.messageBuffer.length >= 8) {\n const secondInt = this.messageBuffer.readInt32BE(4)\n // PostgreSQL 3.0 protocol version\n if (secondInt === 196608 || secondInt === 0x00030000) {\n messageLength = firstInt\n isComplete = this.messageBuffer.length >= messageLength\n }\n }\n\n // Regular message (type byte + length)\n if (!isComplete && this.messageBuffer.length >= 5) {\n const msgLength = this.messageBuffer.readInt32BE(1)\n messageLength = 1 + msgLength\n isComplete = this.messageBuffer.length >= messageLength\n }\n }\n\n if (!isComplete || messageLength === 0) {\n this.log(\n `handleData: incomplete message, buffering ${this.messageBuffer.length} bytes`,\n )\n break\n }\n\n // Extract and process complete message\n const message = this.messageBuffer.slice(0, messageLength)\n this.messageBuffer = this.messageBuffer.slice(messageLength)\n\n this.log(`handleData: processing message of ${message.length} bytes`)\n\n // Check if socket is still active before processing\n if (!this.active || !this.socket) {\n this.log(`handleData: socket no longer active, stopping processing`)\n break\n }\n\n // Queue the query for execution\n // This allows multiple connections to queue queries simultaneously\n const result = await this.queryQueue.enqueue(\n this.id,\n new Uint8Array(message),\n )\n\n this.log(`handleData: received ${result.length} bytes from PGlite`)\n\n // Print the outgoing data to the console\n this.inspectData('outgoing', result)\n\n // Send response if available\n if (\n result.length > 0 &&\n this.socket &&\n this.socket.writable &&\n this.active\n ) {\n await new Promise<number>((resolve, reject) => {\n this.log(`handleData: writing response to socket`)\n if (this.socket?.writable) {\n this.socket.write(Buffer.from(result), (err?: any) => {\n if (err) {\n this.log(`handleData: error writing to socket:`, err)\n reject(err)\n } else {\n this.log(`handleData: socket sent: ${result.length} bytes`)\n resolve(result.length)\n }\n })\n } else {\n this.log(`handleData: socket no longer writable`)\n resolve(0)\n }\n }).catch((writeErr) => {\n this.log(`handleData: failed to write to socket:`, writeErr)\n throw writeErr\n })\n }\n\n totalProcessed += result.length\n }\n\n // Emit data event with byte sizes\n this.dispatchEvent(\n new CustomEvent('data', {\n detail: { incoming: data.length, outgoing: totalProcessed },\n }),\n )\n\n return totalProcessed\n } catch (err) {\n this.log(`handleData: error processing data:`, err)\n throw err\n }\n }\n\n private handleError(err: Error): void {\n if (!this.active) {\n this.log(`handleError: handler not active, ignoring error`)\n return\n }\n\n // ECONNRESET is expected behavior when clients disconnect\n if (err.message?.includes('ECONNRESET')) {\n this.log(\n `handleError: client disconnected (ECONNRESET) - normal behavior`,\n )\n } else if (err.message?.includes('Idle timeout')) {\n this.log(`handleError: connection idle timeout`)\n } else {\n this.log(`handleError:`, err)\n }\n\n this.active = false\n\n // Emit error event\n this.dispatchEvent(new CustomEvent('error', { detail: err }))\n\n // Clean up\n this.detach(true)\n }\n\n private handleClose(): void {\n this.log(`handleClose: socket closed`)\n this.active = false\n this.dispatchEvent(new CustomEvent('close'))\n this.detach(false)\n }\n\n private inspectData(\n direction: 'incoming' | 'outgoing',\n data: Buffer | Uint8Array,\n ): void {\n if (!this.inspect) return\n console.log('-'.repeat(75))\n if (direction === 'incoming') {\n console.log('-> incoming', data.length, 'bytes')\n } else {\n console.log('<- outgoing', data.length, 'bytes')\n }\n\n for (let offset = 0; offset < data.length; offset += 16) {\n const chunkSize = Math.min(16, data.length - offset)\n\n let hexPart = ''\n for (let i = 0; i < 16; i++) {\n if (i < chunkSize) {\n const byte = data[offset + i]\n hexPart += byte.toString(16).padStart(2, '0') + ' '\n } else {\n hexPart += ' '\n }\n }\n\n let asciiPart = ''\n for (let i = 0; i < chunkSize; i++) {\n const byte = data[offset + i]\n asciiPart += byte >= 32 && byte <= 126 ? String.fromCharCode(byte) : '.'\n }\n\n console.log(\n `${offset.toString(16).padStart(8, '0')} ${hexPart} ${asciiPart}`,\n )\n }\n }\n}\n\n/**\n * Options for creating a PGLiteSocketServer\n */\nexport interface PGLiteSocketServerOptions {\n /** The PGlite database instance */\n db: PGlite\n /** The port to listen on (default: 5432) */\n port?: number\n /** The host to bind to (default: 127.0.0.1) */\n host?: string\n /** Unix socket path to bind to (default: undefined) */\n path?: string\n /** Print the incoming and outgoing data to the console in hex and ascii */\n inspect?: boolean\n /** Enable debug logging of method calls */\n debug?: boolean\n /** Idle timeout in ms (0 to disable, default: 0) */\n idleTimeout?: number\n /** Maximum concurrent connections (default: 100) */\n maxConnections?: number\n}\n\n/**\n * PGLite Socket Server with support for multiple concurrent connections\n * Connections remain open and queries are queued at the query level\n */\nexport class PGLiteSocketServer extends EventTarget {\n readonly db: PGlite\n private server: Server | null = null\n private port?: number\n private host?: string\n private path?: string\n private active = false\n private inspect: boolean\n private debug: boolean\n private idleTimeout: number\n private maxConnections: number\n private handlers: Set<PGLiteSocketHandler> = new Set()\n private queryQueue: QueryQueueManager\n\n constructor(options: PGLiteSocketServerOptions) {\n super()\n this.db = options.db\n if (options.path) {\n this.path = options.path\n } else {\n if (typeof options.port === 'number') {\n // Keep port undefined on port 0, will be set by the OS when we start the server.\n this.port = options.port ?? options.port\n } else {\n this.port = 5432\n }\n this.host = options.host || '127.0.0.1'\n }\n this.inspect = options.inspect ?? false\n this.debug = options.debug ?? false\n this.idleTimeout = options.idleTimeout ?? 0\n this.maxConnections = options.maxConnections ?? 1\n\n // Create the shared query queue\n this.queryQueue = new QueryQueueManager(this.db, this.debug)\n\n this.log(`constructor: created server on ${this.getServerConn()}`)\n this.log(`constructor: max connections: ${this.maxConnections}`)\n if (this.idleTimeout > 0) {\n this.log(`constructor: idle timeout: ${this.idleTimeout}ms`)\n }\n }\n\n private log(message: string, ...args: any[]): void {\n if (this.debug) {\n console.log(`[PGLiteSocketServer] ${message}`, ...args)\n }\n }\n\n public async start(): Promise<void> {\n this.log(`start: starting server on ${this.getServerConn()}`)\n\n if (this.server) {\n throw new Error('Socket server already started')\n }\n\n // Ensure PGlite is ready before accepting connections\n await this.db.waitReady\n\n this.active = true\n this.server = createServer((socket) => {\n setImmediate(() => this.handleConnection(socket))\n })\n\n this.server.maxConnections = this.maxConnections\n\n return new Promise<void>((resolve, reject) => {\n if (!this.server) return reject(new Error('Server not initialized'))\n\n this.server.on('error', (err) => {\n this.log(`start: server error:`, err)\n this.dispatchEvent(new CustomEvent('error', { detail: err }))\n if (!this.active) {\n reject(err)\n }\n })\n\n if (this.path) {\n this.server.listen(this.path, () => {\n this.log(`start: server listening on ${this.getServerConn()}`)\n this.dispatchEvent(\n new CustomEvent('listening', {\n detail: { path: this.path },\n }),\n )\n resolve()\n })\n } else {\n const server = this.server\n server.listen(this.port, this.host, () => {\n const address = server.address()\n // We are not using pipes, so return type should be AddressInfo\n if (address === null || typeof address !== 'object') {\n throw Error('Expected address info')\n }\n // Assign the new port number\n this.port = address.port\n this.log(`start: server listening on ${this.getServerConn()}`)\n this.dispatchEvent(\n new CustomEvent('listening', {\n detail: { port: this.port, host: this.host },\n }),\n )\n resolve()\n })\n }\n })\n }\n\n public getServerConn(): string {\n if (this.path) return this.path\n return `${this.host}:${this.port}`\n }\n\n public async stop(): Promise<void> {\n this.log(`stop: stopping server`)\n\n this.active = false\n\n // Detach all handlers\n this.log(`stop: detaching ${this.handlers.size} handlers`)\n for (const handler of this.handlers) {\n handler.detach(true)\n }\n this.handlers.clear()\n\n if (!this.server) {\n this.log(`stop: server not running, nothing to do`)\n return Promise.resolve()\n }\n\n return new Promise<void>((resolve) => {\n if (!this.server) return resolve()\n\n this.server.close(() => {\n this.log(`stop: server closed`)\n this.server = null\n this.dispatchEvent(new CustomEvent('close'))\n resolve()\n })\n })\n }\n\n private async handleConnection(socket: Socket): Promise<void> {\n const clientInfo = {\n clientAddress: socket.remoteAddress || 'unknown',\n clientPort: socket.remotePort || 0,\n }\n\n this.log(\n `handleConnection: new connection from ${clientInfo.clientAddress}:${clientInfo.clientPort}`,\n )\n this.log(\n `handleConnection: active connections: ${this.handlers.size}, queued queries: ${this.queryQueue.getQueueLength()}`,\n )\n\n if (!this.active) {\n this.log(`handleConnection: server not active, closing connection`)\n try {\n socket.end()\n } catch (err) {\n this.log(`handleConnection: error closing socket:`, err)\n }\n return\n }\n\n // Check connection limit\n if (this.handlers.size >= this.maxConnections) {\n this.log(`handleConnection: max connections reached, rejecting`)\n socket.write(Buffer.from('Too many connections\\n'))\n socket.end()\n return\n }\n\n // Create a new handler for this connection\n const handler = new PGLiteSocketHandler({\n queryQueue: this.queryQueue,\n closeOnDetach: true,\n inspect: this.inspect,\n debug: this.debug,\n idleTimeout: this.idleTimeout,\n })\n\n // Track this handler\n this.handlers.add(handler)\n\n // Handle errors\n handler.addEventListener('error', (event) => {\n const error = (event as CustomEvent<Error>).detail\n\n if (error?.message?.includes('ECONNRESET')) {\n this.log(\n `handler #${handler.handlerId}: client disconnected (ECONNRESET)`,\n )\n } else if (error?.message?.includes('Idle timeout')) {\n this.log(`handler #${handler.handlerId}: idle timeout`)\n } else {\n this.log(`handler #${handler.handlerId}: error:`, error)\n }\n })\n\n // Handle close event\n handler.addEventListener('close', () => {\n this.log(`handler #${handler.handlerId}: closed`)\n this.handlers.delete(handler)\n this.log(`handleConnection: active connections: ${this.handlers.size}`)\n })\n\n try {\n await handler.attach(socket)\n this.dispatchEvent(new CustomEvent('connection', { detail: clientInfo }))\n } catch (err) {\n this.log(`handleConnection: error attaching socket:`, err)\n this.handlers.delete(handler)\n this.dispatchEvent(new CustomEvent('error', { detail: err }))\n try {\n socket.end()\n } catch (closeErr) {\n this.log(`handleConnection: error closing socket:`, closeErr)\n }\n }\n }\n\n public getStats() {\n return {\n activeConnections: this.handlers.size,\n queuedQueries: this.queryQueue.getQueueLength(),\n maxConnections: this.maxConnections,\n }\n }\n}\n"],"mappings":"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","names":["createServer","CONNECTION_QUEUE_TIMEOUT","QueryQueueManager","db","debug","message","args","handlerId","resolve","reject","query","i","q","waitTime","result","error","before","removed","_PGLiteSocketHandler","options","socket","data","err","idleTime","close","totalProcessed","messageLength","isComplete","firstInt","secondInt","writeErr","direction","offset","chunkSize","hexPart","byte","asciiPart","PGLiteSocketHandler","PGLiteSocketServer","server","address","handler","clientInfo","event","closeErr"]}
+6
-0
# @electric-sql/pglite-socket
## 0.0.21
### Patch Changes
- 8a03647: Fix: Message buffering, connection handling, and concurrent connection support;
## 0.0.20

@@ -4,0 +10,0 @@

+2
-1

@@ -1,2 +0,3 @@

"use strict";var u=Object.defineProperty;var m=Object.getOwnPropertyDescriptor;var f=Object.getOwnPropertyNames;var b=Object.prototype.hasOwnProperty;var w=(s,o)=>{for(var e in o)u(s,e,{get:o[e],enumerable:!0})},k=(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=>k(u({},"__esModule",{value:!0}),s);var P={};w(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:(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.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"));if(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()});else{let i=this.server;i.listen(this.port,this.host,()=>{let n=i.address();if(n===null||typeof n!="object")throw Error("Expected address info");this.port=n.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(){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});
"use strict";var g=Object.defineProperty;var p=Object.getOwnPropertyDescriptor;var b=Object.getOwnPropertyNames;var y=Object.prototype.hasOwnProperty;var E=(l,s)=>{for(var e in s)g(l,e,{get:s[e],enumerable:!0})},w=(l,s,e,i)=>{if(s&&typeof s=="object"||typeof s=="function")for(let t of b(s))!y.call(l,t)&&t!==e&&g(l,t,{get:()=>s[t],enumerable:!(i=p(s,t))||i.enumerable});return l};var C=l=>w(g({},"__esModule",{value:!0}),l);var q={};E(q,{CONNECTION_QUEUE_TIMEOUT:()=>T,PGLiteSocketHandler:()=>c,PGLiteSocketServer:()=>f});module.exports=C(q);var v=require("net"),T=6e4,m=class{constructor(s,e=!1){this.queue=[];this.processing=!1;this.lastHandlerId=null;this.db=s,this.debug=e}log(s,...e){this.debug&&console.log(`[QueryQueueManager] ${s}`,...e)}async enqueue(s,e){return new Promise((i,t)=>{let r={handlerId:s,message:e,resolve:i,reject:t,timestamp:Date.now()};this.queue.push(r),this.log(`enqueued query from handler #${s}, 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 s;if(this.db.isInTransaction()&&this.lastHandlerId){let i=this.queue.findIndex(t=>t.handlerId===this.lastHandlerId);i===-1?(this.log("transaction started, but no query from the same handler id found in queue",this.lastHandlerId),s=null):s=this.queue.splice(i,1)[0]}else s=this.queue.shift();if(!s)break;let e=Date.now()-s.timestamp;this.log(`processing query from handler #${s.handlerId} (waited ${e}ms)`);try{let i=await this.db.runExclusive(async()=>await this.db.execProtocolRaw(s.message));this.log(`query from handler #${s.handlerId} completed, ${i.length} bytes`),this.lastHandlerId=s.handlerId,s.resolve(i)}catch(i){this.log(`query from handler #${s.handlerId} failed:`,i),s.reject(i)}}this.processing=!1,this.log("queue processing complete, queue length is",this.queue.length)}}getQueueLength(){return this.queue.length}clearQueueForHandler(s){let e=this.queue.length;this.queue=this.queue.filter(t=>t.handlerId===s?(t.reject(new Error("Handler disconnected")),!1):!0);let i=e-this.queue.length;i>0&&this.log(`cleared ${i} queries for handler #${s}`)}async clearTransactionIfNeeded(s){this.db.isInTransaction()&&this.lastHandlerId===s&&(await this.db.exec("ROLLBACK"),this.lastHandlerId=null,await this.processQueue())}},d=class d 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=d.nextHandlerId++,this.log("constructor: created new handler")}get handlerId(){return this.id}log(e,...i){this.debug&&console.log(`[PGLiteSocketHandler#${this.id}] ${e}`,...i)}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",i=>{this.lastActivityTime=Date.now(),this.resetIdleTimer(),setImmediate(async()=>{try{await this.handleData(i)}catch(t){this.log("socket on data error: ",t),this.handleError(t)}})}),e.on("error",i=>{setImmediate(()=>this.handleError(i))}),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(i){this.log("detach: error closing socket:",i)}}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 i=0;for(;this.messageBuffer.length>0;){let t=0,r=!1;if(this.messageBuffer.length>=4){let n=this.messageBuffer.readInt32BE(0);if(this.messageBuffer.length>=8){let a=this.messageBuffer.readInt32BE(4);(a===196608||a===196608)&&(t=n,r=this.messageBuffer.length>=t)}!r&&this.messageBuffer.length>=5&&(t=1+this.messageBuffer.readInt32BE(1),r=this.messageBuffer.length>=t)}if(!r||t===0){this.log(`handleData: incomplete message, buffering ${this.messageBuffer.length} bytes`);break}let o=this.messageBuffer.slice(0,t);if(this.messageBuffer=this.messageBuffer.slice(t),this.log(`handleData: processing message of ${o.length} bytes`),!this.active||!this.socket){this.log("handleData: socket no longer active, stopping processing");break}let h=await this.queryQueue.enqueue(this.id,new Uint8Array(o));this.log(`handleData: received ${h.length} bytes from PGlite`),this.inspectData("outgoing",h),h.length>0&&this.socket&&this.socket.writable&&this.active&&await new Promise((n,a)=>{this.log("handleData: writing response to socket"),this.socket?.writable?this.socket.write(Buffer.from(h),u=>{u?(this.log("handleData: error writing to socket:",u),a(u)):(this.log(`handleData: socket sent: ${h.length} bytes`),n(h.length))}):(this.log("handleData: socket no longer writable"),n(0))}).catch(n=>{throw this.log("handleData: failed to write to socket:",n),n}),i+=h.length}return this.dispatchEvent(new CustomEvent("data",{detail:{incoming:e.length,outgoing:i}})),i}catch(i){throw this.log("handleData: error processing data:",i),i}}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,i){if(this.inspect){console.log("-".repeat(75)),console.log(e==="incoming"?"-> incoming":"<- outgoing",i.length,"bytes");for(let t=0;t<i.length;t+=16){let r=Math.min(16,i.length-t),o="";for(let n=0;n<16;n++)if(n<r){let a=i[t+n];o+=a.toString(16).padStart(2,"0")+" "}else o+=" ";let h="";for(let n=0;n<r;n++){let a=i[t+n];h+=a>=32&&a<=126?String.fromCharCode(a):"."}console.log(`${t.toString(16).padStart(8,"0")} ${o} ${h}`)}}}};d.nextHandlerId=1;var c=d,f=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,...i){this.debug&&console.log(`[PGLiteSocketServer] ${e}`,...i)}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,i)=>{if(!this.server)return i(new Error("Server not initialized"));if(this.server.on("error",t=>{this.log("start: server error:",t),this.dispatchEvent(new CustomEvent("error",{detail:t})),this.active||i(t)}),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 t=this.server;t.listen(this.port,this.host,()=>{let r=t.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 i={clientAddress:e.remoteAddress||"unknown",clientPort:e.remotePort||0};if(this.log(`handleConnection: new connection from ${i.clientAddress}:${i.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 t=new c({queryQueue:this.queryQueue,closeOnDetach:!0,inspect:this.inspect,debug:this.debug,idleTimeout:this.idleTimeout});this.handlers.add(t),t.addEventListener("error",r=>{let o=r.detail;o?.message?.includes("ECONNRESET")?this.log(`handler #${t.handlerId}: client disconnected (ECONNRESET)`):o?.message?.includes("Idle timeout")?this.log(`handler #${t.handlerId}: idle timeout`):this.log(`handler #${t.handlerId}: error:`,o)}),t.addEventListener("close",()=>{this.log(`handler #${t.handlerId}: closed`),this.handlers.delete(t),this.log(`handleConnection: active connections: ${this.handlers.size}`)});try{await t.attach(e),this.dispatchEvent(new CustomEvent("connection",{detail:i}))}catch(r){this.log("handleConnection: error attaching socket:",r),this.handlers.delete(t),this.dispatchEvent(new CustomEvent("error",{detail:r}));try{e.end()}catch(o){this.log("handleConnection: error closing socket:",o)}}}getStats(){return{activeConnections:this.handlers.size,queuedQueries:this.queryQueue.getQueueLength(),maxConnections:this.maxConnections}}};0&&(module.exports={CONNECTION_QUEUE_TIMEOUT,PGLiteSocketHandler,PGLiteSocketServer});
//# sourceMappingURL=index.cjs.map

@@ -1,1 +0,1 @@

{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { PGlite } from '@electric-sql/pglite'\nimport { createServer, Server, Socket } from 'net'\n\n// Connection queue timeout in milliseconds\nexport const CONNECTION_QUEUE_TIMEOUT = 60000 // 60 seconds\n\n/**\n * Options for creating a PGLiteSocketHandler\n */\nexport interface PGLiteSocketHandlerOptions {\n /** The PGlite database instance */\n db: PGlite\n /** Whether to close the socket when detached (default: false) */\n closeOnDetach?: boolean\n /** Print the incoming and outgoing data to the console in hex and ascii */\n inspect?: boolean\n /** Enable debug logging of method calls */\n debug?: boolean\n}\n\n/**\n * Low-level handler for a single socket connection to PGLite\n * Handles the raw protocol communication between a socket and PGLite\n */\nexport class PGLiteSocketHandler extends EventTarget {\n readonly db: PGlite\n private socket: Socket | null = null\n private active = false\n private closeOnDetach: boolean\n private resolveLock?: () => void\n private rejectLock?: (err: Error) => void\n private inspect: boolean\n private debug: boolean\n private readonly id: number\n\n // Static counter for generating unique handler IDs\n private static nextHandlerId = 1\n\n /**\n * Create a new PGLiteSocketHandler\n * @param options Options for the handler\n */\n constructor(options: PGLiteSocketHandlerOptions) {\n super()\n this.db = options.db\n this.closeOnDetach = options.closeOnDetach ?? false\n this.inspect = options.inspect ?? false\n this.debug = options.debug ?? false\n this.id = PGLiteSocketHandler.nextHandlerId++\n\n this.log('constructor: created new handler')\n }\n\n /**\n * Get the unique ID of this handler\n */\n public get handlerId(): number {\n return this.id\n }\n\n /**\n * Log a message if debug is enabled\n * @private\n */\n private log(message: string, ...args: any[]): void {\n if (this.debug) {\n console.log(`[PGLiteSocketHandler#${this.id}] ${message}`, ...args)\n }\n }\n\n /**\n * Attach a socket to this handler\n * @param socket The socket to attach\n * @returns this handler instance\n * @throws Error if a socket is already attached\n */\n public async attach(socket: Socket): Promise<PGLiteSocketHandler> {\n this.log(\n `attach: attaching socket from ${socket.remoteAddress}:${socket.remotePort}`,\n )\n\n if (this.socket) {\n throw new Error('Socket already attached')\n }\n\n this.socket = socket\n this.active = true\n\n // Ensure the PGlite instance is ready\n this.log(`attach: waiting for PGlite to be ready`)\n await this.db.waitReady\n\n // Hold the lock on the PGlite instance\n this.log(`attach: acquiring exclusive lock on PGlite instance`)\n await new Promise<void>((resolve) => {\n this.db.runExclusive(() => {\n // Ensure we have the lock on the PGlite instance\n resolve()\n\n // Use a promise to hold the lock on the PGlite instance\n // this can be resolved or rejected by the handler to release the lock\n return new Promise<void>((resolveLock, rejectLock) => {\n this.resolveLock = resolveLock\n this.rejectLock = rejectLock\n })\n })\n })\n\n // Setup event handlers\n this.log(`attach: setting up socket event handlers`)\n socket.on('data', async (data) => {\n try {\n const result = await this.handleData(data)\n this.log(`socket on data sent: ${result} bytes`)\n } catch (err) {\n this.log('socket on data error: ', err)\n }\n })\n socket.on('error', (err) => this.handleError(err))\n socket.on('close', () => this.handleClose())\n\n return this\n }\n\n /**\n * Detach the current socket from this handler\n * @param close Whether to close the socket when detaching (overrides constructor option)\n * @returns this handler instance\n */\n public detach(close?: boolean): PGLiteSocketHandler {\n this.log(`detach: detaching socket, close=${close ?? this.closeOnDetach}`)\n\n if (!this.socket) {\n this.log(`detach: no socket attached, nothing to do`)\n return this\n }\n\n // Remove all listeners\n this.socket.removeAllListeners('data')\n this.socket.removeAllListeners('error')\n this.socket.removeAllListeners('close')\n\n // Close the socket if requested\n if (close ?? this.closeOnDetach) {\n if (this.socket.writable) {\n this.log(`detach: closing socket`)\n this.socket.end()\n this.socket.destroy()\n }\n }\n\n // Release the lock on the PGlite instance\n this.log(`detach: releasing exclusive lock on PGlite instance`)\n this.resolveLock?.()\n\n this.socket = null\n this.active = false\n return this\n }\n\n /**\n * Check if a socket is currently attached\n */\n public get isAttached(): boolean {\n return this.socket !== null\n }\n\n /**\n * Handle incoming data from the socket\n */\n private async handleData(data: Buffer): Promise<number> {\n if (!this.socket || !this.active) {\n this.log(`handleData: no active socket, ignoring data`)\n return new Promise((_, reject) => reject(`no active socket`))\n }\n\n this.log(`handleData: received ${data.length} bytes`)\n\n // Print the incoming data to the console\n this.inspectData('incoming', data)\n\n try {\n // Process the raw protocol data\n this.log(`handleData: sending data to PGlite for processing`)\n const result = await this.db.execProtocolRaw(new Uint8Array(data))\n\n this.log(`handleData: received ${result.length} bytes from PGlite`)\n\n // Print the outgoing data to the console\n this.inspectData('outgoing', result)\n\n // Send the result back if the socket is still connected\n if (this.socket && this.socket.writable && this.active) {\n if (result.length <= 0) {\n this.log(`handleData: cowardly refusing to send empty packet`)\n return new Promise((_, reject) => reject('no data'))\n }\n\n const promise = new Promise<number>((resolve, reject) => {\n this.log(`handleData: writing response to socket`)\n if (this.socket) {\n this.socket.write(Buffer.from(result), (err?: Error) => {\n if (err) {\n reject(`Error while writing to the socket ${err.toString()}`)\n } else {\n resolve(result.length)\n }\n })\n } else {\n reject(`No socket`)\n }\n })\n\n // Emit data event with byte sizes\n this.dispatchEvent(\n new CustomEvent('data', {\n detail: { incoming: data.length, outgoing: result.length },\n }),\n )\n return promise\n } else {\n this.log(\n `handleData: socket no longer writable or active, discarding response`,\n )\n return new Promise((_, reject) =>\n reject(`No socket, not active or not writeable`),\n )\n }\n } catch (err) {\n this.log(`handleData: error processing data:`, err)\n this.handleError(err as Error)\n return new Promise((_, reject) =>\n reject(`Error while processing data ${(err as Error).toString()}`),\n )\n }\n }\n\n /**\n * Handle errors from the socket\n */\n private handleError(err: Error): void {\n this.log(`handleError:`, err)\n\n // Emit error event\n this.dispatchEvent(new CustomEvent('error', { detail: err }))\n\n // Reject the lock on the PGlite instance\n this.log(`handleError: rejecting exclusive lock on PGlite instance`)\n this.rejectLock?.(err)\n this.resolveLock = undefined\n this.rejectLock = undefined\n\n // Close the connection on error\n this.detach(true)\n }\n\n /**\n * Handle socket close event\n */\n private handleClose(): void {\n this.log(`handleClose: socket closed`)\n\n this.dispatchEvent(new CustomEvent('close'))\n this.detach(false) // Already closed, just clean up\n }\n\n /**\n * Print data in hex and ascii to the console\n */\n private inspectData(\n direction: 'incoming' | 'outgoing',\n data: Buffer | Uint8Array,\n ): void {\n if (!this.inspect) return\n console.log('-'.repeat(75))\n if (direction === 'incoming') {\n console.log('-> incoming', data.length, 'bytes')\n } else {\n console.log('<- outgoing', data.length, 'bytes')\n }\n\n // Process 16 bytes per line\n for (let offset = 0; offset < data.length; offset += 16) {\n // Calculate current chunk size (may be less than 16 for the last chunk)\n const chunkSize = Math.min(16, data.length - offset)\n\n // Build the hex representation\n let hexPart = ''\n for (let i = 0; i < 16; i++) {\n if (i < chunkSize) {\n const byte = data[offset + i]\n hexPart += byte.toString(16).padStart(2, '0') + ' '\n } else {\n hexPart += ' ' // 3 spaces for missing bytes\n }\n }\n\n // Build the ASCII representation\n let asciiPart = ''\n for (let i = 0; i < chunkSize; i++) {\n const byte = data[offset + i]\n // Use printable characters (32-126), replace others with a dot\n asciiPart += byte >= 32 && byte <= 126 ? String.fromCharCode(byte) : '.'\n }\n\n // Print the line with offset in hex, hex values, and ASCII representation\n console.log(\n `${offset.toString(16).padStart(8, '0')} ${hexPart} ${asciiPart}`,\n )\n }\n }\n}\n\n/**\n * Represents a queued connection with timeout\n */\ninterface QueuedConnection {\n socket: Socket\n clientInfo: {\n clientAddress: string\n clientPort: number\n }\n timeoutId: NodeJS.Timeout\n}\n\n/**\n * Options for creating a PGLiteSocketServer\n */\nexport interface PGLiteSocketServerOptions {\n /** The PGlite database instance */\n db: PGlite\n /** The port to listen on (default: 5432) */\n port?: number\n /** The host to bind to (default: 127.0.0.1) */\n host?: string\n /** Unix socket path to bind to (default: undefined). If specified, takes precedence over host:port */\n path?: string\n /** Print the incoming and outgoing data to the console in hex and ascii */\n inspect?: boolean\n /** Connection queue timeout in milliseconds (default: 10000) */\n connectionQueueTimeout?: number\n /** Enable debug logging of method calls */\n debug?: boolean\n}\n\n/**\n * High-level server that manages socket connections to PGLite\n * Creates and manages a TCP server and handles client connections\n */\nexport class PGLiteSocketServer extends EventTarget {\n readonly db: PGlite\n private server: Server | null = null\n private port?: number\n private host?: string\n private path?: string\n private active = false\n private inspect: boolean\n private debug: boolean\n private connectionQueueTimeout: number\n private activeHandler: PGLiteSocketHandler | null = null\n private connectionQueue: QueuedConnection[] = []\n private handlerCount: number = 0\n\n /**\n * Create a new PGLiteSocketServer\n * @param options Options for the server\n */\n constructor(options: PGLiteSocketServerOptions) {\n super()\n this.db = options.db\n if (options.path) {\n this.path = options.path\n } else {\n if (typeof options.port === 'number') {\n // Keep port undefined on port 0, will be set by the OS when we start the server.\n this.port = options.port ?? options.port\n } else {\n this.port = 5432\n }\n this.host = options.host || '127.0.0.1'\n }\n this.inspect = options.inspect ?? false\n this.debug = options.debug ?? false\n this.connectionQueueTimeout =\n options.connectionQueueTimeout ?? CONNECTION_QUEUE_TIMEOUT\n\n this.log(`constructor: created server on ${this.host}:${this.port}`)\n this.log(\n `constructor: connection queue timeout: ${this.connectionQueueTimeout}ms`,\n )\n }\n\n /**\n * Log a message if debug is enabled\n * @private\n */\n private log(message: string, ...args: any[]): void {\n if (this.debug) {\n console.log(`[PGLiteSocketServer] ${message}`, ...args)\n }\n }\n\n /**\n * Start the socket server\n * @returns Promise that resolves when the server is listening\n */\n public async start(): Promise<void> {\n this.log(`start: starting server on ${this.getServerConn()}`)\n\n if (this.server) {\n throw new Error('Socket server already started')\n }\n\n this.active = true\n this.server = createServer((socket) => this.handleConnection(socket))\n\n return new Promise<void>((resolve, reject) => {\n if (!this.server) return reject(new Error('Server not initialized'))\n\n this.server.on('error', (err) => {\n this.log(`start: server error:`, err)\n this.dispatchEvent(new CustomEvent('error', { detail: err }))\n reject(err)\n })\n\n if (this.path) {\n this.server.listen(this.path, () => {\n this.log(`start: server listening on ${this.getServerConn()}`)\n this.dispatchEvent(\n new CustomEvent('listening', {\n detail: { path: this.path },\n }),\n )\n resolve()\n })\n } else {\n const server = this.server\n server.listen(this.port, this.host, () => {\n const address = server.address()\n // We are not using pipes, so return type should be AddressInfo\n if (address === null || typeof address !== 'object') {\n throw Error('Expected address info')\n }\n // Assign the new port number\n this.port = address.port\n this.log(`start: server listening on ${this.getServerConn()}`)\n this.dispatchEvent(\n new CustomEvent('listening', {\n detail: { port: this.port, host: this.host },\n }),\n )\n resolve()\n })\n }\n })\n }\n\n public getServerConn(): string {\n if (this.path) return this.path\n return `${this.host}:${this.port}`\n }\n\n /**\n * Stop the socket server\n * @returns Promise that resolves when the server is closed\n */\n public async stop(): Promise<void> {\n this.log(`stop: stopping server`)\n\n this.active = false\n\n // Clear connection queue\n this.log(\n `stop: clearing connection queue (${this.connectionQueue.length} connections)`,\n )\n\n this.connectionQueue.forEach((queuedConn) => {\n clearTimeout(queuedConn.timeoutId)\n if (queuedConn.socket.writable) {\n this.log(\n `stop: closing queued connection from ${queuedConn.clientInfo.clientAddress}:${queuedConn.clientInfo.clientPort}`,\n )\n queuedConn.socket.end()\n }\n })\n this.connectionQueue = []\n\n // Detach active handler if exists\n if (this.activeHandler) {\n this.log(`stop: detaching active handler #${this.activeHandlerId}`)\n this.activeHandler.detach(true)\n this.activeHandler = null\n }\n\n if (!this.server) {\n this.log(`stop: server not running, nothing to do`)\n return Promise.resolve()\n }\n\n return new Promise<void>((resolve) => {\n if (!this.server) return resolve()\n\n this.server.close(() => {\n this.log(`stop: server closed`)\n this.server = null\n this.dispatchEvent(new CustomEvent('close'))\n resolve()\n })\n })\n }\n\n /**\n * Get the active handler ID, or null if no active handler\n */\n private get activeHandlerId(): number | null {\n return this.activeHandler?.handlerId ?? null\n }\n\n /**\n * Handle a new client connection\n */\n private async handleConnection(socket: Socket): Promise<void> {\n const clientInfo = {\n clientAddress: socket.remoteAddress || 'unknown',\n clientPort: socket.remotePort || 0,\n }\n\n this.log(\n `handleConnection: new connection from ${clientInfo.clientAddress}:${clientInfo.clientPort}`,\n )\n\n // If server is not active, close the connection immediately\n if (!this.active) {\n this.log(`handleConnection: server not active, closing connection`)\n socket.end()\n return\n }\n\n // If we don't have an active handler or it's not attached, we can use this connection immediately\n if (!this.activeHandler || !this.activeHandler.isAttached) {\n this.log(`handleConnection: no active handler, attaching socket directly`)\n this.dispatchEvent(new CustomEvent('connection', { detail: clientInfo }))\n await this.attachSocketToNewHandler(socket, clientInfo)\n return\n }\n\n // Otherwise, queue the connection\n this.log(\n `handleConnection: active handler #${this.activeHandlerId} exists, queueing connection`,\n )\n this.enqueueConnection(socket, clientInfo)\n }\n\n /**\n * Add a connection to the queue\n */\n private enqueueConnection(\n socket: Socket,\n clientInfo: { clientAddress: string; clientPort: number },\n ): void {\n this.log(\n `enqueueConnection: queueing connection from ${clientInfo.clientAddress}:${clientInfo.clientPort}, timeout: ${this.connectionQueueTimeout}ms`,\n )\n\n // Set a timeout for this queued connection\n const timeoutId = setTimeout(() => {\n this.log(\n `enqueueConnection: timeout for connection from ${clientInfo.clientAddress}:${clientInfo.clientPort}`,\n )\n\n // Remove from queue\n this.connectionQueue = this.connectionQueue.filter(\n (queuedConn) => queuedConn.socket !== socket,\n )\n\n // End the connection if it's still open\n if (socket.writable) {\n this.log(`enqueueConnection: closing timed out connection`)\n socket.end()\n }\n\n this.dispatchEvent(\n new CustomEvent('queueTimeout', {\n detail: { ...clientInfo, queueSize: this.connectionQueue.length },\n }),\n )\n }, this.connectionQueueTimeout)\n\n // Add to queue\n this.connectionQueue.push({ socket, clientInfo, timeoutId })\n\n this.log(\n `enqueueConnection: connection queued, queue size: ${this.connectionQueue.length}`,\n )\n\n this.dispatchEvent(\n new CustomEvent('queuedConnection', {\n detail: { ...clientInfo, queueSize: this.connectionQueue.length },\n }),\n )\n }\n\n /**\n * Process the next connection in the queue\n */\n private processNextInQueue(): void {\n this.log(\n `processNextInQueue: processing next connection, queue size: ${this.connectionQueue.length}`,\n )\n\n // No connections in queue or server not active\n if (this.connectionQueue.length === 0 || !this.active) {\n this.log(\n `processNextInQueue: no connections in queue or server not active, nothing to do`,\n )\n return\n }\n\n // Get the next connection\n const nextConn = this.connectionQueue.shift()\n if (!nextConn) return\n\n this.log(\n `processNextInQueue: processing connection from ${nextConn.clientInfo.clientAddress}:${nextConn.clientInfo.clientPort}`,\n )\n\n // Clear the timeout\n clearTimeout(nextConn.timeoutId)\n\n // Check if the socket is still valid\n if (!nextConn.socket.writable) {\n this.log(\n `processNextInQueue: socket no longer writable, skipping to next connection`,\n )\n // Socket closed while waiting, process next in queue\n this.processNextInQueue()\n return\n }\n\n // Attach this socket to a new handler\n this.attachSocketToNewHandler(nextConn.socket, nextConn.clientInfo).catch(\n (err) => {\n this.log(`processNextInQueue: error attaching socket:`, err)\n this.dispatchEvent(new CustomEvent('error', { detail: err }))\n // Try the next connection\n this.processNextInQueue()\n },\n )\n }\n\n /**\n * Attach a socket to a new handler\n */\n private async attachSocketToNewHandler(\n socket: Socket,\n clientInfo: { clientAddress: string; clientPort: number },\n ): Promise<void> {\n this.handlerCount++\n\n this.log(\n `attachSocketToNewHandler: creating new handler for ${clientInfo.clientAddress}:${clientInfo.clientPort} (handler #${this.handlerCount})`,\n )\n\n // Create a new handler for this connection\n const handler = new PGLiteSocketHandler({\n db: this.db,\n closeOnDetach: true,\n inspect: this.inspect,\n debug: this.debug,\n })\n\n // Forward error events from the handler\n handler.addEventListener('error', (event) => {\n this.log(\n `handler #${handler.handlerId}: error from handler:`,\n (event as CustomEvent<Error>).detail,\n )\n this.dispatchEvent(\n new CustomEvent('error', {\n detail: (event as CustomEvent<Error>).detail,\n }),\n )\n })\n\n // Handle close event to process next queued connection\n handler.addEventListener('close', () => {\n this.log(`handler #${handler.handlerId}: closed`)\n\n // If this is our active handler, clear it\n if (this.activeHandler === handler) {\n this.log(\n `handler #${handler.handlerId}: was active handler, processing next connection in queue`,\n )\n this.activeHandler = null\n // Process next connection in queue\n this.processNextInQueue()\n }\n })\n\n try {\n // Set as active handler\n this.activeHandler = handler\n\n this.log(`handler #${handler.handlerId}: attaching socket`)\n\n // Attach the socket to the handler\n await handler.attach(socket)\n\n this.dispatchEvent(new CustomEvent('connection', { detail: clientInfo }))\n } catch (err) {\n // If there was an error attaching, clean up\n this.log(`handler #${handler.handlerId}: error attaching socket:`, err)\n this.activeHandler = null\n if (socket.writable) {\n socket.end()\n }\n throw err\n }\n }\n}\n"],"mappings":"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","names":["src_exports","__export","CONNECTION_QUEUE_TIMEOUT","PGLiteSocketHandler","PGLiteSocketServer","__toCommonJS","import_net","CONNECTION_QUEUE_TIMEOUT","_PGLiteSocketHandler","options","message","args","socket","resolve","resolveLock","rejectLock","data","result","err","close","_","reject","promise","direction","offset","chunkSize","hexPart","i","byte","asciiPart","PGLiteSocketHandler","PGLiteSocketServer","server","address","queuedConn","clientInfo","timeoutId","nextConn","handler","event"]}
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { PGlite } from '@electric-sql/pglite'\nimport { type Server, type Socket, createServer } from 'net'\n\n// Connection queue timeout in milliseconds\nexport const CONNECTION_QUEUE_TIMEOUT = 60000 // 60 seconds\n\n/**\n * Represents a queued query waiting for PGlite access\n */\ninterface QueuedQuery {\n handlerId: number\n message: Uint8Array\n resolve: (result: Uint8Array) => void\n reject: (error: Error) => void\n timestamp: number\n}\n\n/**\n * Global query queue manager\n * Ensures only one query executes at a time in PGlite\n */\nclass QueryQueueManager {\n private queue: QueuedQuery[] = []\n private processing = false\n private db: PGlite\n private debug: boolean\n private lastHandlerId: null | number = null\n\n constructor(db: PGlite, debug = false) {\n this.db = db\n this.debug = debug\n }\n\n private log(message: string, ...args: any[]): void {\n if (this.debug) {\n console.log(`[QueryQueueManager] ${message}`, ...args)\n }\n }\n\n async enqueue(handlerId: number, message: Uint8Array): Promise<Uint8Array> {\n return new Promise((resolve, reject) => {\n const query: QueuedQuery = {\n handlerId,\n message,\n resolve,\n reject,\n timestamp: Date.now(),\n }\n\n this.queue.push(query)\n this.log(\n `enqueued query from handler #${handlerId}, queue size: ${this.queue.length}`,\n )\n\n // Process queue if not already processing\n if (!this.processing) {\n this.processQueue()\n }\n })\n }\n\n private async processQueue(): Promise<void> {\n if (this.processing || this.queue.length === 0) {\n return\n }\n\n this.processing = true\n\n while (this.queue.length > 0) {\n let query\n\n if (this.db.isInTransaction() && this.lastHandlerId) {\n const i = this.queue.findIndex(\n (q) => q.handlerId === this.lastHandlerId,\n )\n if (i === -1) {\n // we didn't find any other query from the same client!\n this.log(\n `transaction started, but no query from the same handler id found in queue`,\n this.lastHandlerId,\n )\n query = null\n } else {\n query = this.queue.splice(i, 1)[0]\n }\n } else {\n query = this.queue.shift()\n }\n if (!query) break\n\n const waitTime = Date.now() - query.timestamp\n this.log(\n `processing query from handler #${query.handlerId} (waited ${waitTime}ms)`,\n )\n\n try {\n // Execute the query with exclusive access to PGlite\n const result = await this.db.runExclusive(async () => {\n return await this.db.execProtocolRaw(query.message)\n })\n\n this.log(\n `query from handler #${query.handlerId} completed, ${result.length} bytes`,\n )\n this.lastHandlerId = query.handlerId\n query.resolve(result)\n } catch (error) {\n this.log(`query from handler #${query.handlerId} failed:`, error)\n query.reject(error as Error)\n }\n }\n\n this.processing = false\n this.log(`queue processing complete, queue length is`, this.queue.length)\n }\n\n getQueueLength(): number {\n return this.queue.length\n }\n\n clearQueueForHandler(handlerId: number): void {\n const before = this.queue.length\n this.queue = this.queue.filter((q) => {\n if (q.handlerId === handlerId) {\n q.reject(new Error('Handler disconnected'))\n return false\n }\n return true\n })\n const removed = before - this.queue.length\n if (removed > 0) {\n this.log(`cleared ${removed} queries for handler #${handlerId}`)\n }\n }\n\n async clearTransactionIfNeeded(handlerId: number): Promise<void> {\n if (this.db.isInTransaction() && this.lastHandlerId === handlerId) {\n await this.db.exec('ROLLBACK')\n this.lastHandlerId = null\n await this.processQueue()\n }\n }\n}\n\n/**\n * Options for creating a PGLiteSocketHandler\n */\nexport interface PGLiteSocketHandlerOptions {\n /** The query queue manager */\n queryQueue: QueryQueueManager\n /** Whether to close the socket when detached (default: false) */\n closeOnDetach?: boolean\n /** Print the incoming and outgoing data to the console in hex and ascii */\n inspect?: boolean\n /** Enable debug logging of method calls */\n debug?: boolean\n /** Idle timeout in ms (0 to disable, default: 0) */\n idleTimeout?: number\n}\n\n/**\n * Handler for a single socket connection to PGlite\n * Each connection can remain open and send multiple queries\n */\nexport class PGLiteSocketHandler extends EventTarget {\n private queryQueue: QueryQueueManager\n private socket: Socket | null = null\n private active = false\n private closeOnDetach: boolean\n private inspect: boolean\n private debug: boolean\n private readonly id: number\n private messageBuffer: Buffer = Buffer.alloc(0)\n private idleTimer?: NodeJS.Timeout\n private idleTimeout: number\n private lastActivityTime: number = Date.now()\n\n // Static counter for generating unique handler IDs\n private static nextHandlerId = 1\n\n constructor(options: PGLiteSocketHandlerOptions) {\n super()\n this.queryQueue = options.queryQueue\n this.closeOnDetach = options.closeOnDetach ?? false\n this.inspect = options.inspect ?? false\n this.debug = options.debug ?? false\n this.idleTimeout = options.idleTimeout ?? 0\n this.id = PGLiteSocketHandler.nextHandlerId++\n\n this.log('constructor: created new handler')\n }\n\n public get handlerId(): number {\n return this.id\n }\n\n private log(message: string, ...args: any[]): void {\n if (this.debug) {\n console.log(`[PGLiteSocketHandler#${this.id}] ${message}`, ...args)\n }\n }\n\n public async attach(socket: Socket): Promise<PGLiteSocketHandler> {\n this.log(\n `attach: attaching socket from ${socket.remoteAddress}:${socket.remotePort}`,\n )\n\n if (this.socket) {\n throw new Error('Socket already attached')\n }\n\n this.socket = socket\n this.active = true\n this.lastActivityTime = Date.now()\n\n // Set up socket options\n socket.setNoDelay(true)\n\n // Set up idle timeout if configured\n if (this.idleTimeout > 0) {\n this.resetIdleTimer()\n }\n\n // Setup event handlers\n this.log(`attach: setting up socket event handlers`)\n\n socket.on('data', (data) => {\n this.lastActivityTime = Date.now()\n this.resetIdleTimer()\n\n setImmediate(async () => {\n try {\n await this.handleData(data)\n } catch (err) {\n this.log('socket on data error: ', err)\n this.handleError(err as Error)\n }\n })\n })\n\n socket.on('error', (err) => {\n setImmediate(() => this.handleError(err))\n })\n\n socket.on('close', () => {\n setImmediate(() => this.handleClose())\n })\n\n this.log(`attach: socket handler ready`)\n return this\n }\n\n private resetIdleTimer(): void {\n if (this.idleTimeout <= 0) return\n\n if (this.idleTimer) {\n clearTimeout(this.idleTimer)\n }\n\n this.idleTimer = setTimeout(() => {\n const idleTime = Date.now() - this.lastActivityTime\n this.log(`idle timeout after ${idleTime}ms`)\n this.handleError(new Error('Idle timeout'))\n }, this.idleTimeout)\n }\n\n public async detach(close?: boolean): Promise<PGLiteSocketHandler> {\n this.log(`detach: detaching socket, close=${close ?? this.closeOnDetach}`)\n\n if (this.idleTimer) {\n clearTimeout(this.idleTimer)\n this.idleTimer = undefined\n }\n\n // Clear any pending queries for this handler\n this.queryQueue.clearQueueForHandler(this.id)\n\n await this.queryQueue.clearTransactionIfNeeded(this.id)\n\n if (!this.socket) {\n this.log(`detach: no socket attached, nothing to do`)\n return this\n }\n\n // Remove all listeners\n this.socket.removeAllListeners('data')\n this.socket.removeAllListeners('error')\n this.socket.removeAllListeners('close')\n\n // Close the socket if requested\n if (close ?? this.closeOnDetach) {\n if (this.socket.writable) {\n this.log(`detach: closing socket`)\n try {\n this.socket.end()\n this.socket.destroy()\n } catch (err) {\n this.log(`detach: error closing socket:`, err)\n }\n }\n }\n\n this.socket = null\n this.active = false\n this.messageBuffer = Buffer.alloc(0)\n\n this.log(`detach: handler cleaned up`)\n return this\n }\n\n public get isAttached(): boolean {\n return this.socket !== null\n }\n\n private async handleData(data: Buffer): Promise<number> {\n if (!this.socket || !this.active) {\n this.log(`handleData: no active socket, ignoring data`)\n return 0\n }\n\n this.log(`handleData: received ${data.length} bytes`)\n\n // Append to buffer for message reassembly\n this.messageBuffer = Buffer.concat([this.messageBuffer, data])\n\n // Print the incoming data to the console\n this.inspectData('incoming', data)\n\n try {\n let totalProcessed = 0\n\n while (this.messageBuffer.length > 0) {\n // Determine message length\n let messageLength = 0\n let isComplete = false\n\n // Handle startup message (no type byte, just length)\n if (this.messageBuffer.length >= 4) {\n const firstInt = this.messageBuffer.readInt32BE(0)\n\n if (this.messageBuffer.length >= 8) {\n const secondInt = this.messageBuffer.readInt32BE(4)\n // PostgreSQL 3.0 protocol version\n if (secondInt === 196608 || secondInt === 0x00030000) {\n messageLength = firstInt\n isComplete = this.messageBuffer.length >= messageLength\n }\n }\n\n // Regular message (type byte + length)\n if (!isComplete && this.messageBuffer.length >= 5) {\n const msgLength = this.messageBuffer.readInt32BE(1)\n messageLength = 1 + msgLength\n isComplete = this.messageBuffer.length >= messageLength\n }\n }\n\n if (!isComplete || messageLength === 0) {\n this.log(\n `handleData: incomplete message, buffering ${this.messageBuffer.length} bytes`,\n )\n break\n }\n\n // Extract and process complete message\n const message = this.messageBuffer.slice(0, messageLength)\n this.messageBuffer = this.messageBuffer.slice(messageLength)\n\n this.log(`handleData: processing message of ${message.length} bytes`)\n\n // Check if socket is still active before processing\n if (!this.active || !this.socket) {\n this.log(`handleData: socket no longer active, stopping processing`)\n break\n }\n\n // Queue the query for execution\n // This allows multiple connections to queue queries simultaneously\n const result = await this.queryQueue.enqueue(\n this.id,\n new Uint8Array(message),\n )\n\n this.log(`handleData: received ${result.length} bytes from PGlite`)\n\n // Print the outgoing data to the console\n this.inspectData('outgoing', result)\n\n // Send response if available\n if (\n result.length > 0 &&\n this.socket &&\n this.socket.writable &&\n this.active\n ) {\n await new Promise<number>((resolve, reject) => {\n this.log(`handleData: writing response to socket`)\n if (this.socket?.writable) {\n this.socket.write(Buffer.from(result), (err?: any) => {\n if (err) {\n this.log(`handleData: error writing to socket:`, err)\n reject(err)\n } else {\n this.log(`handleData: socket sent: ${result.length} bytes`)\n resolve(result.length)\n }\n })\n } else {\n this.log(`handleData: socket no longer writable`)\n resolve(0)\n }\n }).catch((writeErr) => {\n this.log(`handleData: failed to write to socket:`, writeErr)\n throw writeErr\n })\n }\n\n totalProcessed += result.length\n }\n\n // Emit data event with byte sizes\n this.dispatchEvent(\n new CustomEvent('data', {\n detail: { incoming: data.length, outgoing: totalProcessed },\n }),\n )\n\n return totalProcessed\n } catch (err) {\n this.log(`handleData: error processing data:`, err)\n throw err\n }\n }\n\n private handleError(err: Error): void {\n if (!this.active) {\n this.log(`handleError: handler not active, ignoring error`)\n return\n }\n\n // ECONNRESET is expected behavior when clients disconnect\n if (err.message?.includes('ECONNRESET')) {\n this.log(\n `handleError: client disconnected (ECONNRESET) - normal behavior`,\n )\n } else if (err.message?.includes('Idle timeout')) {\n this.log(`handleError: connection idle timeout`)\n } else {\n this.log(`handleError:`, err)\n }\n\n this.active = false\n\n // Emit error event\n this.dispatchEvent(new CustomEvent('error', { detail: err }))\n\n // Clean up\n this.detach(true)\n }\n\n private handleClose(): void {\n this.log(`handleClose: socket closed`)\n this.active = false\n this.dispatchEvent(new CustomEvent('close'))\n this.detach(false)\n }\n\n private inspectData(\n direction: 'incoming' | 'outgoing',\n data: Buffer | Uint8Array,\n ): void {\n if (!this.inspect) return\n console.log('-'.repeat(75))\n if (direction === 'incoming') {\n console.log('-> incoming', data.length, 'bytes')\n } else {\n console.log('<- outgoing', data.length, 'bytes')\n }\n\n for (let offset = 0; offset < data.length; offset += 16) {\n const chunkSize = Math.min(16, data.length - offset)\n\n let hexPart = ''\n for (let i = 0; i < 16; i++) {\n if (i < chunkSize) {\n const byte = data[offset + i]\n hexPart += byte.toString(16).padStart(2, '0') + ' '\n } else {\n hexPart += ' '\n }\n }\n\n let asciiPart = ''\n for (let i = 0; i < chunkSize; i++) {\n const byte = data[offset + i]\n asciiPart += byte >= 32 && byte <= 126 ? String.fromCharCode(byte) : '.'\n }\n\n console.log(\n `${offset.toString(16).padStart(8, '0')} ${hexPart} ${asciiPart}`,\n )\n }\n }\n}\n\n/**\n * Options for creating a PGLiteSocketServer\n */\nexport interface PGLiteSocketServerOptions {\n /** The PGlite database instance */\n db: PGlite\n /** The port to listen on (default: 5432) */\n port?: number\n /** The host to bind to (default: 127.0.0.1) */\n host?: string\n /** Unix socket path to bind to (default: undefined) */\n path?: string\n /** Print the incoming and outgoing data to the console in hex and ascii */\n inspect?: boolean\n /** Enable debug logging of method calls */\n debug?: boolean\n /** Idle timeout in ms (0 to disable, default: 0) */\n idleTimeout?: number\n /** Maximum concurrent connections (default: 100) */\n maxConnections?: number\n}\n\n/**\n * PGLite Socket Server with support for multiple concurrent connections\n * Connections remain open and queries are queued at the query level\n */\nexport class PGLiteSocketServer extends EventTarget {\n readonly db: PGlite\n private server: Server | null = null\n private port?: number\n private host?: string\n private path?: string\n private active = false\n private inspect: boolean\n private debug: boolean\n private idleTimeout: number\n private maxConnections: number\n private handlers: Set<PGLiteSocketHandler> = new Set()\n private queryQueue: QueryQueueManager\n\n constructor(options: PGLiteSocketServerOptions) {\n super()\n this.db = options.db\n if (options.path) {\n this.path = options.path\n } else {\n if (typeof options.port === 'number') {\n // Keep port undefined on port 0, will be set by the OS when we start the server.\n this.port = options.port ?? options.port\n } else {\n this.port = 5432\n }\n this.host = options.host || '127.0.0.1'\n }\n this.inspect = options.inspect ?? false\n this.debug = options.debug ?? false\n this.idleTimeout = options.idleTimeout ?? 0\n this.maxConnections = options.maxConnections ?? 1\n\n // Create the shared query queue\n this.queryQueue = new QueryQueueManager(this.db, this.debug)\n\n this.log(`constructor: created server on ${this.getServerConn()}`)\n this.log(`constructor: max connections: ${this.maxConnections}`)\n if (this.idleTimeout > 0) {\n this.log(`constructor: idle timeout: ${this.idleTimeout}ms`)\n }\n }\n\n private log(message: string, ...args: any[]): void {\n if (this.debug) {\n console.log(`[PGLiteSocketServer] ${message}`, ...args)\n }\n }\n\n public async start(): Promise<void> {\n this.log(`start: starting server on ${this.getServerConn()}`)\n\n if (this.server) {\n throw new Error('Socket server already started')\n }\n\n // Ensure PGlite is ready before accepting connections\n await this.db.waitReady\n\n this.active = true\n this.server = createServer((socket) => {\n setImmediate(() => this.handleConnection(socket))\n })\n\n this.server.maxConnections = this.maxConnections\n\n return new Promise<void>((resolve, reject) => {\n if (!this.server) return reject(new Error('Server not initialized'))\n\n this.server.on('error', (err) => {\n this.log(`start: server error:`, err)\n this.dispatchEvent(new CustomEvent('error', { detail: err }))\n if (!this.active) {\n reject(err)\n }\n })\n\n if (this.path) {\n this.server.listen(this.path, () => {\n this.log(`start: server listening on ${this.getServerConn()}`)\n this.dispatchEvent(\n new CustomEvent('listening', {\n detail: { path: this.path },\n }),\n )\n resolve()\n })\n } else {\n const server = this.server\n server.listen(this.port, this.host, () => {\n const address = server.address()\n // We are not using pipes, so return type should be AddressInfo\n if (address === null || typeof address !== 'object') {\n throw Error('Expected address info')\n }\n // Assign the new port number\n this.port = address.port\n this.log(`start: server listening on ${this.getServerConn()}`)\n this.dispatchEvent(\n new CustomEvent('listening', {\n detail: { port: this.port, host: this.host },\n }),\n )\n resolve()\n })\n }\n })\n }\n\n public getServerConn(): string {\n if (this.path) return this.path\n return `${this.host}:${this.port}`\n }\n\n public async stop(): Promise<void> {\n this.log(`stop: stopping server`)\n\n this.active = false\n\n // Detach all handlers\n this.log(`stop: detaching ${this.handlers.size} handlers`)\n for (const handler of this.handlers) {\n handler.detach(true)\n }\n this.handlers.clear()\n\n if (!this.server) {\n this.log(`stop: server not running, nothing to do`)\n return Promise.resolve()\n }\n\n return new Promise<void>((resolve) => {\n if (!this.server) return resolve()\n\n this.server.close(() => {\n this.log(`stop: server closed`)\n this.server = null\n this.dispatchEvent(new CustomEvent('close'))\n resolve()\n })\n })\n }\n\n private async handleConnection(socket: Socket): Promise<void> {\n const clientInfo = {\n clientAddress: socket.remoteAddress || 'unknown',\n clientPort: socket.remotePort || 0,\n }\n\n this.log(\n `handleConnection: new connection from ${clientInfo.clientAddress}:${clientInfo.clientPort}`,\n )\n this.log(\n `handleConnection: active connections: ${this.handlers.size}, queued queries: ${this.queryQueue.getQueueLength()}`,\n )\n\n if (!this.active) {\n this.log(`handleConnection: server not active, closing connection`)\n try {\n socket.end()\n } catch (err) {\n this.log(`handleConnection: error closing socket:`, err)\n }\n return\n }\n\n // Check connection limit\n if (this.handlers.size >= this.maxConnections) {\n this.log(`handleConnection: max connections reached, rejecting`)\n socket.write(Buffer.from('Too many connections\\n'))\n socket.end()\n return\n }\n\n // Create a new handler for this connection\n const handler = new PGLiteSocketHandler({\n queryQueue: this.queryQueue,\n closeOnDetach: true,\n inspect: this.inspect,\n debug: this.debug,\n idleTimeout: this.idleTimeout,\n })\n\n // Track this handler\n this.handlers.add(handler)\n\n // Handle errors\n handler.addEventListener('error', (event) => {\n const error = (event as CustomEvent<Error>).detail\n\n if (error?.message?.includes('ECONNRESET')) {\n this.log(\n `handler #${handler.handlerId}: client disconnected (ECONNRESET)`,\n )\n } else if (error?.message?.includes('Idle timeout')) {\n this.log(`handler #${handler.handlerId}: idle timeout`)\n } else {\n this.log(`handler #${handler.handlerId}: error:`, error)\n }\n })\n\n // Handle close event\n handler.addEventListener('close', () => {\n this.log(`handler #${handler.handlerId}: closed`)\n this.handlers.delete(handler)\n this.log(`handleConnection: active connections: ${this.handlers.size}`)\n })\n\n try {\n await handler.attach(socket)\n this.dispatchEvent(new CustomEvent('connection', { detail: clientInfo }))\n } catch (err) {\n this.log(`handleConnection: error attaching socket:`, err)\n this.handlers.delete(handler)\n this.dispatchEvent(new CustomEvent('error', { detail: err }))\n try {\n socket.end()\n } catch (closeErr) {\n this.log(`handleConnection: error closing socket:`, closeErr)\n }\n }\n }\n\n public getStats() {\n return {\n activeConnections: this.handlers.size,\n queuedQueries: this.queryQueue.getQueueLength(),\n maxConnections: this.maxConnections,\n }\n }\n}\n"],"mappings":"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","names":["src_exports","__export","CONNECTION_QUEUE_TIMEOUT","PGLiteSocketHandler","PGLiteSocketServer","__toCommonJS","import_net","CONNECTION_QUEUE_TIMEOUT","QueryQueueManager","db","debug","message","args","handlerId","resolve","reject","query","q","waitTime","result","error","before","removed","_PGLiteSocketHandler","options","socket","data","err","idleTime","close","totalProcessed","messageLength","isComplete","firstInt","secondInt","writeErr","direction","offset","chunkSize","hexPart","i","byte","asciiPart","PGLiteSocketHandler","PGLiteSocketServer","server","address","handler","clientInfo","event","closeErr"]}

@@ -6,7 +6,25 @@ import { PGlite } from '@electric-sql/pglite';

/**
* Global query queue manager
* Ensures only one query executes at a time in PGlite
*/
declare class QueryQueueManager {
private queue;
private processing;
private db;
private debug;
private lastHandlerId;
constructor(db: PGlite, debug?: boolean);
private log;
enqueue(handlerId: number, message: Uint8Array): Promise<Uint8Array>;
private processQueue;
getQueueLength(): number;
clearQueueForHandler(handlerId: number): void;
clearTransactionIfNeeded(handlerId: number): Promise<void>;
}
/**
* Options for creating a PGLiteSocketHandler
*/
interface PGLiteSocketHandlerOptions {
/** The PGlite database instance */
db: PGlite;
/** The query queue manager */
queryQueue: QueryQueueManager;
/** Whether to close the socket when detached (default: false) */

@@ -18,64 +36,32 @@ closeOnDetach?: boolean;

debug?: boolean;
/** Idle timeout in ms (0 to disable, default: 0) */
idleTimeout?: number;
}
/**
* Low-level handler for a single socket connection to PGLite
* Handles the raw protocol communication between a socket and PGLite
* Handler for a single socket connection to PGlite
* Each connection can remain open and send multiple queries
*/
declare class PGLiteSocketHandler extends EventTarget {
readonly db: PGlite;
private queryQueue;
private socket;
private active;
private closeOnDetach;
private resolveLock?;
private rejectLock?;
private inspect;
private debug;
private readonly id;
private messageBuffer;
private idleTimer?;
private idleTimeout;
private lastActivityTime;
private static nextHandlerId;
/**
* Create a new PGLiteSocketHandler
* @param options Options for the handler
*/
constructor(options: PGLiteSocketHandlerOptions);
/**
* Get the unique ID of this handler
*/
get handlerId(): number;
/**
* Log a message if debug is enabled
* @private
*/
private log;
/**
* Attach a socket to this handler
* @param socket The socket to attach
* @returns this handler instance
* @throws Error if a socket is already attached
*/
attach(socket: Socket): Promise<PGLiteSocketHandler>;
/**
* Detach the current socket from this handler
* @param close Whether to close the socket when detaching (overrides constructor option)
* @returns this handler instance
*/
detach(close?: boolean): PGLiteSocketHandler;
/**
* Check if a socket is currently attached
*/
private resetIdleTimer;
detach(close?: boolean): Promise<PGLiteSocketHandler>;
get isAttached(): boolean;
/**
* Handle incoming data from the socket
*/
private handleData;
/**
* Handle errors from the socket
*/
private handleError;
/**
* Handle socket close event
*/
private handleClose;
/**
* Print data in hex and ascii to the console
*/
private inspectData;

@@ -93,14 +79,16 @@ }

host?: string;
/** Unix socket path to bind to (default: undefined). If specified, takes precedence over host:port */
/** Unix socket path to bind to (default: undefined) */
path?: string;
/** Print the incoming and outgoing data to the console in hex and ascii */
inspect?: boolean;
/** Connection queue timeout in milliseconds (default: 10000) */
connectionQueueTimeout?: number;
/** Enable debug logging of method calls */
debug?: boolean;
/** Idle timeout in ms (0 to disable, default: 0) */
idleTimeout?: number;
/** Maximum concurrent connections (default: 100) */
maxConnections?: number;
}
/**
* High-level server that manages socket connections to PGLite
* Creates and manages a TCP server and handles client connections
* PGLite Socket Server with support for multiple concurrent connections
* Connections remain open and queries are queued at the query level
*/

@@ -116,49 +104,19 @@ declare class PGLiteSocketServer extends EventTarget {

private debug;
private connectionQueueTimeout;
private activeHandler;
private connectionQueue;
private handlerCount;
/**
* Create a new PGLiteSocketServer
* @param options Options for the server
*/
private idleTimeout;
private maxConnections;
private handlers;
private queryQueue;
constructor(options: PGLiteSocketServerOptions);
/**
* Log a message if debug is enabled
* @private
*/
private log;
/**
* Start the socket server
* @returns Promise that resolves when the server is listening
*/
start(): Promise<void>;
getServerConn(): string;
/**
* Stop the socket server
* @returns Promise that resolves when the server is closed
*/
stop(): Promise<void>;
/**
* Get the active handler ID, or null if no active handler
*/
private get activeHandlerId();
/**
* Handle a new client connection
*/
private handleConnection;
/**
* Add a connection to the queue
*/
private enqueueConnection;
/**
* Process the next connection in the queue
*/
private processNextInQueue;
/**
* Attach a socket to a new handler
*/
private attachSocketToNewHandler;
getStats(): {
activeConnections: number;
queuedQueries: number;
maxConnections: number;
};
}
export { CONNECTION_QUEUE_TIMEOUT, PGLiteSocketHandler, type PGLiteSocketHandlerOptions, PGLiteSocketServer, type PGLiteSocketServerOptions };

@@ -6,7 +6,25 @@ import { PGlite } from '@electric-sql/pglite';

/**
* Global query queue manager
* Ensures only one query executes at a time in PGlite
*/
declare class QueryQueueManager {
private queue;
private processing;
private db;
private debug;
private lastHandlerId;
constructor(db: PGlite, debug?: boolean);
private log;
enqueue(handlerId: number, message: Uint8Array): Promise<Uint8Array>;
private processQueue;
getQueueLength(): number;
clearQueueForHandler(handlerId: number): void;
clearTransactionIfNeeded(handlerId: number): Promise<void>;
}
/**
* Options for creating a PGLiteSocketHandler
*/
interface PGLiteSocketHandlerOptions {
/** The PGlite database instance */
db: PGlite;
/** The query queue manager */
queryQueue: QueryQueueManager;
/** Whether to close the socket when detached (default: false) */

@@ -18,64 +36,32 @@ closeOnDetach?: boolean;

debug?: boolean;
/** Idle timeout in ms (0 to disable, default: 0) */
idleTimeout?: number;
}
/**
* Low-level handler for a single socket connection to PGLite
* Handles the raw protocol communication between a socket and PGLite
* Handler for a single socket connection to PGlite
* Each connection can remain open and send multiple queries
*/
declare class PGLiteSocketHandler extends EventTarget {
readonly db: PGlite;
private queryQueue;
private socket;
private active;
private closeOnDetach;
private resolveLock?;
private rejectLock?;
private inspect;
private debug;
private readonly id;
private messageBuffer;
private idleTimer?;
private idleTimeout;
private lastActivityTime;
private static nextHandlerId;
/**
* Create a new PGLiteSocketHandler
* @param options Options for the handler
*/
constructor(options: PGLiteSocketHandlerOptions);
/**
* Get the unique ID of this handler
*/
get handlerId(): number;
/**
* Log a message if debug is enabled
* @private
*/
private log;
/**
* Attach a socket to this handler
* @param socket The socket to attach
* @returns this handler instance
* @throws Error if a socket is already attached
*/
attach(socket: Socket): Promise<PGLiteSocketHandler>;
/**
* Detach the current socket from this handler
* @param close Whether to close the socket when detaching (overrides constructor option)
* @returns this handler instance
*/
detach(close?: boolean): PGLiteSocketHandler;
/**
* Check if a socket is currently attached
*/
private resetIdleTimer;
detach(close?: boolean): Promise<PGLiteSocketHandler>;
get isAttached(): boolean;
/**
* Handle incoming data from the socket
*/
private handleData;
/**
* Handle errors from the socket
*/
private handleError;
/**
* Handle socket close event
*/
private handleClose;
/**
* Print data in hex and ascii to the console
*/
private inspectData;

@@ -93,14 +79,16 @@ }

host?: string;
/** Unix socket path to bind to (default: undefined). If specified, takes precedence over host:port */
/** Unix socket path to bind to (default: undefined) */
path?: string;
/** Print the incoming and outgoing data to the console in hex and ascii */
inspect?: boolean;
/** Connection queue timeout in milliseconds (default: 10000) */
connectionQueueTimeout?: number;
/** Enable debug logging of method calls */
debug?: boolean;
/** Idle timeout in ms (0 to disable, default: 0) */
idleTimeout?: number;
/** Maximum concurrent connections (default: 100) */
maxConnections?: number;
}
/**
* High-level server that manages socket connections to PGLite
* Creates and manages a TCP server and handles client connections
* PGLite Socket Server with support for multiple concurrent connections
* Connections remain open and queries are queued at the query level
*/

@@ -116,49 +104,19 @@ declare class PGLiteSocketServer extends EventTarget {

private debug;
private connectionQueueTimeout;
private activeHandler;
private connectionQueue;
private handlerCount;
/**
* Create a new PGLiteSocketServer
* @param options Options for the server
*/
private idleTimeout;
private maxConnections;
private handlers;
private queryQueue;
constructor(options: PGLiteSocketServerOptions);
/**
* Log a message if debug is enabled
* @private
*/
private log;
/**
* Start the socket server
* @returns Promise that resolves when the server is listening
*/
start(): Promise<void>;
getServerConn(): string;
/**
* Stop the socket server
* @returns Promise that resolves when the server is closed
*/
stop(): Promise<void>;
/**
* Get the active handler ID, or null if no active handler
*/
private get activeHandlerId();
/**
* Handle a new client connection
*/
private handleConnection;
/**
* Add a connection to the queue
*/
private enqueueConnection;
/**
* Process the next connection in the queue
*/
private processNextInQueue;
/**
* Attach a socket to a new handler
*/
private attachSocketToNewHandler;
getStats(): {
activeConnections: number;
queuedQueries: number;
maxConnections: number;
};
}
export { CONNECTION_QUEUE_TIMEOUT, PGLiteSocketHandler, type PGLiteSocketHandlerOptions, PGLiteSocketServer, type PGLiteSocketServerOptions };

@@ -1,2 +0,2 @@

import{a,b,c}from"./chunk-F6KLIXM7.js";export{a as CONNECTION_QUEUE_TIMEOUT,b as PGLiteSocketHandler,c as PGLiteSocketServer};
import{a,b,c}from"./chunk-JGAU6COP.js";export{a as CONNECTION_QUEUE_TIMEOUT,b as PGLiteSocketHandler,c as PGLiteSocketServer};
//# sourceMappingURL=index.js.map
#!/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||"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,m.createServer)(e=>this.handleConnection(e)),new Promise((e,t)=>{if(!this.server)return t(new Error("Server not initialized"));if(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()});else{let i=this.server;i.listen(this.port,this.host,()=>{let s=i.address();if(s===null||typeof s!="object")throw Error("Expected address info");this.port=s.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(){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(s=>s.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 p({db:this.db,closeOnDetach:!0,inspect:this.inspect,debug:this.debug});i.addEventListener("error",s=>{this.log(`handler #${i.handlerId}: error from handler:`,s.detail),this.dispatchEvent(new CustomEvent("error",{detail:s.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(s){throw this.log(`handler #${i.handlerId}: error attaching socket:`,s),this.activeHandler=null,e.writable&&e.end(),s}}};var b=require("util"),w=require("child_process"),r=(0,b.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)"},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)"},help:{type:"boolean",short:"?",default:!1,help:"Show help"}}}),k=`PGlite Socket Server
"use strict";var y=require("@electric-sql/pglite");var b=require("net");var p=class{constructor(s,e=!1){this.queue=[];this.processing=!1;this.lastHandlerId=null;this.db=s,this.debug=e}log(s,...e){this.debug&&console.log(`[QueryQueueManager] ${s}`,...e)}async enqueue(s,e){return new Promise((t,i)=>{let r={handlerId:s,message:e,resolve:t,reject:i,timestamp:Date.now()};this.queue.push(r),this.log(`enqueued query from handler #${s}, 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 s;if(this.db.isInTransaction()&&this.lastHandlerId){let t=this.queue.findIndex(i=>i.handlerId===this.lastHandlerId);t===-1?(this.log("transaction started, but no query from the same handler id found in queue",this.lastHandlerId),s=null):s=this.queue.splice(t,1)[0]}else s=this.queue.shift();if(!s)break;let e=Date.now()-s.timestamp;this.log(`processing query from handler #${s.handlerId} (waited ${e}ms)`);try{let t=await this.db.runExclusive(async()=>await this.db.execProtocolRaw(s.message));this.log(`query from handler #${s.handlerId} completed, ${t.length} bytes`),this.lastHandlerId=s.handlerId,s.resolve(t)}catch(t){this.log(`query from handler #${s.handlerId} failed:`,t),s.reject(t)}}this.processing=!1,this.log("queue processing complete, queue length is",this.queue.length)}}getQueueLength(){return this.queue.length}clearQueueForHandler(s){let e=this.queue.length;this.queue=this.queue.filter(i=>i.handlerId===s?(i.reject(new Error("Handler disconnected")),!1):!0);let t=e-this.queue.length;t>0&&this.log(`cleared ${t} queries for handler #${s}`)}async clearTransactionIfNeeded(s){this.db.isInTransaction()&&this.lastHandlerId===s&&(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(i){this.log("socket on data error: ",i),this.handleError(i)}})}),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 i=0,r=!1;if(this.messageBuffer.length>=4){let o=this.messageBuffer.readInt32BE(0);if(this.messageBuffer.length>=8){let l=this.messageBuffer.readInt32BE(4);(l===196608||l===196608)&&(i=o,r=this.messageBuffer.length>=i)}!r&&this.messageBuffer.length>=5&&(i=1+this.messageBuffer.readInt32BE(1),r=this.messageBuffer.length>=i)}if(!r||i===0){this.log(`handleData: incomplete message, buffering ${this.messageBuffer.length} bytes`);break}let n=this.messageBuffer.slice(0,i);if(this.messageBuffer=this.messageBuffer.slice(i),this.log(`handleData: processing message of ${n.length} bytes`),!this.active||!this.socket){this.log("handleData: socket no longer active, stopping processing");break}let a=await this.queryQueue.enqueue(this.id,new Uint8Array(n));this.log(`handleData: received ${a.length} bytes from PGlite`),this.inspectData("outgoing",a),a.length>0&&this.socket&&this.socket.writable&&this.active&&await new Promise((o,l)=>{this.log("handleData: writing response to socket"),this.socket?.writable?this.socket.write(Buffer.from(a),m=>{m?(this.log("handleData: error writing to socket:",m),l(m)):(this.log(`handleData: socket sent: ${a.length} bytes`),o(a.length))}):(this.log("handleData: socket no longer writable"),o(0))}).catch(o=>{throw this.log("handleData: failed to write to socket:",o),o}),t+=a.length}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 i=0;i<t.length;i+=16){let r=Math.min(16,t.length-i),n="";for(let o=0;o<16;o++)if(o<r){let l=t[i+o];n+=l.toString(16).padStart(2,"0")+" "}else n+=" ";let a="";for(let o=0;o<r;o++){let l=t[i+o];a+=l>=32&&l<=126?String.fromCharCode(l):"."}console.log(`${i.toString(16).padStart(8,"0")} ${n} ${a}`)}}}};u.nextHandlerId=1;var f=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 p(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,b.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",i=>{this.log("start: server error:",i),this.dispatchEvent(new CustomEvent("error",{detail:i})),this.active||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()});else{let i=this.server;i.listen(this.port,this.host,()=>{let r=i.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 i=new f({queryQueue:this.queryQueue,closeOnDetach:!0,inspect:this.inspect,debug:this.debug,idleTimeout:this.idleTimeout});this.handlers.add(i),i.addEventListener("error",r=>{let n=r.detail;n?.message?.includes("ECONNRESET")?this.log(`handler #${i.handlerId}: client disconnected (ECONNRESET)`):n?.message?.includes("Idle timeout")?this.log(`handler #${i.handlerId}: idle timeout`):this.log(`handler #${i.handlerId}: error:`,n)}),i.addEventListener("close",()=>{this.log(`handler #${i.handlerId}: closed`),this.handlers.delete(i),this.log(`handleConnection: active connections: ${this.handlers.size}`)});try{await i.attach(e),this.dispatchEvent(new CustomEvent("connection",{detail:t}))}catch(r){this.log("handleConnection: error attaching socket:",r),this.handlers.delete(i),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 w=require("util"),E=require("child_process"),h=(0,w.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)"},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"}}}),C=`PGlite Socket Server
Usage: pglite-server [options]

@@ -17,4 +18,5 @@

--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();
-m, --max-connections=N Maximum concurrent connections (default is no concurrency: 1)
`,g=class{constructor(s){this.db=null;this.server=null;this.subprocessManager=null;this.config=s}static parseConfig(){let s=h.values.extensions;return{dbPath:h.values.db,port:parseInt(h.values.port,10),host:h.values.host,path:h.values.path,debugLevel:parseInt(h.values.debug,10),extensionNames:s?s.split(",").map(e=>e.trim()):void 0,runCommand:h.values.run,includeDatabaseUrl:h.values["include-database-url"],shutdownTimeout:parseInt(h.values["shutdown-timeout"],10),maxConnections:parseInt(h.values["max-connections"],10)}}createDatabaseUrl(){let{host:s,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@${s}:${e}/postgres`}async importExtensions(){if(!this.config.extensionNames?.length)return;let s={},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[r,n]=t.split(":");if(!r||!n)throw new Error(`Invalid extension format '${t}'. Expected: package/path:exportedName`);i=(await import(r))[n],i&&(s[n]=i,console.log(`Imported extension '${n}' from '${r}'`))}else if(e.includes(t))i=(await import(`@electric-sql/pglite/${t}`))[t],i&&(s[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&&(s[t]=i,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(s).length>0?s:void 0}async initializeDatabase(){console.log(`Initializing PGLite with database: ${this.config.dbPath}`),console.log(`Debug level: ${this.config.debugLevel}`);let s=await this.importExtensions();this.db=new y.PGlite(this.config.dbPath,{debug:this.config.debugLevel,extensions:s}),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",s=>{let e=s.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",s=>{let{clientAddress:e,clientPort:t}=s.detail;console.log(`Client connected from ${e}:${t}`)}),this.server.addEventListener("error",s=>{let e=s.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 v(s=>{this.shutdown(s)}),this.setupServerEventHandlers(),this.setupSignalHandlers(),await this.server.start()}catch(s){throw console.error("Failed to start PGLiteSocketServer:",s),s}}async shutdown(s=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(s)}},v=class{constructor(s){this.childProcess=null;this.onExit=s}get process(){return this.childProcess}spawn(s,e,t){console.log(`Running command: ${s}`);let i={...process.env};t&&(i.DATABASE_URL=e,console.log(`Setting DATABASE_URL=${e}`));let r=s.trim().split(/\s+/);this.childProcess=(0,E.spawn)(r[0],r.slice(1),{env:i,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(s){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"))},s))}};async function S(){h.values.help&&(console.log(C),process.exit(0));try{let c=g.parseConfig();await new g(c).start()}catch(c){console.error("Unhandled error:",c),process.exit(1)}}S();
//# sourceMappingURL=server.cjs.map

@@ -1,1 +0,1 @@

{"version":3,"sources":["../../src/scripts/server.ts","../../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { PGlite, DebugLevel } from '@electric-sql/pglite'\nimport type { Extension, Extensions } from '@electric-sql/pglite'\nimport { PGLiteSocketServer } from '../index'\nimport { parseArgs } from 'node:util'\nimport { spawn, ChildProcess } from 'node:child_process'\n\n// Define command line argument options\nconst args = parseArgs({\n options: {\n db: {\n type: 'string',\n short: 'd',\n default: 'memory://',\n help: 'Database path (relative or absolute). Use memory:// for in-memory database.',\n },\n port: {\n type: 'string',\n short: 'p',\n default: '5432',\n help: 'Port to listen on',\n },\n host: {\n type: 'string',\n short: 'h',\n default: '127.0.0.1',\n help: 'Host to bind to',\n },\n path: {\n type: 'string',\n short: 'u',\n default: undefined,\n help: 'unix socket to bind to. Takes precedence over host:port',\n },\n debug: {\n type: 'string',\n short: 'v',\n default: '0',\n help: 'Debug level (0-5)',\n },\n extensions: {\n type: 'string',\n short: 'e',\n default: undefined,\n help: 'Comma-separated list of extensions to load (e.g., vector,pgcrypto)',\n },\n run: {\n type: 'string',\n short: 'r',\n default: undefined,\n help: 'Command to run after server starts',\n },\n 'include-database-url': {\n type: 'boolean',\n default: false,\n help: 'Include DATABASE_URL in the environment of the subprocess',\n },\n 'shutdown-timeout': {\n type: 'string',\n default: '5000',\n help: 'Timeout in milliseconds for graceful subprocess shutdown (default: 5000)',\n },\n help: {\n type: 'boolean',\n short: '?',\n default: false,\n help: 'Show help',\n },\n },\n})\n\nconst help = `PGlite Socket Server\nUsage: pglite-server [options]\n\nOptions:\n -d, --db=PATH Database path (default: memory://)\n -p, --port=PORT Port to listen on (default: 5432)\n -h, --host=HOST Host to bind to (default: 127.0.0.1)\n -u, --path=UNIX Unix socket to bind to (default: undefined). Takes precedence over host:port\n -v, --debug=LEVEL Debug level 0-5 (default: 0)\n -e, --extensions=LIST Comma-separated list of extensions to load\n Formats: vector, pgcrypto (built-in/contrib)\n @org/package/path:exportedName (npm package)\n -r, --run=COMMAND Command to run after server starts\n --include-database-url Include DATABASE_URL in subprocess environment\n --shutdown-timeout=MS Timeout for graceful subprocess shutdown in ms (default: 5000)\n`\n\ninterface ServerConfig {\n dbPath: string\n port: number\n host: string\n path?: string\n debugLevel: DebugLevel\n extensionNames?: string[]\n runCommand?: string\n includeDatabaseUrl: boolean\n shutdownTimeout: number\n}\n\nclass PGLiteServerRunner {\n private config: ServerConfig\n private db: PGlite | null = null\n private server: PGLiteSocketServer | null = null\n private subprocessManager: SubprocessManager | null = null\n\n constructor(config: ServerConfig) {\n this.config = config\n }\n\n static parseConfig(): ServerConfig {\n const extensionsArg = args.values.extensions as string | undefined\n return {\n dbPath: args.values.db as string,\n port: parseInt(args.values.port as string, 10),\n host: args.values.host as string,\n path: args.values.path as string,\n debugLevel: parseInt(args.values.debug as string, 10) as DebugLevel,\n extensionNames: extensionsArg\n ? extensionsArg.split(',').map((e) => e.trim())\n : undefined,\n runCommand: args.values.run as string,\n includeDatabaseUrl: args.values['include-database-url'] as boolean,\n shutdownTimeout: parseInt(args.values['shutdown-timeout'] as string, 10),\n }\n }\n\n private createDatabaseUrl(): string {\n const { host, port, path } = this.config\n\n if (path) {\n // Unix socket connection\n const socketDir = path.endsWith('/.s.PGSQL.5432')\n ? path.slice(0, -13)\n : path\n return `postgresql://postgres:postgres@/postgres?host=${encodeURIComponent(socketDir)}`\n } else {\n // TCP connection\n return `postgresql://postgres:postgres@${host}:${port}/postgres`\n }\n }\n\n private async importExtensions(): Promise<Extensions | undefined> {\n if (!this.config.extensionNames?.length) {\n return undefined\n }\n\n const extensions: Extensions = {}\n\n // Built-in extensions that are not in contrib\n const builtInExtensions = [\n 'vector',\n 'live',\n 'pg_hashids',\n 'pg_ivm',\n 'pg_uuidv7',\n 'pgtap',\n ]\n\n for (const name of this.config.extensionNames) {\n let ext: Extension | null = null\n\n try {\n // Check if this is a custom package path (contains ':')\n // Format: @org/package/path:exportedName or package/path:exportedName\n if (name.includes(':')) {\n const [packagePath, exportName] = name.split(':')\n if (!packagePath || !exportName) {\n throw new Error(\n `Invalid extension format '${name}'. Expected: package/path:exportedName`,\n )\n }\n const mod = await import(packagePath)\n ext = mod[exportName] as Extension\n if (ext) {\n extensions[exportName] = ext\n console.log(\n `Imported extension '${exportName}' from '${packagePath}'`,\n )\n }\n } else if (builtInExtensions.includes(name)) {\n // Built-in extension (e.g., @electric-sql/pglite/vector)\n const mod = await import(`@electric-sql/pglite/${name}`)\n ext = mod[name] as Extension\n if (ext) {\n extensions[name] = ext\n console.log(`Imported extension: ${name}`)\n }\n } else {\n // Try contrib first (e.g., @electric-sql/pglite/contrib/pgcrypto)\n try {\n const mod = await import(`@electric-sql/pglite/contrib/${name}`)\n ext = mod[name] as Extension\n } catch {\n // Fall back to external package (e.g., @electric-sql/pglite-<extension>)\n const mod = await import(`@electric-sql/pglite-${name}`)\n ext = mod[name] as Extension\n }\n if (ext) {\n extensions[name] = ext\n console.log(`Imported extension: ${name}`)\n }\n }\n } catch (error) {\n console.error(`Failed to import extension '${name}':`, error)\n throw new Error(`Failed to import extension '${name}'`)\n }\n }\n\n return Object.keys(extensions).length > 0 ? extensions : undefined\n }\n\n private async initializeDatabase(): Promise<void> {\n console.log(`Initializing PGLite with database: ${this.config.dbPath}`)\n console.log(`Debug level: ${this.config.debugLevel}`)\n\n const extensions = await this.importExtensions()\n\n this.db = new PGlite(this.config.dbPath, {\n debug: this.config.debugLevel,\n extensions,\n })\n await this.db.waitReady\n console.log('PGlite database initialized')\n }\n\n private setupServerEventHandlers(): void {\n if (!this.server || !this.subprocessManager) {\n throw new Error('Server or subprocess manager not initialized')\n }\n\n this.server.addEventListener('listening', (event) => {\n const detail = (\n event as CustomEvent<{ port: number; host: string } | { host: string }>\n ).detail\n console.log(`PGLiteSocketServer listening on ${JSON.stringify(detail)}`)\n\n // Run the command after server starts listening\n if (this.config.runCommand && this.subprocessManager) {\n const databaseUrl = this.createDatabaseUrl()\n this.subprocessManager.spawn(\n this.config.runCommand,\n databaseUrl,\n this.config.includeDatabaseUrl,\n )\n }\n })\n\n this.server.addEventListener('connection', (event) => {\n const { clientAddress, clientPort } = (\n event as CustomEvent<{ clientAddress: string; clientPort: number }>\n ).detail\n console.log(`Client connected from ${clientAddress}:${clientPort}`)\n })\n\n this.server.addEventListener('error', (event) => {\n const error = (event as CustomEvent<Error>).detail\n console.error('Socket server error:', error)\n })\n }\n\n private setupSignalHandlers(): void {\n process.on('SIGINT', () => this.shutdown())\n process.on('SIGTERM', () => this.shutdown())\n }\n\n async start(): Promise<void> {\n try {\n // Initialize database\n await this.initializeDatabase()\n\n if (!this.db) {\n throw new Error('Database initialization failed')\n }\n\n // Create and setup the socket server\n this.server = new PGLiteSocketServer({\n db: this.db,\n port: this.config.port,\n host: this.config.host,\n path: this.config.path,\n inspect: this.config.debugLevel > 0,\n })\n\n // Create subprocess manager\n this.subprocessManager = new SubprocessManager((exitCode) => {\n this.shutdown(exitCode)\n })\n\n // Setup event handlers\n this.setupServerEventHandlers()\n this.setupSignalHandlers()\n\n // Start the server\n await this.server.start()\n } catch (error) {\n console.error('Failed to start PGLiteSocketServer:', error)\n throw error\n }\n }\n\n async shutdown(exitCode: number = 0): Promise<void> {\n console.log('\\nShutting down PGLiteSocketServer...')\n\n // Terminate subprocess if running\n if (this.subprocessManager) {\n this.subprocessManager.terminate(this.config.shutdownTimeout)\n }\n\n // Stop server\n if (this.server) {\n await this.server.stop()\n }\n\n // Close database\n if (this.db) {\n await this.db.close()\n }\n\n console.log('Server stopped')\n process.exit(exitCode)\n }\n}\n\nclass SubprocessManager {\n private childProcess: ChildProcess | null = null\n private onExit: (code: number) => void\n\n constructor(onExit: (code: number) => void) {\n this.onExit = onExit\n }\n\n get process(): ChildProcess | null {\n return this.childProcess\n }\n\n spawn(\n command: string,\n databaseUrl: string,\n includeDatabaseUrl: boolean,\n ): void {\n console.log(`Running command: ${command}`)\n\n // Prepare environment variables\n const env = { ...process.env }\n if (includeDatabaseUrl) {\n env.DATABASE_URL = databaseUrl\n console.log(`Setting DATABASE_URL=${databaseUrl}`)\n }\n\n // Parse and spawn the command\n const commandParts = command.trim().split(/\\s+/)\n this.childProcess = spawn(commandParts[0], commandParts.slice(1), {\n env,\n stdio: 'inherit',\n })\n\n this.childProcess.on('error', (error) => {\n console.error('Error running command:', error)\n // If subprocess fails to start, shutdown the server\n console.log('Subprocess failed to start, shutting down...')\n this.onExit(1)\n })\n\n this.childProcess.on('close', (code) => {\n console.log(`Command exited with code ${code}`)\n this.childProcess = null\n\n // If child process exits with non-zero code, notify parent\n if (code !== null && code !== 0) {\n console.log(\n `Child process failed with exit code ${code}, shutting down...`,\n )\n this.onExit(code)\n }\n })\n }\n\n terminate(timeout: number): void {\n if (this.childProcess) {\n console.log('Terminating child process...')\n this.childProcess.kill('SIGTERM')\n\n // Give it a moment to exit gracefully, then force kill if needed\n setTimeout(() => {\n if (this.childProcess && !this.childProcess.killed) {\n console.log('Force killing child process...')\n this.childProcess.kill('SIGKILL')\n }\n }, timeout)\n }\n }\n}\n\n// Main execution\nasync function main() {\n // Show help and exit if requested\n if (args.values.help) {\n console.log(help)\n process.exit(0)\n }\n\n try {\n const config = PGLiteServerRunner.parseConfig()\n const serverRunner = new PGLiteServerRunner(config)\n await serverRunner.start()\n } catch (error) {\n console.error('Unhandled error:', error)\n process.exit(1)\n }\n}\n\n// Run the main function\nmain()\n","import type { PGlite } from '@electric-sql/pglite'\nimport { createServer, Server, Socket } from 'net'\n\n// Connection queue timeout in milliseconds\nexport const CONNECTION_QUEUE_TIMEOUT = 60000 // 60 seconds\n\n/**\n * Options for creating a PGLiteSocketHandler\n */\nexport interface PGLiteSocketHandlerOptions {\n /** The PGlite database instance */\n db: PGlite\n /** Whether to close the socket when detached (default: false) */\n closeOnDetach?: boolean\n /** Print the incoming and outgoing data to the console in hex and ascii */\n inspect?: boolean\n /** Enable debug logging of method calls */\n debug?: boolean\n}\n\n/**\n * Low-level handler for a single socket connection to PGLite\n * Handles the raw protocol communication between a socket and PGLite\n */\nexport class PGLiteSocketHandler extends EventTarget {\n readonly db: PGlite\n private socket: Socket | null = null\n private active = false\n private closeOnDetach: boolean\n private resolveLock?: () => void\n private rejectLock?: (err: Error) => void\n private inspect: boolean\n private debug: boolean\n private readonly id: number\n\n // Static counter for generating unique handler IDs\n private static nextHandlerId = 1\n\n /**\n * Create a new PGLiteSocketHandler\n * @param options Options for the handler\n */\n constructor(options: PGLiteSocketHandlerOptions) {\n super()\n this.db = options.db\n this.closeOnDetach = options.closeOnDetach ?? false\n this.inspect = options.inspect ?? false\n this.debug = options.debug ?? false\n this.id = PGLiteSocketHandler.nextHandlerId++\n\n this.log('constructor: created new handler')\n }\n\n /**\n * Get the unique ID of this handler\n */\n public get handlerId(): number {\n return this.id\n }\n\n /**\n * Log a message if debug is enabled\n * @private\n */\n private log(message: string, ...args: any[]): void {\n if (this.debug) {\n console.log(`[PGLiteSocketHandler#${this.id}] ${message}`, ...args)\n }\n }\n\n /**\n * Attach a socket to this handler\n * @param socket The socket to attach\n * @returns this handler instance\n * @throws Error if a socket is already attached\n */\n public async attach(socket: Socket): Promise<PGLiteSocketHandler> {\n this.log(\n `attach: attaching socket from ${socket.remoteAddress}:${socket.remotePort}`,\n )\n\n if (this.socket) {\n throw new Error('Socket already attached')\n }\n\n this.socket = socket\n this.active = true\n\n // Ensure the PGlite instance is ready\n this.log(`attach: waiting for PGlite to be ready`)\n await this.db.waitReady\n\n // Hold the lock on the PGlite instance\n this.log(`attach: acquiring exclusive lock on PGlite instance`)\n await new Promise<void>((resolve) => {\n this.db.runExclusive(() => {\n // Ensure we have the lock on the PGlite instance\n resolve()\n\n // Use a promise to hold the lock on the PGlite instance\n // this can be resolved or rejected by the handler to release the lock\n return new Promise<void>((resolveLock, rejectLock) => {\n this.resolveLock = resolveLock\n this.rejectLock = rejectLock\n })\n })\n })\n\n // Setup event handlers\n this.log(`attach: setting up socket event handlers`)\n socket.on('data', async (data) => {\n try {\n const result = await this.handleData(data)\n this.log(`socket on data sent: ${result} bytes`)\n } catch (err) {\n this.log('socket on data error: ', err)\n }\n })\n socket.on('error', (err) => this.handleError(err))\n socket.on('close', () => this.handleClose())\n\n return this\n }\n\n /**\n * Detach the current socket from this handler\n * @param close Whether to close the socket when detaching (overrides constructor option)\n * @returns this handler instance\n */\n public detach(close?: boolean): PGLiteSocketHandler {\n this.log(`detach: detaching socket, close=${close ?? this.closeOnDetach}`)\n\n if (!this.socket) {\n this.log(`detach: no socket attached, nothing to do`)\n return this\n }\n\n // Remove all listeners\n this.socket.removeAllListeners('data')\n this.socket.removeAllListeners('error')\n this.socket.removeAllListeners('close')\n\n // Close the socket if requested\n if (close ?? this.closeOnDetach) {\n if (this.socket.writable) {\n this.log(`detach: closing socket`)\n this.socket.end()\n this.socket.destroy()\n }\n }\n\n // Release the lock on the PGlite instance\n this.log(`detach: releasing exclusive lock on PGlite instance`)\n this.resolveLock?.()\n\n this.socket = null\n this.active = false\n return this\n }\n\n /**\n * Check if a socket is currently attached\n */\n public get isAttached(): boolean {\n return this.socket !== null\n }\n\n /**\n * Handle incoming data from the socket\n */\n private async handleData(data: Buffer): Promise<number> {\n if (!this.socket || !this.active) {\n this.log(`handleData: no active socket, ignoring data`)\n return new Promise((_, reject) => reject(`no active socket`))\n }\n\n this.log(`handleData: received ${data.length} bytes`)\n\n // Print the incoming data to the console\n this.inspectData('incoming', data)\n\n try {\n // Process the raw protocol data\n this.log(`handleData: sending data to PGlite for processing`)\n const result = await this.db.execProtocolRaw(new Uint8Array(data))\n\n this.log(`handleData: received ${result.length} bytes from PGlite`)\n\n // Print the outgoing data to the console\n this.inspectData('outgoing', result)\n\n // Send the result back if the socket is still connected\n if (this.socket && this.socket.writable && this.active) {\n if (result.length <= 0) {\n this.log(`handleData: cowardly refusing to send empty packet`)\n return new Promise((_, reject) => reject('no data'))\n }\n\n const promise = new Promise<number>((resolve, reject) => {\n this.log(`handleData: writing response to socket`)\n if (this.socket) {\n this.socket.write(Buffer.from(result), (err?: Error) => {\n if (err) {\n reject(`Error while writing to the socket ${err.toString()}`)\n } else {\n resolve(result.length)\n }\n })\n } else {\n reject(`No socket`)\n }\n })\n\n // Emit data event with byte sizes\n this.dispatchEvent(\n new CustomEvent('data', {\n detail: { incoming: data.length, outgoing: result.length },\n }),\n )\n return promise\n } else {\n this.log(\n `handleData: socket no longer writable or active, discarding response`,\n )\n return new Promise((_, reject) =>\n reject(`No socket, not active or not writeable`),\n )\n }\n } catch (err) {\n this.log(`handleData: error processing data:`, err)\n this.handleError(err as Error)\n return new Promise((_, reject) =>\n reject(`Error while processing data ${(err as Error).toString()}`),\n )\n }\n }\n\n /**\n * Handle errors from the socket\n */\n private handleError(err: Error): void {\n this.log(`handleError:`, err)\n\n // Emit error event\n this.dispatchEvent(new CustomEvent('error', { detail: err }))\n\n // Reject the lock on the PGlite instance\n this.log(`handleError: rejecting exclusive lock on PGlite instance`)\n this.rejectLock?.(err)\n this.resolveLock = undefined\n this.rejectLock = undefined\n\n // Close the connection on error\n this.detach(true)\n }\n\n /**\n * Handle socket close event\n */\n private handleClose(): void {\n this.log(`handleClose: socket closed`)\n\n this.dispatchEvent(new CustomEvent('close'))\n this.detach(false) // Already closed, just clean up\n }\n\n /**\n * Print data in hex and ascii to the console\n */\n private inspectData(\n direction: 'incoming' | 'outgoing',\n data: Buffer | Uint8Array,\n ): void {\n if (!this.inspect) return\n console.log('-'.repeat(75))\n if (direction === 'incoming') {\n console.log('-> incoming', data.length, 'bytes')\n } else {\n console.log('<- outgoing', data.length, 'bytes')\n }\n\n // Process 16 bytes per line\n for (let offset = 0; offset < data.length; offset += 16) {\n // Calculate current chunk size (may be less than 16 for the last chunk)\n const chunkSize = Math.min(16, data.length - offset)\n\n // Build the hex representation\n let hexPart = ''\n for (let i = 0; i < 16; i++) {\n if (i < chunkSize) {\n const byte = data[offset + i]\n hexPart += byte.toString(16).padStart(2, '0') + ' '\n } else {\n hexPart += ' ' // 3 spaces for missing bytes\n }\n }\n\n // Build the ASCII representation\n let asciiPart = ''\n for (let i = 0; i < chunkSize; i++) {\n const byte = data[offset + i]\n // Use printable characters (32-126), replace others with a dot\n asciiPart += byte >= 32 && byte <= 126 ? String.fromCharCode(byte) : '.'\n }\n\n // Print the line with offset in hex, hex values, and ASCII representation\n console.log(\n `${offset.toString(16).padStart(8, '0')} ${hexPart} ${asciiPart}`,\n )\n }\n }\n}\n\n/**\n * Represents a queued connection with timeout\n */\ninterface QueuedConnection {\n socket: Socket\n clientInfo: {\n clientAddress: string\n clientPort: number\n }\n timeoutId: NodeJS.Timeout\n}\n\n/**\n * Options for creating a PGLiteSocketServer\n */\nexport interface PGLiteSocketServerOptions {\n /** The PGlite database instance */\n db: PGlite\n /** The port to listen on (default: 5432) */\n port?: number\n /** The host to bind to (default: 127.0.0.1) */\n host?: string\n /** Unix socket path to bind to (default: undefined). If specified, takes precedence over host:port */\n path?: string\n /** Print the incoming and outgoing data to the console in hex and ascii */\n inspect?: boolean\n /** Connection queue timeout in milliseconds (default: 10000) */\n connectionQueueTimeout?: number\n /** Enable debug logging of method calls */\n debug?: boolean\n}\n\n/**\n * High-level server that manages socket connections to PGLite\n * Creates and manages a TCP server and handles client connections\n */\nexport class PGLiteSocketServer extends EventTarget {\n readonly db: PGlite\n private server: Server | null = null\n private port?: number\n private host?: string\n private path?: string\n private active = false\n private inspect: boolean\n private debug: boolean\n private connectionQueueTimeout: number\n private activeHandler: PGLiteSocketHandler | null = null\n private connectionQueue: QueuedConnection[] = []\n private handlerCount: number = 0\n\n /**\n * Create a new PGLiteSocketServer\n * @param options Options for the server\n */\n constructor(options: PGLiteSocketServerOptions) {\n super()\n this.db = options.db\n if (options.path) {\n this.path = options.path\n } else {\n if (typeof options.port === 'number') {\n // Keep port undefined on port 0, will be set by the OS when we start the server.\n this.port = options.port ?? options.port\n } else {\n this.port = 5432\n }\n this.host = options.host || '127.0.0.1'\n }\n this.inspect = options.inspect ?? false\n this.debug = options.debug ?? false\n this.connectionQueueTimeout =\n options.connectionQueueTimeout ?? CONNECTION_QUEUE_TIMEOUT\n\n this.log(`constructor: created server on ${this.host}:${this.port}`)\n this.log(\n `constructor: connection queue timeout: ${this.connectionQueueTimeout}ms`,\n )\n }\n\n /**\n * Log a message if debug is enabled\n * @private\n */\n private log(message: string, ...args: any[]): void {\n if (this.debug) {\n console.log(`[PGLiteSocketServer] ${message}`, ...args)\n }\n }\n\n /**\n * Start the socket server\n * @returns Promise that resolves when the server is listening\n */\n public async start(): Promise<void> {\n this.log(`start: starting server on ${this.getServerConn()}`)\n\n if (this.server) {\n throw new Error('Socket server already started')\n }\n\n this.active = true\n this.server = createServer((socket) => this.handleConnection(socket))\n\n return new Promise<void>((resolve, reject) => {\n if (!this.server) return reject(new Error('Server not initialized'))\n\n this.server.on('error', (err) => {\n this.log(`start: server error:`, err)\n this.dispatchEvent(new CustomEvent('error', { detail: err }))\n reject(err)\n })\n\n if (this.path) {\n this.server.listen(this.path, () => {\n this.log(`start: server listening on ${this.getServerConn()}`)\n this.dispatchEvent(\n new CustomEvent('listening', {\n detail: { path: this.path },\n }),\n )\n resolve()\n })\n } else {\n const server = this.server\n server.listen(this.port, this.host, () => {\n const address = server.address()\n // We are not using pipes, so return type should be AddressInfo\n if (address === null || typeof address !== 'object') {\n throw Error('Expected address info')\n }\n // Assign the new port number\n this.port = address.port\n this.log(`start: server listening on ${this.getServerConn()}`)\n this.dispatchEvent(\n new CustomEvent('listening', {\n detail: { port: this.port, host: this.host },\n }),\n )\n resolve()\n })\n }\n })\n }\n\n public getServerConn(): string {\n if (this.path) return this.path\n return `${this.host}:${this.port}`\n }\n\n /**\n * Stop the socket server\n * @returns Promise that resolves when the server is closed\n */\n public async stop(): Promise<void> {\n this.log(`stop: stopping server`)\n\n this.active = false\n\n // Clear connection queue\n this.log(\n `stop: clearing connection queue (${this.connectionQueue.length} connections)`,\n )\n\n this.connectionQueue.forEach((queuedConn) => {\n clearTimeout(queuedConn.timeoutId)\n if (queuedConn.socket.writable) {\n this.log(\n `stop: closing queued connection from ${queuedConn.clientInfo.clientAddress}:${queuedConn.clientInfo.clientPort}`,\n )\n queuedConn.socket.end()\n }\n })\n this.connectionQueue = []\n\n // Detach active handler if exists\n if (this.activeHandler) {\n this.log(`stop: detaching active handler #${this.activeHandlerId}`)\n this.activeHandler.detach(true)\n this.activeHandler = null\n }\n\n if (!this.server) {\n this.log(`stop: server not running, nothing to do`)\n return Promise.resolve()\n }\n\n return new Promise<void>((resolve) => {\n if (!this.server) return resolve()\n\n this.server.close(() => {\n this.log(`stop: server closed`)\n this.server = null\n this.dispatchEvent(new CustomEvent('close'))\n resolve()\n })\n })\n }\n\n /**\n * Get the active handler ID, or null if no active handler\n */\n private get activeHandlerId(): number | null {\n return this.activeHandler?.handlerId ?? null\n }\n\n /**\n * Handle a new client connection\n */\n private async handleConnection(socket: Socket): Promise<void> {\n const clientInfo = {\n clientAddress: socket.remoteAddress || 'unknown',\n clientPort: socket.remotePort || 0,\n }\n\n this.log(\n `handleConnection: new connection from ${clientInfo.clientAddress}:${clientInfo.clientPort}`,\n )\n\n // If server is not active, close the connection immediately\n if (!this.active) {\n this.log(`handleConnection: server not active, closing connection`)\n socket.end()\n return\n }\n\n // If we don't have an active handler or it's not attached, we can use this connection immediately\n if (!this.activeHandler || !this.activeHandler.isAttached) {\n this.log(`handleConnection: no active handler, attaching socket directly`)\n this.dispatchEvent(new CustomEvent('connection', { detail: clientInfo }))\n await this.attachSocketToNewHandler(socket, clientInfo)\n return\n }\n\n // Otherwise, queue the connection\n this.log(\n `handleConnection: active handler #${this.activeHandlerId} exists, queueing connection`,\n )\n this.enqueueConnection(socket, clientInfo)\n }\n\n /**\n * Add a connection to the queue\n */\n private enqueueConnection(\n socket: Socket,\n clientInfo: { clientAddress: string; clientPort: number },\n ): void {\n this.log(\n `enqueueConnection: queueing connection from ${clientInfo.clientAddress}:${clientInfo.clientPort}, timeout: ${this.connectionQueueTimeout}ms`,\n )\n\n // Set a timeout for this queued connection\n const timeoutId = setTimeout(() => {\n this.log(\n `enqueueConnection: timeout for connection from ${clientInfo.clientAddress}:${clientInfo.clientPort}`,\n )\n\n // Remove from queue\n this.connectionQueue = this.connectionQueue.filter(\n (queuedConn) => queuedConn.socket !== socket,\n )\n\n // End the connection if it's still open\n if (socket.writable) {\n this.log(`enqueueConnection: closing timed out connection`)\n socket.end()\n }\n\n this.dispatchEvent(\n new CustomEvent('queueTimeout', {\n detail: { ...clientInfo, queueSize: this.connectionQueue.length },\n }),\n )\n }, this.connectionQueueTimeout)\n\n // Add to queue\n this.connectionQueue.push({ socket, clientInfo, timeoutId })\n\n this.log(\n `enqueueConnection: connection queued, queue size: ${this.connectionQueue.length}`,\n )\n\n this.dispatchEvent(\n new CustomEvent('queuedConnection', {\n detail: { ...clientInfo, queueSize: this.connectionQueue.length },\n }),\n )\n }\n\n /**\n * Process the next connection in the queue\n */\n private processNextInQueue(): void {\n this.log(\n `processNextInQueue: processing next connection, queue size: ${this.connectionQueue.length}`,\n )\n\n // No connections in queue or server not active\n if (this.connectionQueue.length === 0 || !this.active) {\n this.log(\n `processNextInQueue: no connections in queue or server not active, nothing to do`,\n )\n return\n }\n\n // Get the next connection\n const nextConn = this.connectionQueue.shift()\n if (!nextConn) return\n\n this.log(\n `processNextInQueue: processing connection from ${nextConn.clientInfo.clientAddress}:${nextConn.clientInfo.clientPort}`,\n )\n\n // Clear the timeout\n clearTimeout(nextConn.timeoutId)\n\n // Check if the socket is still valid\n if (!nextConn.socket.writable) {\n this.log(\n `processNextInQueue: socket no longer writable, skipping to next connection`,\n )\n // Socket closed while waiting, process next in queue\n this.processNextInQueue()\n return\n }\n\n // Attach this socket to a new handler\n this.attachSocketToNewHandler(nextConn.socket, nextConn.clientInfo).catch(\n (err) => {\n this.log(`processNextInQueue: error attaching socket:`, err)\n this.dispatchEvent(new CustomEvent('error', { detail: err }))\n // Try the next connection\n this.processNextInQueue()\n },\n )\n }\n\n /**\n * Attach a socket to a new handler\n */\n private async attachSocketToNewHandler(\n socket: Socket,\n clientInfo: { clientAddress: string; clientPort: number },\n ): Promise<void> {\n this.handlerCount++\n\n this.log(\n `attachSocketToNewHandler: creating new handler for ${clientInfo.clientAddress}:${clientInfo.clientPort} (handler #${this.handlerCount})`,\n )\n\n // Create a new handler for this connection\n const handler = new PGLiteSocketHandler({\n db: this.db,\n closeOnDetach: true,\n inspect: this.inspect,\n debug: this.debug,\n })\n\n // Forward error events from the handler\n handler.addEventListener('error', (event) => {\n this.log(\n `handler #${handler.handlerId}: error from handler:`,\n (event as CustomEvent<Error>).detail,\n )\n this.dispatchEvent(\n new CustomEvent('error', {\n detail: (event as CustomEvent<Error>).detail,\n }),\n )\n })\n\n // Handle close event to process next queued connection\n handler.addEventListener('close', () => {\n this.log(`handler #${handler.handlerId}: closed`)\n\n // If this is our active handler, clear it\n if (this.activeHandler === handler) {\n this.log(\n `handler #${handler.handlerId}: was active handler, processing next connection in queue`,\n )\n this.activeHandler = null\n // Process next connection in queue\n this.processNextInQueue()\n }\n })\n\n try {\n // Set as active handler\n this.activeHandler = handler\n\n this.log(`handler #${handler.handlerId}: attaching socket`)\n\n // Attach the socket to the handler\n await handler.attach(socket)\n\n this.dispatchEvent(new CustomEvent('connection', { detail: clientInfo }))\n } catch (err) {\n // If there was an error attaching, clean up\n this.log(`handler #${handler.handlerId}: error attaching socket:`, err)\n this.activeHandler = null\n if (socket.writable) {\n socket.end()\n }\n throw err\n }\n }\n}\n"],"mappings":"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","names":["import_pglite","import_net","CONNECTION_QUEUE_TIMEOUT","_PGLiteSocketHandler","options","message","args","socket","resolve","resolveLock","rejectLock","data","result","err","close","_","reject","promise","direction","offset","chunkSize","hexPart","i","byte","asciiPart","PGLiteSocketHandler","PGLiteSocketServer","server","address","queuedConn","clientInfo","timeoutId","nextConn","handler","event","import_node_util","import_node_child_process","args","help","PGLiteServerRunner","config","extensionsArg","host","port","path","socketDir","extensions","builtInExtensions","name","ext","packagePath","exportName","error","event","detail","databaseUrl","clientAddress","clientPort","PGLiteSocketServer","SubprocessManager","exitCode","onExit","command","includeDatabaseUrl","env","commandParts","code","timeout","main"]}
{"version":3,"sources":["../../src/scripts/server.ts","../../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { PGlite, DebugLevel } from '@electric-sql/pglite'\nimport type { Extension, Extensions } from '@electric-sql/pglite'\nimport { PGLiteSocketServer } from '../index'\nimport { parseArgs } from 'node:util'\nimport { spawn, ChildProcess } from 'node:child_process'\n\n// Define command line argument options\nconst args = parseArgs({\n options: {\n db: {\n type: 'string',\n short: 'd',\n default: 'memory://',\n help: 'Database path (relative or absolute). Use memory:// for in-memory database.',\n },\n port: {\n type: 'string',\n short: 'p',\n default: '5432',\n help: 'Port to listen on',\n },\n host: {\n type: 'string',\n short: 'h',\n default: '127.0.0.1',\n help: 'Host to bind to',\n },\n path: {\n type: 'string',\n short: 'u',\n default: undefined,\n help: 'unix socket to bind to. Takes precedence over host:port',\n },\n debug: {\n type: 'string',\n short: 'v',\n default: '0',\n help: 'Debug level (0-5)',\n },\n extensions: {\n type: 'string',\n short: 'e',\n default: undefined,\n help: 'Comma-separated list of extensions to load (e.g., vector,pgcrypto)',\n },\n run: {\n type: 'string',\n short: 'r',\n default: undefined,\n help: 'Command to run after server starts',\n },\n 'include-database-url': {\n type: 'boolean',\n default: false,\n help: 'Include DATABASE_URL in the environment of the subprocess',\n },\n 'shutdown-timeout': {\n type: 'string',\n default: '5000',\n help: 'Timeout in milliseconds for graceful subprocess shutdown (default: 5000)',\n },\n 'max-connections': {\n type: 'string',\n short: 'm',\n default: '1',\n help: 'Maximum concurrent connections (default: 1)',\n },\n help: {\n type: 'boolean',\n short: '?',\n default: false,\n help: 'Show help',\n },\n },\n})\n\nconst help = `PGlite Socket Server\nUsage: pglite-server [options]\n\nOptions:\n -d, --db=PATH Database path (default: memory://)\n -p, --port=PORT Port to listen on (default: 5432)\n -h, --host=HOST Host to bind to (default: 127.0.0.1)\n -u, --path=UNIX Unix socket to bind to (default: undefined). Takes precedence over host:port\n -v, --debug=LEVEL Debug level 0-5 (default: 0)\n -e, --extensions=LIST Comma-separated list of extensions to load\n Formats: vector, pgcrypto (built-in/contrib)\n @org/package/path:exportedName (npm package)\n -r, --run=COMMAND Command to run after server starts\n --include-database-url Include DATABASE_URL in subprocess environment\n --shutdown-timeout=MS Timeout for graceful subprocess shutdown in ms (default: 5000)\n -m, --max-connections=N Maximum concurrent connections (default is no concurrency: 1)\n`\n\ninterface ServerConfig {\n dbPath: string\n port: number\n host: string\n path?: string\n debugLevel: DebugLevel\n extensionNames?: string[]\n runCommand?: string\n includeDatabaseUrl: boolean\n shutdownTimeout: number\n maxConnections: number\n}\n\nclass PGLiteServerRunner {\n private config: ServerConfig\n private db: PGlite | null = null\n private server: PGLiteSocketServer | null = null\n private subprocessManager: SubprocessManager | null = null\n\n constructor(config: ServerConfig) {\n this.config = config\n }\n\n static parseConfig(): ServerConfig {\n const extensionsArg = args.values.extensions as string | undefined\n return {\n dbPath: args.values.db as string,\n port: parseInt(args.values.port as string, 10),\n host: args.values.host as string,\n path: args.values.path as string,\n debugLevel: parseInt(args.values.debug as string, 10) as DebugLevel,\n extensionNames: extensionsArg\n ? extensionsArg.split(',').map((e) => e.trim())\n : undefined,\n runCommand: args.values.run as string,\n includeDatabaseUrl: args.values['include-database-url'] as boolean,\n shutdownTimeout: parseInt(args.values['shutdown-timeout'] as string, 10),\n maxConnections: parseInt(args.values['max-connections'] as string, 10),\n }\n }\n\n private createDatabaseUrl(): string {\n const { host, port, path } = this.config\n\n if (path) {\n // Unix socket connection\n const socketDir = path.endsWith('/.s.PGSQL.5432')\n ? path.slice(0, -13)\n : path\n return `postgresql://postgres:postgres@/postgres?host=${encodeURIComponent(socketDir)}`\n } else {\n // TCP connection\n return `postgresql://postgres:postgres@${host}:${port}/postgres`\n }\n }\n\n private async importExtensions(): Promise<Extensions | undefined> {\n if (!this.config.extensionNames?.length) {\n return undefined\n }\n\n const extensions: Extensions = {}\n\n // Built-in extensions that are not in contrib\n const builtInExtensions = [\n 'vector',\n 'live',\n 'pg_hashids',\n 'pg_ivm',\n 'pg_uuidv7',\n 'pgtap',\n ]\n\n for (const name of this.config.extensionNames) {\n let ext: Extension | null = null\n\n try {\n // Check if this is a custom package path (contains ':')\n // Format: @org/package/path:exportedName or package/path:exportedName\n if (name.includes(':')) {\n const [packagePath, exportName] = name.split(':')\n if (!packagePath || !exportName) {\n throw new Error(\n `Invalid extension format '${name}'. Expected: package/path:exportedName`,\n )\n }\n const mod = await import(packagePath)\n ext = mod[exportName] as Extension\n if (ext) {\n extensions[exportName] = ext\n console.log(\n `Imported extension '${exportName}' from '${packagePath}'`,\n )\n }\n } else if (builtInExtensions.includes(name)) {\n // Built-in extension (e.g., @electric-sql/pglite/vector)\n const mod = await import(`@electric-sql/pglite/${name}`)\n ext = mod[name] as Extension\n if (ext) {\n extensions[name] = ext\n console.log(`Imported extension: ${name}`)\n }\n } else {\n // Try contrib first (e.g., @electric-sql/pglite/contrib/pgcrypto)\n try {\n const mod = await import(`@electric-sql/pglite/contrib/${name}`)\n ext = mod[name] as Extension\n } catch {\n // Fall back to external package (e.g., @electric-sql/pglite-<extension>)\n const mod = await import(`@electric-sql/pglite-${name}`)\n ext = mod[name] as Extension\n }\n if (ext) {\n extensions[name] = ext\n console.log(`Imported extension: ${name}`)\n }\n }\n } catch (error) {\n console.error(`Failed to import extension '${name}':`, error)\n throw new Error(`Failed to import extension '${name}'`)\n }\n }\n\n return Object.keys(extensions).length > 0 ? extensions : undefined\n }\n\n private async initializeDatabase(): Promise<void> {\n console.log(`Initializing PGLite with database: ${this.config.dbPath}`)\n console.log(`Debug level: ${this.config.debugLevel}`)\n\n const extensions = await this.importExtensions()\n\n this.db = new PGlite(this.config.dbPath, {\n debug: this.config.debugLevel,\n extensions,\n })\n await this.db.waitReady\n console.log('PGlite database initialized')\n }\n\n private setupServerEventHandlers(): void {\n if (!this.server || !this.subprocessManager) {\n throw new Error('Server or subprocess manager not initialized')\n }\n\n this.server.addEventListener('listening', (event) => {\n const detail = (\n event as CustomEvent<{ port: number; host: string } | { host: string }>\n ).detail\n console.log(`PGLiteSocketServer listening on ${JSON.stringify(detail)}`)\n\n // Run the command after server starts listening\n if (this.config.runCommand && this.subprocessManager) {\n const databaseUrl = this.createDatabaseUrl()\n this.subprocessManager.spawn(\n this.config.runCommand,\n databaseUrl,\n this.config.includeDatabaseUrl,\n )\n }\n })\n\n this.server.addEventListener('connection', (event) => {\n const { clientAddress, clientPort } = (\n event as CustomEvent<{ clientAddress: string; clientPort: number }>\n ).detail\n console.log(`Client connected from ${clientAddress}:${clientPort}`)\n })\n\n this.server.addEventListener('error', (event) => {\n const error = (event as CustomEvent<Error>).detail\n console.error('Socket server error:', error)\n })\n }\n\n private setupSignalHandlers(): void {\n process.on('SIGINT', () => this.shutdown())\n process.on('SIGTERM', () => this.shutdown())\n }\n\n async start(): Promise<void> {\n try {\n // Initialize database\n await this.initializeDatabase()\n\n if (!this.db) {\n throw new Error('Database initialization failed')\n }\n\n // Create and setup the socket server\n this.server = new PGLiteSocketServer({\n db: this.db,\n port: this.config.port,\n host: this.config.host,\n path: this.config.path,\n inspect: this.config.debugLevel > 0,\n maxConnections: this.config.maxConnections,\n })\n\n // Create subprocess manager\n this.subprocessManager = new SubprocessManager((exitCode) => {\n this.shutdown(exitCode)\n })\n\n // Setup event handlers\n this.setupServerEventHandlers()\n this.setupSignalHandlers()\n\n // Start the server\n await this.server.start()\n } catch (error) {\n console.error('Failed to start PGLiteSocketServer:', error)\n throw error\n }\n }\n\n async shutdown(exitCode: number = 0): Promise<void> {\n console.log('\\nShutting down PGLiteSocketServer...')\n\n // Terminate subprocess if running\n if (this.subprocessManager) {\n this.subprocessManager.terminate(this.config.shutdownTimeout)\n }\n\n // Stop server\n if (this.server) {\n await this.server.stop()\n }\n\n // Close database\n if (this.db) {\n await this.db.close()\n }\n\n console.log('Server stopped')\n process.exit(exitCode)\n }\n}\n\nclass SubprocessManager {\n private childProcess: ChildProcess | null = null\n private onExit: (code: number) => void\n\n constructor(onExit: (code: number) => void) {\n this.onExit = onExit\n }\n\n get process(): ChildProcess | null {\n return this.childProcess\n }\n\n spawn(\n command: string,\n databaseUrl: string,\n includeDatabaseUrl: boolean,\n ): void {\n console.log(`Running command: ${command}`)\n\n // Prepare environment variables\n const env = { ...process.env }\n if (includeDatabaseUrl) {\n env.DATABASE_URL = databaseUrl\n console.log(`Setting DATABASE_URL=${databaseUrl}`)\n }\n\n // Parse and spawn the command\n const commandParts = command.trim().split(/\\s+/)\n this.childProcess = spawn(commandParts[0], commandParts.slice(1), {\n env,\n stdio: 'inherit',\n })\n\n this.childProcess.on('error', (error) => {\n console.error('Error running command:', error)\n // If subprocess fails to start, shutdown the server\n console.log('Subprocess failed to start, shutting down...')\n this.onExit(1)\n })\n\n this.childProcess.on('close', (code) => {\n console.log(`Command exited with code ${code}`)\n this.childProcess = null\n\n // If child process exits with non-zero code, notify parent\n if (code !== null && code !== 0) {\n console.log(\n `Child process failed with exit code ${code}, shutting down...`,\n )\n this.onExit(code)\n }\n })\n }\n\n terminate(timeout: number): void {\n if (this.childProcess) {\n console.log('Terminating child process...')\n this.childProcess.kill('SIGTERM')\n\n // Give it a moment to exit gracefully, then force kill if needed\n setTimeout(() => {\n if (this.childProcess && !this.childProcess.killed) {\n console.log('Force killing child process...')\n this.childProcess.kill('SIGKILL')\n }\n }, timeout)\n }\n }\n}\n\n// Main execution\nasync function main() {\n // Show help and exit if requested\n if (args.values.help) {\n console.log(help)\n process.exit(0)\n }\n\n try {\n const config = PGLiteServerRunner.parseConfig()\n const serverRunner = new PGLiteServerRunner(config)\n await serverRunner.start()\n } catch (error) {\n console.error('Unhandled error:', error)\n process.exit(1)\n }\n}\n\n// Run the main function\nmain()\n","import type { PGlite } from '@electric-sql/pglite'\nimport { type Server, type Socket, createServer } from 'net'\n\n// Connection queue timeout in milliseconds\nexport const CONNECTION_QUEUE_TIMEOUT = 60000 // 60 seconds\n\n/**\n * Represents a queued query waiting for PGlite access\n */\ninterface QueuedQuery {\n handlerId: number\n message: Uint8Array\n resolve: (result: Uint8Array) => void\n reject: (error: Error) => void\n timestamp: number\n}\n\n/**\n * Global query queue manager\n * Ensures only one query executes at a time in PGlite\n */\nclass QueryQueueManager {\n private queue: QueuedQuery[] = []\n private processing = false\n private db: PGlite\n private debug: boolean\n private lastHandlerId: null | number = null\n\n constructor(db: PGlite, debug = false) {\n this.db = db\n this.debug = debug\n }\n\n private log(message: string, ...args: any[]): void {\n if (this.debug) {\n console.log(`[QueryQueueManager] ${message}`, ...args)\n }\n }\n\n async enqueue(handlerId: number, message: Uint8Array): Promise<Uint8Array> {\n return new Promise((resolve, reject) => {\n const query: QueuedQuery = {\n handlerId,\n message,\n resolve,\n reject,\n timestamp: Date.now(),\n }\n\n this.queue.push(query)\n this.log(\n `enqueued query from handler #${handlerId}, queue size: ${this.queue.length}`,\n )\n\n // Process queue if not already processing\n if (!this.processing) {\n this.processQueue()\n }\n })\n }\n\n private async processQueue(): Promise<void> {\n if (this.processing || this.queue.length === 0) {\n return\n }\n\n this.processing = true\n\n while (this.queue.length > 0) {\n let query\n\n if (this.db.isInTransaction() && this.lastHandlerId) {\n const i = this.queue.findIndex(\n (q) => q.handlerId === this.lastHandlerId,\n )\n if (i === -1) {\n // we didn't find any other query from the same client!\n this.log(\n `transaction started, but no query from the same handler id found in queue`,\n this.lastHandlerId,\n )\n query = null\n } else {\n query = this.queue.splice(i, 1)[0]\n }\n } else {\n query = this.queue.shift()\n }\n if (!query) break\n\n const waitTime = Date.now() - query.timestamp\n this.log(\n `processing query from handler #${query.handlerId} (waited ${waitTime}ms)`,\n )\n\n try {\n // Execute the query with exclusive access to PGlite\n const result = await this.db.runExclusive(async () => {\n return await this.db.execProtocolRaw(query.message)\n })\n\n this.log(\n `query from handler #${query.handlerId} completed, ${result.length} bytes`,\n )\n this.lastHandlerId = query.handlerId\n query.resolve(result)\n } catch (error) {\n this.log(`query from handler #${query.handlerId} failed:`, error)\n query.reject(error as Error)\n }\n }\n\n this.processing = false\n this.log(`queue processing complete, queue length is`, this.queue.length)\n }\n\n getQueueLength(): number {\n return this.queue.length\n }\n\n clearQueueForHandler(handlerId: number): void {\n const before = this.queue.length\n this.queue = this.queue.filter((q) => {\n if (q.handlerId === handlerId) {\n q.reject(new Error('Handler disconnected'))\n return false\n }\n return true\n })\n const removed = before - this.queue.length\n if (removed > 0) {\n this.log(`cleared ${removed} queries for handler #${handlerId}`)\n }\n }\n\n async clearTransactionIfNeeded(handlerId: number): Promise<void> {\n if (this.db.isInTransaction() && this.lastHandlerId === handlerId) {\n await this.db.exec('ROLLBACK')\n this.lastHandlerId = null\n await this.processQueue()\n }\n }\n}\n\n/**\n * Options for creating a PGLiteSocketHandler\n */\nexport interface PGLiteSocketHandlerOptions {\n /** The query queue manager */\n queryQueue: QueryQueueManager\n /** Whether to close the socket when detached (default: false) */\n closeOnDetach?: boolean\n /** Print the incoming and outgoing data to the console in hex and ascii */\n inspect?: boolean\n /** Enable debug logging of method calls */\n debug?: boolean\n /** Idle timeout in ms (0 to disable, default: 0) */\n idleTimeout?: number\n}\n\n/**\n * Handler for a single socket connection to PGlite\n * Each connection can remain open and send multiple queries\n */\nexport class PGLiteSocketHandler extends EventTarget {\n private queryQueue: QueryQueueManager\n private socket: Socket | null = null\n private active = false\n private closeOnDetach: boolean\n private inspect: boolean\n private debug: boolean\n private readonly id: number\n private messageBuffer: Buffer = Buffer.alloc(0)\n private idleTimer?: NodeJS.Timeout\n private idleTimeout: number\n private lastActivityTime: number = Date.now()\n\n // Static counter for generating unique handler IDs\n private static nextHandlerId = 1\n\n constructor(options: PGLiteSocketHandlerOptions) {\n super()\n this.queryQueue = options.queryQueue\n this.closeOnDetach = options.closeOnDetach ?? false\n this.inspect = options.inspect ?? false\n this.debug = options.debug ?? false\n this.idleTimeout = options.idleTimeout ?? 0\n this.id = PGLiteSocketHandler.nextHandlerId++\n\n this.log('constructor: created new handler')\n }\n\n public get handlerId(): number {\n return this.id\n }\n\n private log(message: string, ...args: any[]): void {\n if (this.debug) {\n console.log(`[PGLiteSocketHandler#${this.id}] ${message}`, ...args)\n }\n }\n\n public async attach(socket: Socket): Promise<PGLiteSocketHandler> {\n this.log(\n `attach: attaching socket from ${socket.remoteAddress}:${socket.remotePort}`,\n )\n\n if (this.socket) {\n throw new Error('Socket already attached')\n }\n\n this.socket = socket\n this.active = true\n this.lastActivityTime = Date.now()\n\n // Set up socket options\n socket.setNoDelay(true)\n\n // Set up idle timeout if configured\n if (this.idleTimeout > 0) {\n this.resetIdleTimer()\n }\n\n // Setup event handlers\n this.log(`attach: setting up socket event handlers`)\n\n socket.on('data', (data) => {\n this.lastActivityTime = Date.now()\n this.resetIdleTimer()\n\n setImmediate(async () => {\n try {\n await this.handleData(data)\n } catch (err) {\n this.log('socket on data error: ', err)\n this.handleError(err as Error)\n }\n })\n })\n\n socket.on('error', (err) => {\n setImmediate(() => this.handleError(err))\n })\n\n socket.on('close', () => {\n setImmediate(() => this.handleClose())\n })\n\n this.log(`attach: socket handler ready`)\n return this\n }\n\n private resetIdleTimer(): void {\n if (this.idleTimeout <= 0) return\n\n if (this.idleTimer) {\n clearTimeout(this.idleTimer)\n }\n\n this.idleTimer = setTimeout(() => {\n const idleTime = Date.now() - this.lastActivityTime\n this.log(`idle timeout after ${idleTime}ms`)\n this.handleError(new Error('Idle timeout'))\n }, this.idleTimeout)\n }\n\n public async detach(close?: boolean): Promise<PGLiteSocketHandler> {\n this.log(`detach: detaching socket, close=${close ?? this.closeOnDetach}`)\n\n if (this.idleTimer) {\n clearTimeout(this.idleTimer)\n this.idleTimer = undefined\n }\n\n // Clear any pending queries for this handler\n this.queryQueue.clearQueueForHandler(this.id)\n\n await this.queryQueue.clearTransactionIfNeeded(this.id)\n\n if (!this.socket) {\n this.log(`detach: no socket attached, nothing to do`)\n return this\n }\n\n // Remove all listeners\n this.socket.removeAllListeners('data')\n this.socket.removeAllListeners('error')\n this.socket.removeAllListeners('close')\n\n // Close the socket if requested\n if (close ?? this.closeOnDetach) {\n if (this.socket.writable) {\n this.log(`detach: closing socket`)\n try {\n this.socket.end()\n this.socket.destroy()\n } catch (err) {\n this.log(`detach: error closing socket:`, err)\n }\n }\n }\n\n this.socket = null\n this.active = false\n this.messageBuffer = Buffer.alloc(0)\n\n this.log(`detach: handler cleaned up`)\n return this\n }\n\n public get isAttached(): boolean {\n return this.socket !== null\n }\n\n private async handleData(data: Buffer): Promise<number> {\n if (!this.socket || !this.active) {\n this.log(`handleData: no active socket, ignoring data`)\n return 0\n }\n\n this.log(`handleData: received ${data.length} bytes`)\n\n // Append to buffer for message reassembly\n this.messageBuffer = Buffer.concat([this.messageBuffer, data])\n\n // Print the incoming data to the console\n this.inspectData('incoming', data)\n\n try {\n let totalProcessed = 0\n\n while (this.messageBuffer.length > 0) {\n // Determine message length\n let messageLength = 0\n let isComplete = false\n\n // Handle startup message (no type byte, just length)\n if (this.messageBuffer.length >= 4) {\n const firstInt = this.messageBuffer.readInt32BE(0)\n\n if (this.messageBuffer.length >= 8) {\n const secondInt = this.messageBuffer.readInt32BE(4)\n // PostgreSQL 3.0 protocol version\n if (secondInt === 196608 || secondInt === 0x00030000) {\n messageLength = firstInt\n isComplete = this.messageBuffer.length >= messageLength\n }\n }\n\n // Regular message (type byte + length)\n if (!isComplete && this.messageBuffer.length >= 5) {\n const msgLength = this.messageBuffer.readInt32BE(1)\n messageLength = 1 + msgLength\n isComplete = this.messageBuffer.length >= messageLength\n }\n }\n\n if (!isComplete || messageLength === 0) {\n this.log(\n `handleData: incomplete message, buffering ${this.messageBuffer.length} bytes`,\n )\n break\n }\n\n // Extract and process complete message\n const message = this.messageBuffer.slice(0, messageLength)\n this.messageBuffer = this.messageBuffer.slice(messageLength)\n\n this.log(`handleData: processing message of ${message.length} bytes`)\n\n // Check if socket is still active before processing\n if (!this.active || !this.socket) {\n this.log(`handleData: socket no longer active, stopping processing`)\n break\n }\n\n // Queue the query for execution\n // This allows multiple connections to queue queries simultaneously\n const result = await this.queryQueue.enqueue(\n this.id,\n new Uint8Array(message),\n )\n\n this.log(`handleData: received ${result.length} bytes from PGlite`)\n\n // Print the outgoing data to the console\n this.inspectData('outgoing', result)\n\n // Send response if available\n if (\n result.length > 0 &&\n this.socket &&\n this.socket.writable &&\n this.active\n ) {\n await new Promise<number>((resolve, reject) => {\n this.log(`handleData: writing response to socket`)\n if (this.socket?.writable) {\n this.socket.write(Buffer.from(result), (err?: any) => {\n if (err) {\n this.log(`handleData: error writing to socket:`, err)\n reject(err)\n } else {\n this.log(`handleData: socket sent: ${result.length} bytes`)\n resolve(result.length)\n }\n })\n } else {\n this.log(`handleData: socket no longer writable`)\n resolve(0)\n }\n }).catch((writeErr) => {\n this.log(`handleData: failed to write to socket:`, writeErr)\n throw writeErr\n })\n }\n\n totalProcessed += result.length\n }\n\n // Emit data event with byte sizes\n this.dispatchEvent(\n new CustomEvent('data', {\n detail: { incoming: data.length, outgoing: totalProcessed },\n }),\n )\n\n return totalProcessed\n } catch (err) {\n this.log(`handleData: error processing data:`, err)\n throw err\n }\n }\n\n private handleError(err: Error): void {\n if (!this.active) {\n this.log(`handleError: handler not active, ignoring error`)\n return\n }\n\n // ECONNRESET is expected behavior when clients disconnect\n if (err.message?.includes('ECONNRESET')) {\n this.log(\n `handleError: client disconnected (ECONNRESET) - normal behavior`,\n )\n } else if (err.message?.includes('Idle timeout')) {\n this.log(`handleError: connection idle timeout`)\n } else {\n this.log(`handleError:`, err)\n }\n\n this.active = false\n\n // Emit error event\n this.dispatchEvent(new CustomEvent('error', { detail: err }))\n\n // Clean up\n this.detach(true)\n }\n\n private handleClose(): void {\n this.log(`handleClose: socket closed`)\n this.active = false\n this.dispatchEvent(new CustomEvent('close'))\n this.detach(false)\n }\n\n private inspectData(\n direction: 'incoming' | 'outgoing',\n data: Buffer | Uint8Array,\n ): void {\n if (!this.inspect) return\n console.log('-'.repeat(75))\n if (direction === 'incoming') {\n console.log('-> incoming', data.length, 'bytes')\n } else {\n console.log('<- outgoing', data.length, 'bytes')\n }\n\n for (let offset = 0; offset < data.length; offset += 16) {\n const chunkSize = Math.min(16, data.length - offset)\n\n let hexPart = ''\n for (let i = 0; i < 16; i++) {\n if (i < chunkSize) {\n const byte = data[offset + i]\n hexPart += byte.toString(16).padStart(2, '0') + ' '\n } else {\n hexPart += ' '\n }\n }\n\n let asciiPart = ''\n for (let i = 0; i < chunkSize; i++) {\n const byte = data[offset + i]\n asciiPart += byte >= 32 && byte <= 126 ? String.fromCharCode(byte) : '.'\n }\n\n console.log(\n `${offset.toString(16).padStart(8, '0')} ${hexPart} ${asciiPart}`,\n )\n }\n }\n}\n\n/**\n * Options for creating a PGLiteSocketServer\n */\nexport interface PGLiteSocketServerOptions {\n /** The PGlite database instance */\n db: PGlite\n /** The port to listen on (default: 5432) */\n port?: number\n /** The host to bind to (default: 127.0.0.1) */\n host?: string\n /** Unix socket path to bind to (default: undefined) */\n path?: string\n /** Print the incoming and outgoing data to the console in hex and ascii */\n inspect?: boolean\n /** Enable debug logging of method calls */\n debug?: boolean\n /** Idle timeout in ms (0 to disable, default: 0) */\n idleTimeout?: number\n /** Maximum concurrent connections (default: 100) */\n maxConnections?: number\n}\n\n/**\n * PGLite Socket Server with support for multiple concurrent connections\n * Connections remain open and queries are queued at the query level\n */\nexport class PGLiteSocketServer extends EventTarget {\n readonly db: PGlite\n private server: Server | null = null\n private port?: number\n private host?: string\n private path?: string\n private active = false\n private inspect: boolean\n private debug: boolean\n private idleTimeout: number\n private maxConnections: number\n private handlers: Set<PGLiteSocketHandler> = new Set()\n private queryQueue: QueryQueueManager\n\n constructor(options: PGLiteSocketServerOptions) {\n super()\n this.db = options.db\n if (options.path) {\n this.path = options.path\n } else {\n if (typeof options.port === 'number') {\n // Keep port undefined on port 0, will be set by the OS when we start the server.\n this.port = options.port ?? options.port\n } else {\n this.port = 5432\n }\n this.host = options.host || '127.0.0.1'\n }\n this.inspect = options.inspect ?? false\n this.debug = options.debug ?? false\n this.idleTimeout = options.idleTimeout ?? 0\n this.maxConnections = options.maxConnections ?? 1\n\n // Create the shared query queue\n this.queryQueue = new QueryQueueManager(this.db, this.debug)\n\n this.log(`constructor: created server on ${this.getServerConn()}`)\n this.log(`constructor: max connections: ${this.maxConnections}`)\n if (this.idleTimeout > 0) {\n this.log(`constructor: idle timeout: ${this.idleTimeout}ms`)\n }\n }\n\n private log(message: string, ...args: any[]): void {\n if (this.debug) {\n console.log(`[PGLiteSocketServer] ${message}`, ...args)\n }\n }\n\n public async start(): Promise<void> {\n this.log(`start: starting server on ${this.getServerConn()}`)\n\n if (this.server) {\n throw new Error('Socket server already started')\n }\n\n // Ensure PGlite is ready before accepting connections\n await this.db.waitReady\n\n this.active = true\n this.server = createServer((socket) => {\n setImmediate(() => this.handleConnection(socket))\n })\n\n this.server.maxConnections = this.maxConnections\n\n return new Promise<void>((resolve, reject) => {\n if (!this.server) return reject(new Error('Server not initialized'))\n\n this.server.on('error', (err) => {\n this.log(`start: server error:`, err)\n this.dispatchEvent(new CustomEvent('error', { detail: err }))\n if (!this.active) {\n reject(err)\n }\n })\n\n if (this.path) {\n this.server.listen(this.path, () => {\n this.log(`start: server listening on ${this.getServerConn()}`)\n this.dispatchEvent(\n new CustomEvent('listening', {\n detail: { path: this.path },\n }),\n )\n resolve()\n })\n } else {\n const server = this.server\n server.listen(this.port, this.host, () => {\n const address = server.address()\n // We are not using pipes, so return type should be AddressInfo\n if (address === null || typeof address !== 'object') {\n throw Error('Expected address info')\n }\n // Assign the new port number\n this.port = address.port\n this.log(`start: server listening on ${this.getServerConn()}`)\n this.dispatchEvent(\n new CustomEvent('listening', {\n detail: { port: this.port, host: this.host },\n }),\n )\n resolve()\n })\n }\n })\n }\n\n public getServerConn(): string {\n if (this.path) return this.path\n return `${this.host}:${this.port}`\n }\n\n public async stop(): Promise<void> {\n this.log(`stop: stopping server`)\n\n this.active = false\n\n // Detach all handlers\n this.log(`stop: detaching ${this.handlers.size} handlers`)\n for (const handler of this.handlers) {\n handler.detach(true)\n }\n this.handlers.clear()\n\n if (!this.server) {\n this.log(`stop: server not running, nothing to do`)\n return Promise.resolve()\n }\n\n return new Promise<void>((resolve) => {\n if (!this.server) return resolve()\n\n this.server.close(() => {\n this.log(`stop: server closed`)\n this.server = null\n this.dispatchEvent(new CustomEvent('close'))\n resolve()\n })\n })\n }\n\n private async handleConnection(socket: Socket): Promise<void> {\n const clientInfo = {\n clientAddress: socket.remoteAddress || 'unknown',\n clientPort: socket.remotePort || 0,\n }\n\n this.log(\n `handleConnection: new connection from ${clientInfo.clientAddress}:${clientInfo.clientPort}`,\n )\n this.log(\n `handleConnection: active connections: ${this.handlers.size}, queued queries: ${this.queryQueue.getQueueLength()}`,\n )\n\n if (!this.active) {\n this.log(`handleConnection: server not active, closing connection`)\n try {\n socket.end()\n } catch (err) {\n this.log(`handleConnection: error closing socket:`, err)\n }\n return\n }\n\n // Check connection limit\n if (this.handlers.size >= this.maxConnections) {\n this.log(`handleConnection: max connections reached, rejecting`)\n socket.write(Buffer.from('Too many connections\\n'))\n socket.end()\n return\n }\n\n // Create a new handler for this connection\n const handler = new PGLiteSocketHandler({\n queryQueue: this.queryQueue,\n closeOnDetach: true,\n inspect: this.inspect,\n debug: this.debug,\n idleTimeout: this.idleTimeout,\n })\n\n // Track this handler\n this.handlers.add(handler)\n\n // Handle errors\n handler.addEventListener('error', (event) => {\n const error = (event as CustomEvent<Error>).detail\n\n if (error?.message?.includes('ECONNRESET')) {\n this.log(\n `handler #${handler.handlerId}: client disconnected (ECONNRESET)`,\n )\n } else if (error?.message?.includes('Idle timeout')) {\n this.log(`handler #${handler.handlerId}: idle timeout`)\n } else {\n this.log(`handler #${handler.handlerId}: error:`, error)\n }\n })\n\n // Handle close event\n handler.addEventListener('close', () => {\n this.log(`handler #${handler.handlerId}: closed`)\n this.handlers.delete(handler)\n this.log(`handleConnection: active connections: ${this.handlers.size}`)\n })\n\n try {\n await handler.attach(socket)\n this.dispatchEvent(new CustomEvent('connection', { detail: clientInfo }))\n } catch (err) {\n this.log(`handleConnection: error attaching socket:`, err)\n this.handlers.delete(handler)\n this.dispatchEvent(new CustomEvent('error', { detail: err }))\n try {\n socket.end()\n } catch (closeErr) {\n this.log(`handleConnection: error closing socket:`, closeErr)\n }\n }\n }\n\n public getStats() {\n return {\n activeConnections: this.handlers.size,\n queuedQueries: this.queryQueue.getQueueLength(),\n maxConnections: this.maxConnections,\n }\n }\n}\n"],"mappings":"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","names":["import_pglite","import_net","QueryQueueManager","db","debug","message","args","handlerId","resolve","reject","query","i","q","waitTime","result","error","before","removed","_PGLiteSocketHandler","options","socket","data","err","idleTime","close","totalProcessed","messageLength","isComplete","firstInt","secondInt","writeErr","direction","offset","chunkSize","hexPart","byte","asciiPart","PGLiteSocketHandler","PGLiteSocketServer","server","address","handler","clientInfo","event","closeErr","import_node_util","import_node_child_process","args","help","PGLiteServerRunner","config","extensionsArg","host","port","path","socketDir","extensions","builtInExtensions","name","ext","packagePath","exportName","error","event","detail","databaseUrl","clientAddress","clientPort","PGLiteSocketServer","SubprocessManager","exitCode","onExit","command","includeDatabaseUrl","env","commandParts","code","timeout","main"]}
#!/usr/bin/env node
import{c as d}from"../chunk-F6KLIXM7.js";import{PGlite as h}from"@electric-sql/pglite";import{parseArgs as u}from"node:util";import{spawn as p}from"node:child_process";var r=u({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)"},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)"},help:{type:"boolean",short:"?",default:!1,help:"Show help"}}}),g=`PGlite Socket Server
import{c as d}from"../chunk-JGAU6COP.js";import{PGlite as u}from"@electric-sql/pglite";import{parseArgs as h}from"node:util";import{spawn as p}from"node:child_process";var r=h({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)"},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"}}}),g=`PGlite Socket Server
Usage: pglite-server [options]

@@ -17,4 +17,5 @@

--shutdown-timeout=MS Timeout for graceful subprocess shutdown in ms (default: 5000)
`,l=class{constructor(e){this.db=null;this.server=null;this.subprocessManager=null;this.config=e}static parseConfig(){let e=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:e?e.split(",").map(o=>o.trim()):void 0,runCommand:r.values.run,includeDatabaseUrl:r.values["include-database-url"],shutdownTimeout:parseInt(r.values["shutdown-timeout"],10)}}createDatabaseUrl(){let{host:e,port:o,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@${e}:${o}/postgres`}async importExtensions(){if(!this.config.extensionNames?.length)return;let e={},o=["vector","live","pg_hashids","pg_ivm","pg_uuidv7","pgtap"];for(let t of this.config.extensionNames){let s=null;try{if(t.includes(":")){let[i,n]=t.split(":");if(!i||!n)throw new Error(`Invalid extension format '${t}'. Expected: package/path:exportedName`);s=(await import(i))[n],s&&(e[n]=s,console.log(`Imported extension '${n}' from '${i}'`))}else if(o.includes(t))s=(await import(`@electric-sql/pglite/${t}`))[t],s&&(e[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&&(e[t]=s,console.log(`Imported extension: ${t}`))}}catch(i){throw console.error(`Failed to import extension '${t}':`,i),new Error(`Failed to import extension '${t}'`)}}return Object.keys(e).length>0?e:void 0}async initializeDatabase(){console.log(`Initializing PGLite with database: ${this.config.dbPath}`),console.log(`Debug level: ${this.config.debugLevel}`);let e=await this.importExtensions();this.db=new h(this.config.dbPath,{debug:this.config.debugLevel,extensions:e}),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",e=>{let o=e.detail;if(console.log(`PGLiteSocketServer listening on ${JSON.stringify(o)}`),this.config.runCommand&&this.subprocessManager){let t=this.createDatabaseUrl();this.subprocessManager.spawn(this.config.runCommand,t,this.config.includeDatabaseUrl)}}),this.server.addEventListener("connection",e=>{let{clientAddress:o,clientPort:t}=e.detail;console.log(`Client connected from ${o}:${t}`)}),this.server.addEventListener("error",e=>{let o=e.detail;console.error("Socket server error:",o)})}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 c(e=>{this.shutdown(e)}),this.setupServerEventHandlers(),this.setupSignalHandlers(),await this.server.start()}catch(e){throw console.error("Failed to start PGLiteSocketServer:",e),e}}async shutdown(e=0){console.log(`
-m, --max-connections=N Maximum concurrent connections (default is no concurrency: 1)
`,l=class{constructor(e){this.db=null;this.server=null;this.subprocessManager=null;this.config=e}static parseConfig(){let e=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:e?e.split(",").map(o=>o.trim()):void 0,runCommand:r.values.run,includeDatabaseUrl:r.values["include-database-url"],shutdownTimeout:parseInt(r.values["shutdown-timeout"],10),maxConnections:parseInt(r.values["max-connections"],10)}}createDatabaseUrl(){let{host:e,port:o,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@${e}:${o}/postgres`}async importExtensions(){if(!this.config.extensionNames?.length)return;let e={},o=["vector","live","pg_hashids","pg_ivm","pg_uuidv7","pgtap"];for(let t of this.config.extensionNames){let s=null;try{if(t.includes(":")){let[i,n]=t.split(":");if(!i||!n)throw new Error(`Invalid extension format '${t}'. Expected: package/path:exportedName`);s=(await import(i))[n],s&&(e[n]=s,console.log(`Imported extension '${n}' from '${i}'`))}else if(o.includes(t))s=(await import(`@electric-sql/pglite/${t}`))[t],s&&(e[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&&(e[t]=s,console.log(`Imported extension: ${t}`))}}catch(i){throw console.error(`Failed to import extension '${t}':`,i),new Error(`Failed to import extension '${t}'`)}}return Object.keys(e).length>0?e:void 0}async initializeDatabase(){console.log(`Initializing PGLite with database: ${this.config.dbPath}`),console.log(`Debug level: ${this.config.debugLevel}`);let e=await this.importExtensions();this.db=new u(this.config.dbPath,{debug:this.config.debugLevel,extensions:e}),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",e=>{let o=e.detail;if(console.log(`PGLiteSocketServer listening on ${JSON.stringify(o)}`),this.config.runCommand&&this.subprocessManager){let t=this.createDatabaseUrl();this.subprocessManager.spawn(this.config.runCommand,t,this.config.includeDatabaseUrl)}}),this.server.addEventListener("connection",e=>{let{clientAddress:o,clientPort:t}=e.detail;console.log(`Client connected from ${o}:${t}`)}),this.server.addEventListener("error",e=>{let o=e.detail;console.error("Socket server error:",o)})}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 c(e=>{this.shutdown(e)}),this.setupServerEventHandlers(),this.setupSignalHandlers(),await this.server.start()}catch(e){throw console.error("Failed to start PGLiteSocketServer:",e),e}}async shutdown(e=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(e)}},c=class{constructor(e){this.childProcess=null;this.onExit=e}get process(){return this.childProcess}spawn(e,o,t){console.log(`Running command: ${e}`);let s={...process.env};t&&(s.DATABASE_URL=o,console.log(`Setting DATABASE_URL=${o}`));let i=e.trim().split(/\s+/);this.childProcess=p(i[0],i.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(e){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"))},e))}};async function m(){r.values.help&&(console.log(g),process.exit(0));try{let a=l.parseConfig();await new l(a).start()}catch(a){console.error("Unhandled error:",a),process.exit(1)}}m();
//# sourceMappingURL=server.js.map

@@ -1,1 +0,1 @@

{"version":3,"sources":["../../src/scripts/server.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { PGlite, DebugLevel } from '@electric-sql/pglite'\nimport type { Extension, Extensions } from '@electric-sql/pglite'\nimport { PGLiteSocketServer } from '../index'\nimport { parseArgs } from 'node:util'\nimport { spawn, ChildProcess } from 'node:child_process'\n\n// Define command line argument options\nconst args = parseArgs({\n options: {\n db: {\n type: 'string',\n short: 'd',\n default: 'memory://',\n help: 'Database path (relative or absolute). Use memory:// for in-memory database.',\n },\n port: {\n type: 'string',\n short: 'p',\n default: '5432',\n help: 'Port to listen on',\n },\n host: {\n type: 'string',\n short: 'h',\n default: '127.0.0.1',\n help: 'Host to bind to',\n },\n path: {\n type: 'string',\n short: 'u',\n default: undefined,\n help: 'unix socket to bind to. Takes precedence over host:port',\n },\n debug: {\n type: 'string',\n short: 'v',\n default: '0',\n help: 'Debug level (0-5)',\n },\n extensions: {\n type: 'string',\n short: 'e',\n default: undefined,\n help: 'Comma-separated list of extensions to load (e.g., vector,pgcrypto)',\n },\n run: {\n type: 'string',\n short: 'r',\n default: undefined,\n help: 'Command to run after server starts',\n },\n 'include-database-url': {\n type: 'boolean',\n default: false,\n help: 'Include DATABASE_URL in the environment of the subprocess',\n },\n 'shutdown-timeout': {\n type: 'string',\n default: '5000',\n help: 'Timeout in milliseconds for graceful subprocess shutdown (default: 5000)',\n },\n help: {\n type: 'boolean',\n short: '?',\n default: false,\n help: 'Show help',\n },\n },\n})\n\nconst help = `PGlite Socket Server\nUsage: pglite-server [options]\n\nOptions:\n -d, --db=PATH Database path (default: memory://)\n -p, --port=PORT Port to listen on (default: 5432)\n -h, --host=HOST Host to bind to (default: 127.0.0.1)\n -u, --path=UNIX Unix socket to bind to (default: undefined). Takes precedence over host:port\n -v, --debug=LEVEL Debug level 0-5 (default: 0)\n -e, --extensions=LIST Comma-separated list of extensions to load\n Formats: vector, pgcrypto (built-in/contrib)\n @org/package/path:exportedName (npm package)\n -r, --run=COMMAND Command to run after server starts\n --include-database-url Include DATABASE_URL in subprocess environment\n --shutdown-timeout=MS Timeout for graceful subprocess shutdown in ms (default: 5000)\n`\n\ninterface ServerConfig {\n dbPath: string\n port: number\n host: string\n path?: string\n debugLevel: DebugLevel\n extensionNames?: string[]\n runCommand?: string\n includeDatabaseUrl: boolean\n shutdownTimeout: number\n}\n\nclass PGLiteServerRunner {\n private config: ServerConfig\n private db: PGlite | null = null\n private server: PGLiteSocketServer | null = null\n private subprocessManager: SubprocessManager | null = null\n\n constructor(config: ServerConfig) {\n this.config = config\n }\n\n static parseConfig(): ServerConfig {\n const extensionsArg = args.values.extensions as string | undefined\n return {\n dbPath: args.values.db as string,\n port: parseInt(args.values.port as string, 10),\n host: args.values.host as string,\n path: args.values.path as string,\n debugLevel: parseInt(args.values.debug as string, 10) as DebugLevel,\n extensionNames: extensionsArg\n ? extensionsArg.split(',').map((e) => e.trim())\n : undefined,\n runCommand: args.values.run as string,\n includeDatabaseUrl: args.values['include-database-url'] as boolean,\n shutdownTimeout: parseInt(args.values['shutdown-timeout'] as string, 10),\n }\n }\n\n private createDatabaseUrl(): string {\n const { host, port, path } = this.config\n\n if (path) {\n // Unix socket connection\n const socketDir = path.endsWith('/.s.PGSQL.5432')\n ? path.slice(0, -13)\n : path\n return `postgresql://postgres:postgres@/postgres?host=${encodeURIComponent(socketDir)}`\n } else {\n // TCP connection\n return `postgresql://postgres:postgres@${host}:${port}/postgres`\n }\n }\n\n private async importExtensions(): Promise<Extensions | undefined> {\n if (!this.config.extensionNames?.length) {\n return undefined\n }\n\n const extensions: Extensions = {}\n\n // Built-in extensions that are not in contrib\n const builtInExtensions = [\n 'vector',\n 'live',\n 'pg_hashids',\n 'pg_ivm',\n 'pg_uuidv7',\n 'pgtap',\n ]\n\n for (const name of this.config.extensionNames) {\n let ext: Extension | null = null\n\n try {\n // Check if this is a custom package path (contains ':')\n // Format: @org/package/path:exportedName or package/path:exportedName\n if (name.includes(':')) {\n const [packagePath, exportName] = name.split(':')\n if (!packagePath || !exportName) {\n throw new Error(\n `Invalid extension format '${name}'. Expected: package/path:exportedName`,\n )\n }\n const mod = await import(packagePath)\n ext = mod[exportName] as Extension\n if (ext) {\n extensions[exportName] = ext\n console.log(\n `Imported extension '${exportName}' from '${packagePath}'`,\n )\n }\n } else if (builtInExtensions.includes(name)) {\n // Built-in extension (e.g., @electric-sql/pglite/vector)\n const mod = await import(`@electric-sql/pglite/${name}`)\n ext = mod[name] as Extension\n if (ext) {\n extensions[name] = ext\n console.log(`Imported extension: ${name}`)\n }\n } else {\n // Try contrib first (e.g., @electric-sql/pglite/contrib/pgcrypto)\n try {\n const mod = await import(`@electric-sql/pglite/contrib/${name}`)\n ext = mod[name] as Extension\n } catch {\n // Fall back to external package (e.g., @electric-sql/pglite-<extension>)\n const mod = await import(`@electric-sql/pglite-${name}`)\n ext = mod[name] as Extension\n }\n if (ext) {\n extensions[name] = ext\n console.log(`Imported extension: ${name}`)\n }\n }\n } catch (error) {\n console.error(`Failed to import extension '${name}':`, error)\n throw new Error(`Failed to import extension '${name}'`)\n }\n }\n\n return Object.keys(extensions).length > 0 ? extensions : undefined\n }\n\n private async initializeDatabase(): Promise<void> {\n console.log(`Initializing PGLite with database: ${this.config.dbPath}`)\n console.log(`Debug level: ${this.config.debugLevel}`)\n\n const extensions = await this.importExtensions()\n\n this.db = new PGlite(this.config.dbPath, {\n debug: this.config.debugLevel,\n extensions,\n })\n await this.db.waitReady\n console.log('PGlite database initialized')\n }\n\n private setupServerEventHandlers(): void {\n if (!this.server || !this.subprocessManager) {\n throw new Error('Server or subprocess manager not initialized')\n }\n\n this.server.addEventListener('listening', (event) => {\n const detail = (\n event as CustomEvent<{ port: number; host: string } | { host: string }>\n ).detail\n console.log(`PGLiteSocketServer listening on ${JSON.stringify(detail)}`)\n\n // Run the command after server starts listening\n if (this.config.runCommand && this.subprocessManager) {\n const databaseUrl = this.createDatabaseUrl()\n this.subprocessManager.spawn(\n this.config.runCommand,\n databaseUrl,\n this.config.includeDatabaseUrl,\n )\n }\n })\n\n this.server.addEventListener('connection', (event) => {\n const { clientAddress, clientPort } = (\n event as CustomEvent<{ clientAddress: string; clientPort: number }>\n ).detail\n console.log(`Client connected from ${clientAddress}:${clientPort}`)\n })\n\n this.server.addEventListener('error', (event) => {\n const error = (event as CustomEvent<Error>).detail\n console.error('Socket server error:', error)\n })\n }\n\n private setupSignalHandlers(): void {\n process.on('SIGINT', () => this.shutdown())\n process.on('SIGTERM', () => this.shutdown())\n }\n\n async start(): Promise<void> {\n try {\n // Initialize database\n await this.initializeDatabase()\n\n if (!this.db) {\n throw new Error('Database initialization failed')\n }\n\n // Create and setup the socket server\n this.server = new PGLiteSocketServer({\n db: this.db,\n port: this.config.port,\n host: this.config.host,\n path: this.config.path,\n inspect: this.config.debugLevel > 0,\n })\n\n // Create subprocess manager\n this.subprocessManager = new SubprocessManager((exitCode) => {\n this.shutdown(exitCode)\n })\n\n // Setup event handlers\n this.setupServerEventHandlers()\n this.setupSignalHandlers()\n\n // Start the server\n await this.server.start()\n } catch (error) {\n console.error('Failed to start PGLiteSocketServer:', error)\n throw error\n }\n }\n\n async shutdown(exitCode: number = 0): Promise<void> {\n console.log('\\nShutting down PGLiteSocketServer...')\n\n // Terminate subprocess if running\n if (this.subprocessManager) {\n this.subprocessManager.terminate(this.config.shutdownTimeout)\n }\n\n // Stop server\n if (this.server) {\n await this.server.stop()\n }\n\n // Close database\n if (this.db) {\n await this.db.close()\n }\n\n console.log('Server stopped')\n process.exit(exitCode)\n }\n}\n\nclass SubprocessManager {\n private childProcess: ChildProcess | null = null\n private onExit: (code: number) => void\n\n constructor(onExit: (code: number) => void) {\n this.onExit = onExit\n }\n\n get process(): ChildProcess | null {\n return this.childProcess\n }\n\n spawn(\n command: string,\n databaseUrl: string,\n includeDatabaseUrl: boolean,\n ): void {\n console.log(`Running command: ${command}`)\n\n // Prepare environment variables\n const env = { ...process.env }\n if (includeDatabaseUrl) {\n env.DATABASE_URL = databaseUrl\n console.log(`Setting DATABASE_URL=${databaseUrl}`)\n }\n\n // Parse and spawn the command\n const commandParts = command.trim().split(/\\s+/)\n this.childProcess = spawn(commandParts[0], commandParts.slice(1), {\n env,\n stdio: 'inherit',\n })\n\n this.childProcess.on('error', (error) => {\n console.error('Error running command:', error)\n // If subprocess fails to start, shutdown the server\n console.log('Subprocess failed to start, shutting down...')\n this.onExit(1)\n })\n\n this.childProcess.on('close', (code) => {\n console.log(`Command exited with code ${code}`)\n this.childProcess = null\n\n // If child process exits with non-zero code, notify parent\n if (code !== null && code !== 0) {\n console.log(\n `Child process failed with exit code ${code}, shutting down...`,\n )\n this.onExit(code)\n }\n })\n }\n\n terminate(timeout: number): void {\n if (this.childProcess) {\n console.log('Terminating child process...')\n this.childProcess.kill('SIGTERM')\n\n // Give it a moment to exit gracefully, then force kill if needed\n setTimeout(() => {\n if (this.childProcess && !this.childProcess.killed) {\n console.log('Force killing child process...')\n this.childProcess.kill('SIGKILL')\n }\n }, timeout)\n }\n }\n}\n\n// Main execution\nasync function main() {\n // Show help and exit if requested\n if (args.values.help) {\n console.log(help)\n process.exit(0)\n }\n\n try {\n const config = PGLiteServerRunner.parseConfig()\n const serverRunner = new PGLiteServerRunner(config)\n await serverRunner.start()\n } catch (error) {\n console.error('Unhandled error:', error)\n process.exit(1)\n }\n}\n\n// Run the main function\nmain()\n"],"mappings":"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","names":["PGlite","parseArgs","spawn","args","help","PGLiteServerRunner","config","extensionsArg","e","host","port","path","socketDir","extensions","builtInExtensions","name","ext","packagePath","exportName","error","PGlite","event","detail","databaseUrl","clientAddress","clientPort","PGLiteSocketServer","SubprocessManager","exitCode","onExit","command","includeDatabaseUrl","env","commandParts","code","timeout","main"]}
{"version":3,"sources":["../../src/scripts/server.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { PGlite, DebugLevel } from '@electric-sql/pglite'\nimport type { Extension, Extensions } from '@electric-sql/pglite'\nimport { PGLiteSocketServer } from '../index'\nimport { parseArgs } from 'node:util'\nimport { spawn, ChildProcess } from 'node:child_process'\n\n// Define command line argument options\nconst args = parseArgs({\n options: {\n db: {\n type: 'string',\n short: 'd',\n default: 'memory://',\n help: 'Database path (relative or absolute). Use memory:// for in-memory database.',\n },\n port: {\n type: 'string',\n short: 'p',\n default: '5432',\n help: 'Port to listen on',\n },\n host: {\n type: 'string',\n short: 'h',\n default: '127.0.0.1',\n help: 'Host to bind to',\n },\n path: {\n type: 'string',\n short: 'u',\n default: undefined,\n help: 'unix socket to bind to. Takes precedence over host:port',\n },\n debug: {\n type: 'string',\n short: 'v',\n default: '0',\n help: 'Debug level (0-5)',\n },\n extensions: {\n type: 'string',\n short: 'e',\n default: undefined,\n help: 'Comma-separated list of extensions to load (e.g., vector,pgcrypto)',\n },\n run: {\n type: 'string',\n short: 'r',\n default: undefined,\n help: 'Command to run after server starts',\n },\n 'include-database-url': {\n type: 'boolean',\n default: false,\n help: 'Include DATABASE_URL in the environment of the subprocess',\n },\n 'shutdown-timeout': {\n type: 'string',\n default: '5000',\n help: 'Timeout in milliseconds for graceful subprocess shutdown (default: 5000)',\n },\n 'max-connections': {\n type: 'string',\n short: 'm',\n default: '1',\n help: 'Maximum concurrent connections (default: 1)',\n },\n help: {\n type: 'boolean',\n short: '?',\n default: false,\n help: 'Show help',\n },\n },\n})\n\nconst help = `PGlite Socket Server\nUsage: pglite-server [options]\n\nOptions:\n -d, --db=PATH Database path (default: memory://)\n -p, --port=PORT Port to listen on (default: 5432)\n -h, --host=HOST Host to bind to (default: 127.0.0.1)\n -u, --path=UNIX Unix socket to bind to (default: undefined). Takes precedence over host:port\n -v, --debug=LEVEL Debug level 0-5 (default: 0)\n -e, --extensions=LIST Comma-separated list of extensions to load\n Formats: vector, pgcrypto (built-in/contrib)\n @org/package/path:exportedName (npm package)\n -r, --run=COMMAND Command to run after server starts\n --include-database-url Include DATABASE_URL in subprocess environment\n --shutdown-timeout=MS Timeout for graceful subprocess shutdown in ms (default: 5000)\n -m, --max-connections=N Maximum concurrent connections (default is no concurrency: 1)\n`\n\ninterface ServerConfig {\n dbPath: string\n port: number\n host: string\n path?: string\n debugLevel: DebugLevel\n extensionNames?: string[]\n runCommand?: string\n includeDatabaseUrl: boolean\n shutdownTimeout: number\n maxConnections: number\n}\n\nclass PGLiteServerRunner {\n private config: ServerConfig\n private db: PGlite | null = null\n private server: PGLiteSocketServer | null = null\n private subprocessManager: SubprocessManager | null = null\n\n constructor(config: ServerConfig) {\n this.config = config\n }\n\n static parseConfig(): ServerConfig {\n const extensionsArg = args.values.extensions as string | undefined\n return {\n dbPath: args.values.db as string,\n port: parseInt(args.values.port as string, 10),\n host: args.values.host as string,\n path: args.values.path as string,\n debugLevel: parseInt(args.values.debug as string, 10) as DebugLevel,\n extensionNames: extensionsArg\n ? extensionsArg.split(',').map((e) => e.trim())\n : undefined,\n runCommand: args.values.run as string,\n includeDatabaseUrl: args.values['include-database-url'] as boolean,\n shutdownTimeout: parseInt(args.values['shutdown-timeout'] as string, 10),\n maxConnections: parseInt(args.values['max-connections'] as string, 10),\n }\n }\n\n private createDatabaseUrl(): string {\n const { host, port, path } = this.config\n\n if (path) {\n // Unix socket connection\n const socketDir = path.endsWith('/.s.PGSQL.5432')\n ? path.slice(0, -13)\n : path\n return `postgresql://postgres:postgres@/postgres?host=${encodeURIComponent(socketDir)}`\n } else {\n // TCP connection\n return `postgresql://postgres:postgres@${host}:${port}/postgres`\n }\n }\n\n private async importExtensions(): Promise<Extensions | undefined> {\n if (!this.config.extensionNames?.length) {\n return undefined\n }\n\n const extensions: Extensions = {}\n\n // Built-in extensions that are not in contrib\n const builtInExtensions = [\n 'vector',\n 'live',\n 'pg_hashids',\n 'pg_ivm',\n 'pg_uuidv7',\n 'pgtap',\n ]\n\n for (const name of this.config.extensionNames) {\n let ext: Extension | null = null\n\n try {\n // Check if this is a custom package path (contains ':')\n // Format: @org/package/path:exportedName or package/path:exportedName\n if (name.includes(':')) {\n const [packagePath, exportName] = name.split(':')\n if (!packagePath || !exportName) {\n throw new Error(\n `Invalid extension format '${name}'. Expected: package/path:exportedName`,\n )\n }\n const mod = await import(packagePath)\n ext = mod[exportName] as Extension\n if (ext) {\n extensions[exportName] = ext\n console.log(\n `Imported extension '${exportName}' from '${packagePath}'`,\n )\n }\n } else if (builtInExtensions.includes(name)) {\n // Built-in extension (e.g., @electric-sql/pglite/vector)\n const mod = await import(`@electric-sql/pglite/${name}`)\n ext = mod[name] as Extension\n if (ext) {\n extensions[name] = ext\n console.log(`Imported extension: ${name}`)\n }\n } else {\n // Try contrib first (e.g., @electric-sql/pglite/contrib/pgcrypto)\n try {\n const mod = await import(`@electric-sql/pglite/contrib/${name}`)\n ext = mod[name] as Extension\n } catch {\n // Fall back to external package (e.g., @electric-sql/pglite-<extension>)\n const mod = await import(`@electric-sql/pglite-${name}`)\n ext = mod[name] as Extension\n }\n if (ext) {\n extensions[name] = ext\n console.log(`Imported extension: ${name}`)\n }\n }\n } catch (error) {\n console.error(`Failed to import extension '${name}':`, error)\n throw new Error(`Failed to import extension '${name}'`)\n }\n }\n\n return Object.keys(extensions).length > 0 ? extensions : undefined\n }\n\n private async initializeDatabase(): Promise<void> {\n console.log(`Initializing PGLite with database: ${this.config.dbPath}`)\n console.log(`Debug level: ${this.config.debugLevel}`)\n\n const extensions = await this.importExtensions()\n\n this.db = new PGlite(this.config.dbPath, {\n debug: this.config.debugLevel,\n extensions,\n })\n await this.db.waitReady\n console.log('PGlite database initialized')\n }\n\n private setupServerEventHandlers(): void {\n if (!this.server || !this.subprocessManager) {\n throw new Error('Server or subprocess manager not initialized')\n }\n\n this.server.addEventListener('listening', (event) => {\n const detail = (\n event as CustomEvent<{ port: number; host: string } | { host: string }>\n ).detail\n console.log(`PGLiteSocketServer listening on ${JSON.stringify(detail)}`)\n\n // Run the command after server starts listening\n if (this.config.runCommand && this.subprocessManager) {\n const databaseUrl = this.createDatabaseUrl()\n this.subprocessManager.spawn(\n this.config.runCommand,\n databaseUrl,\n this.config.includeDatabaseUrl,\n )\n }\n })\n\n this.server.addEventListener('connection', (event) => {\n const { clientAddress, clientPort } = (\n event as CustomEvent<{ clientAddress: string; clientPort: number }>\n ).detail\n console.log(`Client connected from ${clientAddress}:${clientPort}`)\n })\n\n this.server.addEventListener('error', (event) => {\n const error = (event as CustomEvent<Error>).detail\n console.error('Socket server error:', error)\n })\n }\n\n private setupSignalHandlers(): void {\n process.on('SIGINT', () => this.shutdown())\n process.on('SIGTERM', () => this.shutdown())\n }\n\n async start(): Promise<void> {\n try {\n // Initialize database\n await this.initializeDatabase()\n\n if (!this.db) {\n throw new Error('Database initialization failed')\n }\n\n // Create and setup the socket server\n this.server = new PGLiteSocketServer({\n db: this.db,\n port: this.config.port,\n host: this.config.host,\n path: this.config.path,\n inspect: this.config.debugLevel > 0,\n maxConnections: this.config.maxConnections,\n })\n\n // Create subprocess manager\n this.subprocessManager = new SubprocessManager((exitCode) => {\n this.shutdown(exitCode)\n })\n\n // Setup event handlers\n this.setupServerEventHandlers()\n this.setupSignalHandlers()\n\n // Start the server\n await this.server.start()\n } catch (error) {\n console.error('Failed to start PGLiteSocketServer:', error)\n throw error\n }\n }\n\n async shutdown(exitCode: number = 0): Promise<void> {\n console.log('\\nShutting down PGLiteSocketServer...')\n\n // Terminate subprocess if running\n if (this.subprocessManager) {\n this.subprocessManager.terminate(this.config.shutdownTimeout)\n }\n\n // Stop server\n if (this.server) {\n await this.server.stop()\n }\n\n // Close database\n if (this.db) {\n await this.db.close()\n }\n\n console.log('Server stopped')\n process.exit(exitCode)\n }\n}\n\nclass SubprocessManager {\n private childProcess: ChildProcess | null = null\n private onExit: (code: number) => void\n\n constructor(onExit: (code: number) => void) {\n this.onExit = onExit\n }\n\n get process(): ChildProcess | null {\n return this.childProcess\n }\n\n spawn(\n command: string,\n databaseUrl: string,\n includeDatabaseUrl: boolean,\n ): void {\n console.log(`Running command: ${command}`)\n\n // Prepare environment variables\n const env = { ...process.env }\n if (includeDatabaseUrl) {\n env.DATABASE_URL = databaseUrl\n console.log(`Setting DATABASE_URL=${databaseUrl}`)\n }\n\n // Parse and spawn the command\n const commandParts = command.trim().split(/\\s+/)\n this.childProcess = spawn(commandParts[0], commandParts.slice(1), {\n env,\n stdio: 'inherit',\n })\n\n this.childProcess.on('error', (error) => {\n console.error('Error running command:', error)\n // If subprocess fails to start, shutdown the server\n console.log('Subprocess failed to start, shutting down...')\n this.onExit(1)\n })\n\n this.childProcess.on('close', (code) => {\n console.log(`Command exited with code ${code}`)\n this.childProcess = null\n\n // If child process exits with non-zero code, notify parent\n if (code !== null && code !== 0) {\n console.log(\n `Child process failed with exit code ${code}, shutting down...`,\n )\n this.onExit(code)\n }\n })\n }\n\n terminate(timeout: number): void {\n if (this.childProcess) {\n console.log('Terminating child process...')\n this.childProcess.kill('SIGTERM')\n\n // Give it a moment to exit gracefully, then force kill if needed\n setTimeout(() => {\n if (this.childProcess && !this.childProcess.killed) {\n console.log('Force killing child process...')\n this.childProcess.kill('SIGKILL')\n }\n }, timeout)\n }\n }\n}\n\n// Main execution\nasync function main() {\n // Show help and exit if requested\n if (args.values.help) {\n console.log(help)\n process.exit(0)\n }\n\n try {\n const config = PGLiteServerRunner.parseConfig()\n const serverRunner = new PGLiteServerRunner(config)\n await serverRunner.start()\n } catch (error) {\n console.error('Unhandled error:', error)\n process.exit(1)\n }\n}\n\n// Run the main function\nmain()\n"],"mappings":"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","names":["PGlite","parseArgs","spawn","args","help","PGLiteServerRunner","config","extensionsArg","e","host","port","path","socketDir","extensions","builtInExtensions","name","ext","packagePath","exportName","error","PGlite","event","detail","databaseUrl","clientAddress","clientPort","PGLiteSocketServer","SubprocessManager","exitCode","onExit","command","includeDatabaseUrl","env","commandParts","code","timeout","main"]}
{
"name": "@electric-sql/pglite-socket",
"version": "0.0.20",
"version": "0.0.21",
"description": "A socket implementation for PGlite enabling remote connections",

@@ -5,0 +5,0 @@ "author": "Electric DB Limited",

+413
-371
import type { PGlite } from '@electric-sql/pglite'
import { createServer, Server, Socket } from 'net'
import { type Server, type Socket, createServer } from 'net'

@@ -8,7 +8,145 @@ // Connection queue timeout in milliseconds

/**
* Represents a queued query waiting for PGlite access
*/
interface QueuedQuery {
handlerId: number
message: Uint8Array
resolve: (result: Uint8Array) => void
reject: (error: Error) => void
timestamp: number
}
/**
* Global query queue manager
* Ensures only one query executes at a time in PGlite
*/
class QueryQueueManager {
private queue: QueuedQuery[] = []
private processing = false
private db: PGlite
private debug: boolean
private lastHandlerId: null | number = null
constructor(db: PGlite, debug = false) {
this.db = db
this.debug = debug
}
private log(message: string, ...args: any[]): void {
if (this.debug) {
console.log(`[QueryQueueManager] ${message}`, ...args)
}
}
async enqueue(handlerId: number, message: Uint8Array): Promise<Uint8Array> {
return new Promise((resolve, reject) => {
const query: QueuedQuery = {
handlerId,
message,
resolve,
reject,
timestamp: Date.now(),
}
this.queue.push(query)
this.log(
`enqueued query from handler #${handlerId}, queue size: ${this.queue.length}`,
)
// Process queue if not already processing
if (!this.processing) {
this.processQueue()
}
})
}
private async processQueue(): Promise<void> {
if (this.processing || this.queue.length === 0) {
return
}
this.processing = true
while (this.queue.length > 0) {
let query
if (this.db.isInTransaction() && this.lastHandlerId) {
const i = this.queue.findIndex(
(q) => q.handlerId === this.lastHandlerId,
)
if (i === -1) {
// we didn't find any other query from the same client!
this.log(
`transaction started, but no query from the same handler id found in queue`,
this.lastHandlerId,
)
query = null
} else {
query = this.queue.splice(i, 1)[0]
}
} else {
query = this.queue.shift()
}
if (!query) break
const waitTime = Date.now() - query.timestamp
this.log(
`processing query from handler #${query.handlerId} (waited ${waitTime}ms)`,
)
try {
// Execute the query with exclusive access to PGlite
const result = await this.db.runExclusive(async () => {
return await this.db.execProtocolRaw(query.message)
})
this.log(
`query from handler #${query.handlerId} completed, ${result.length} bytes`,
)
this.lastHandlerId = query.handlerId
query.resolve(result)
} catch (error) {
this.log(`query from handler #${query.handlerId} failed:`, error)
query.reject(error as Error)
}
}
this.processing = false
this.log(`queue processing complete, queue length is`, this.queue.length)
}
getQueueLength(): number {
return this.queue.length
}
clearQueueForHandler(handlerId: number): void {
const before = this.queue.length
this.queue = this.queue.filter((q) => {
if (q.handlerId === handlerId) {
q.reject(new Error('Handler disconnected'))
return false
}
return true
})
const removed = before - this.queue.length
if (removed > 0) {
this.log(`cleared ${removed} queries for handler #${handlerId}`)
}
}
async clearTransactionIfNeeded(handlerId: number): Promise<void> {
if (this.db.isInTransaction() && this.lastHandlerId === handlerId) {
await this.db.exec('ROLLBACK')
this.lastHandlerId = null
await this.processQueue()
}
}
}
/**
* Options for creating a PGLiteSocketHandler
*/
export interface PGLiteSocketHandlerOptions {
/** The PGlite database instance */
db: PGlite
/** The query queue manager */
queryQueue: QueryQueueManager
/** Whether to close the socket when detached (default: false) */

@@ -20,18 +158,22 @@ closeOnDetach?: boolean

debug?: boolean
/** Idle timeout in ms (0 to disable, default: 0) */
idleTimeout?: number
}
/**
* Low-level handler for a single socket connection to PGLite
* Handles the raw protocol communication between a socket and PGLite
* Handler for a single socket connection to PGlite
* Each connection can remain open and send multiple queries
*/
export class PGLiteSocketHandler extends EventTarget {
readonly db: PGlite
private queryQueue: QueryQueueManager
private socket: Socket | null = null
private active = false
private closeOnDetach: boolean
private resolveLock?: () => void
private rejectLock?: (err: Error) => void
private inspect: boolean
private debug: boolean
private readonly id: number
private messageBuffer: Buffer = Buffer.alloc(0)
private idleTimer?: NodeJS.Timeout
private idleTimeout: number
private lastActivityTime: number = Date.now()

@@ -41,12 +183,9 @@ // Static counter for generating unique handler IDs

/**
* Create a new PGLiteSocketHandler
* @param options Options for the handler
*/
constructor(options: PGLiteSocketHandlerOptions) {
super()
this.db = options.db
this.queryQueue = options.queryQueue
this.closeOnDetach = options.closeOnDetach ?? false
this.inspect = options.inspect ?? false
this.debug = options.debug ?? false
this.idleTimeout = options.idleTimeout ?? 0
this.id = PGLiteSocketHandler.nextHandlerId++

@@ -57,5 +196,2 @@

/**
* Get the unique ID of this handler
*/
public get handlerId(): number {

@@ -65,6 +201,2 @@ return this.id

/**
* Log a message if debug is enabled
* @private
*/
private log(message: string, ...args: any[]): void {

@@ -76,8 +208,2 @@ if (this.debug) {

/**
* Attach a socket to this handler
* @param socket The socket to attach
* @returns this handler instance
* @throws Error if a socket is already attached
*/
public async attach(socket: Socket): Promise<PGLiteSocketHandler> {

@@ -94,47 +220,68 @@ this.log(

this.active = true
this.lastActivityTime = Date.now()
// Ensure the PGlite instance is ready
this.log(`attach: waiting for PGlite to be ready`)
await this.db.waitReady
// Set up socket options
socket.setNoDelay(true)
// Hold the lock on the PGlite instance
this.log(`attach: acquiring exclusive lock on PGlite instance`)
await new Promise<void>((resolve) => {
this.db.runExclusive(() => {
// Ensure we have the lock on the PGlite instance
resolve()
// Set up idle timeout if configured
if (this.idleTimeout > 0) {
this.resetIdleTimer()
}
// Use a promise to hold the lock on the PGlite instance
// this can be resolved or rejected by the handler to release the lock
return new Promise<void>((resolveLock, rejectLock) => {
this.resolveLock = resolveLock
this.rejectLock = rejectLock
})
// Setup event handlers
this.log(`attach: setting up socket event handlers`)
socket.on('data', (data) => {
this.lastActivityTime = Date.now()
this.resetIdleTimer()
setImmediate(async () => {
try {
await this.handleData(data)
} catch (err) {
this.log('socket on data error: ', err)
this.handleError(err as Error)
}
})
})
// Setup event handlers
this.log(`attach: setting up socket event handlers`)
socket.on('data', async (data) => {
try {
const result = await this.handleData(data)
this.log(`socket on data sent: ${result} bytes`)
} catch (err) {
this.log('socket on data error: ', err)
}
socket.on('error', (err) => {
setImmediate(() => this.handleError(err))
})
socket.on('error', (err) => this.handleError(err))
socket.on('close', () => this.handleClose())
socket.on('close', () => {
setImmediate(() => this.handleClose())
})
this.log(`attach: socket handler ready`)
return this
}
/**
* Detach the current socket from this handler
* @param close Whether to close the socket when detaching (overrides constructor option)
* @returns this handler instance
*/
public detach(close?: boolean): PGLiteSocketHandler {
private resetIdleTimer(): void {
if (this.idleTimeout <= 0) return
if (this.idleTimer) {
clearTimeout(this.idleTimer)
}
this.idleTimer = setTimeout(() => {
const idleTime = Date.now() - this.lastActivityTime
this.log(`idle timeout after ${idleTime}ms`)
this.handleError(new Error('Idle timeout'))
}, this.idleTimeout)
}
public async detach(close?: boolean): Promise<PGLiteSocketHandler> {
this.log(`detach: detaching socket, close=${close ?? this.closeOnDetach}`)
if (this.idleTimer) {
clearTimeout(this.idleTimer)
this.idleTimer = undefined
}
// Clear any pending queries for this handler
this.queryQueue.clearQueueForHandler(this.id)
await this.queryQueue.clearTransactionIfNeeded(this.id)
if (!this.socket) {

@@ -154,19 +301,19 @@ this.log(`detach: no socket attached, nothing to do`)

this.log(`detach: closing socket`)
this.socket.end()
this.socket.destroy()
try {
this.socket.end()
this.socket.destroy()
} catch (err) {
this.log(`detach: error closing socket:`, err)
}
}
}
// Release the lock on the PGlite instance
this.log(`detach: releasing exclusive lock on PGlite instance`)
this.resolveLock?.()
this.socket = null
this.active = false
this.messageBuffer = Buffer.alloc(0)
this.log(`detach: handler cleaned up`)
return this
}
/**
* Check if a socket is currently attached
*/
public get isAttached(): boolean {

@@ -176,9 +323,6 @@ return this.socket !== null

/**
* Handle incoming data from the socket
*/
private async handleData(data: Buffer): Promise<number> {
if (!this.socket || !this.active) {
this.log(`handleData: no active socket, ignoring data`)
return new Promise((_, reject) => reject(`no active socket`))
return 0
}

@@ -188,2 +332,5 @@

// Append to buffer for message reassembly
this.messageBuffer = Buffer.concat([this.messageBuffer, data])
// Print the incoming data to the console

@@ -193,89 +340,140 @@ this.inspectData('incoming', data)

try {
// Process the raw protocol data
this.log(`handleData: sending data to PGlite for processing`)
const result = await this.db.execProtocolRaw(new Uint8Array(data))
let totalProcessed = 0
this.log(`handleData: received ${result.length} bytes from PGlite`)
while (this.messageBuffer.length > 0) {
// Determine message length
let messageLength = 0
let isComplete = false
// Print the outgoing data to the console
this.inspectData('outgoing', result)
// Handle startup message (no type byte, just length)
if (this.messageBuffer.length >= 4) {
const firstInt = this.messageBuffer.readInt32BE(0)
// Send the result back if the socket is still connected
if (this.socket && this.socket.writable && this.active) {
if (result.length <= 0) {
this.log(`handleData: cowardly refusing to send empty packet`)
return new Promise((_, reject) => reject('no data'))
}
if (this.messageBuffer.length >= 8) {
const secondInt = this.messageBuffer.readInt32BE(4)
// PostgreSQL 3.0 protocol version
if (secondInt === 196608 || secondInt === 0x00030000) {
messageLength = firstInt
isComplete = this.messageBuffer.length >= messageLength
}
}
const promise = new Promise<number>((resolve, reject) => {
this.log(`handleData: writing response to socket`)
if (this.socket) {
this.socket.write(Buffer.from(result), (err?: Error) => {
if (err) {
reject(`Error while writing to the socket ${err.toString()}`)
} else {
resolve(result.length)
}
})
} else {
reject(`No socket`)
// Regular message (type byte + length)
if (!isComplete && this.messageBuffer.length >= 5) {
const msgLength = this.messageBuffer.readInt32BE(1)
messageLength = 1 + msgLength
isComplete = this.messageBuffer.length >= messageLength
}
})
}
// Emit data event with byte sizes
this.dispatchEvent(
new CustomEvent('data', {
detail: { incoming: data.length, outgoing: result.length },
}),
if (!isComplete || messageLength === 0) {
this.log(
`handleData: incomplete message, buffering ${this.messageBuffer.length} bytes`,
)
break
}
// Extract and process complete message
const message = this.messageBuffer.slice(0, messageLength)
this.messageBuffer = this.messageBuffer.slice(messageLength)
this.log(`handleData: processing message of ${message.length} bytes`)
// Check if socket is still active before processing
if (!this.active || !this.socket) {
this.log(`handleData: socket no longer active, stopping processing`)
break
}
// Queue the query for execution
// This allows multiple connections to queue queries simultaneously
const result = await this.queryQueue.enqueue(
this.id,
new Uint8Array(message),
)
return promise
} else {
this.log(
`handleData: socket no longer writable or active, discarding response`,
)
return new Promise((_, reject) =>
reject(`No socket, not active or not writeable`),
)
this.log(`handleData: received ${result.length} bytes from PGlite`)
// Print the outgoing data to the console
this.inspectData('outgoing', result)
// Send response if available
if (
result.length > 0 &&
this.socket &&
this.socket.writable &&
this.active
) {
await new Promise<number>((resolve, reject) => {
this.log(`handleData: writing response to socket`)
if (this.socket?.writable) {
this.socket.write(Buffer.from(result), (err?: any) => {
if (err) {
this.log(`handleData: error writing to socket:`, err)
reject(err)
} else {
this.log(`handleData: socket sent: ${result.length} bytes`)
resolve(result.length)
}
})
} else {
this.log(`handleData: socket no longer writable`)
resolve(0)
}
}).catch((writeErr) => {
this.log(`handleData: failed to write to socket:`, writeErr)
throw writeErr
})
}
totalProcessed += result.length
}
// Emit data event with byte sizes
this.dispatchEvent(
new CustomEvent('data', {
detail: { incoming: data.length, outgoing: totalProcessed },
}),
)
return totalProcessed
} catch (err) {
this.log(`handleData: error processing data:`, err)
this.handleError(err as Error)
return new Promise((_, reject) =>
reject(`Error while processing data ${(err as Error).toString()}`),
)
throw err
}
}
/**
* Handle errors from the socket
*/
private handleError(err: Error): void {
this.log(`handleError:`, err)
if (!this.active) {
this.log(`handleError: handler not active, ignoring error`)
return
}
// ECONNRESET is expected behavior when clients disconnect
if (err.message?.includes('ECONNRESET')) {
this.log(
`handleError: client disconnected (ECONNRESET) - normal behavior`,
)
} else if (err.message?.includes('Idle timeout')) {
this.log(`handleError: connection idle timeout`)
} else {
this.log(`handleError:`, err)
}
this.active = false
// Emit error event
this.dispatchEvent(new CustomEvent('error', { detail: err }))
// Reject the lock on the PGlite instance
this.log(`handleError: rejecting exclusive lock on PGlite instance`)
this.rejectLock?.(err)
this.resolveLock = undefined
this.rejectLock = undefined
// Close the connection on error
// Clean up
this.detach(true)
}
/**
* Handle socket close event
*/
private handleClose(): void {
this.log(`handleClose: socket closed`)
this.active = false
this.dispatchEvent(new CustomEvent('close'))
this.detach(false) // Already closed, just clean up
this.detach(false)
}
/**
* Print data in hex and ascii to the console
*/
private inspectData(

@@ -293,8 +491,5 @@ direction: 'incoming' | 'outgoing',

// Process 16 bytes per line
for (let offset = 0; offset < data.length; offset += 16) {
// Calculate current chunk size (may be less than 16 for the last chunk)
const chunkSize = Math.min(16, data.length - offset)
// Build the hex representation
let hexPart = ''

@@ -306,15 +501,12 @@ for (let i = 0; i < 16; i++) {

} else {
hexPart += ' ' // 3 spaces for missing bytes
hexPart += ' '
}
}
// Build the ASCII representation
let asciiPart = ''
for (let i = 0; i < chunkSize; i++) {
const byte = data[offset + i]
// Use printable characters (32-126), replace others with a dot
asciiPart += byte >= 32 && byte <= 126 ? String.fromCharCode(byte) : '.'
}
// Print the line with offset in hex, hex values, and ASCII representation
console.log(

@@ -328,14 +520,2 @@ `${offset.toString(16).padStart(8, '0')} ${hexPart} ${asciiPart}`,

/**
* Represents a queued connection with timeout
*/
interface QueuedConnection {
socket: Socket
clientInfo: {
clientAddress: string
clientPort: number
}
timeoutId: NodeJS.Timeout
}
/**
* Options for creating a PGLiteSocketServer

@@ -350,15 +530,17 @@ */

host?: string
/** Unix socket path to bind to (default: undefined). If specified, takes precedence over host:port */
/** Unix socket path to bind to (default: undefined) */
path?: string
/** Print the incoming and outgoing data to the console in hex and ascii */
inspect?: boolean
/** Connection queue timeout in milliseconds (default: 10000) */
connectionQueueTimeout?: number
/** Enable debug logging of method calls */
debug?: boolean
/** Idle timeout in ms (0 to disable, default: 0) */
idleTimeout?: number
/** Maximum concurrent connections (default: 100) */
maxConnections?: number
}
/**
* High-level server that manages socket connections to PGLite
* Creates and manages a TCP server and handles client connections
* PGLite Socket Server with support for multiple concurrent connections
* Connections remain open and queries are queued at the query level
*/

@@ -374,11 +556,7 @@ export class PGLiteSocketServer extends EventTarget {

private debug: boolean
private connectionQueueTimeout: number
private activeHandler: PGLiteSocketHandler | null = null
private connectionQueue: QueuedConnection[] = []
private handlerCount: number = 0
private idleTimeout: number
private maxConnections: number
private handlers: Set<PGLiteSocketHandler> = new Set()
private queryQueue: QueryQueueManager
/**
* Create a new PGLiteSocketServer
* @param options Options for the server
*/
constructor(options: PGLiteSocketServerOptions) {

@@ -400,15 +578,15 @@ super()

this.debug = options.debug ?? false
this.connectionQueueTimeout =
options.connectionQueueTimeout ?? CONNECTION_QUEUE_TIMEOUT
this.idleTimeout = options.idleTimeout ?? 0
this.maxConnections = options.maxConnections ?? 1
this.log(`constructor: created server on ${this.host}:${this.port}`)
this.log(
`constructor: connection queue timeout: ${this.connectionQueueTimeout}ms`,
)
// Create the shared query queue
this.queryQueue = new QueryQueueManager(this.db, this.debug)
this.log(`constructor: created server on ${this.getServerConn()}`)
this.log(`constructor: max connections: ${this.maxConnections}`)
if (this.idleTimeout > 0) {
this.log(`constructor: idle timeout: ${this.idleTimeout}ms`)
}
}
/**
* Log a message if debug is enabled
* @private
*/
private log(message: string, ...args: any[]): void {

@@ -420,6 +598,2 @@ if (this.debug) {

/**
* Start the socket server
* @returns Promise that resolves when the server is listening
*/
public async start(): Promise<void> {

@@ -432,5 +606,12 @@ this.log(`start: starting server on ${this.getServerConn()}`)

// Ensure PGlite is ready before accepting connections
await this.db.waitReady
this.active = true
this.server = createServer((socket) => this.handleConnection(socket))
this.server = createServer((socket) => {
setImmediate(() => this.handleConnection(socket))
})
this.server.maxConnections = this.maxConnections
return new Promise<void>((resolve, reject) => {

@@ -442,3 +623,5 @@ if (!this.server) return reject(new Error('Server not initialized'))

this.dispatchEvent(new CustomEvent('error', { detail: err }))
reject(err)
if (!this.active) {
reject(err)
}
})

@@ -483,6 +666,2 @@

/**
* Stop the socket server
* @returns Promise that resolves when the server is closed
*/
public async stop(): Promise<void> {

@@ -493,24 +672,8 @@ this.log(`stop: stopping server`)

// Clear connection queue
this.log(
`stop: clearing connection queue (${this.connectionQueue.length} connections)`,
)
this.connectionQueue.forEach((queuedConn) => {
clearTimeout(queuedConn.timeoutId)
if (queuedConn.socket.writable) {
this.log(
`stop: closing queued connection from ${queuedConn.clientInfo.clientAddress}:${queuedConn.clientInfo.clientPort}`,
)
queuedConn.socket.end()
}
})
this.connectionQueue = []
// Detach active handler if exists
if (this.activeHandler) {
this.log(`stop: detaching active handler #${this.activeHandlerId}`)
this.activeHandler.detach(true)
this.activeHandler = null
// Detach all handlers
this.log(`stop: detaching ${this.handlers.size} handlers`)
for (const handler of this.handlers) {
handler.detach(true)
}
this.handlers.clear()

@@ -534,12 +697,2 @@ if (!this.server) {

/**
* Get the active handler ID, or null if no active handler
*/
private get activeHandlerId(): number | null {
return this.activeHandler?.handlerId ?? null
}
/**
* Handle a new client connection
*/
private async handleConnection(socket: Socket): Promise<void> {

@@ -554,191 +707,80 @@ const clientInfo = {

)
this.log(
`handleConnection: active connections: ${this.handlers.size}, queued queries: ${this.queryQueue.getQueueLength()}`,
)
// If server is not active, close the connection immediately
if (!this.active) {
this.log(`handleConnection: server not active, closing connection`)
socket.end()
return
}
// If we don't have an active handler or it's not attached, we can use this connection immediately
if (!this.activeHandler || !this.activeHandler.isAttached) {
this.log(`handleConnection: no active handler, attaching socket directly`)
this.dispatchEvent(new CustomEvent('connection', { detail: clientInfo }))
await this.attachSocketToNewHandler(socket, clientInfo)
return
}
// Otherwise, queue the connection
this.log(
`handleConnection: active handler #${this.activeHandlerId} exists, queueing connection`,
)
this.enqueueConnection(socket, clientInfo)
}
/**
* Add a connection to the queue
*/
private enqueueConnection(
socket: Socket,
clientInfo: { clientAddress: string; clientPort: number },
): void {
this.log(
`enqueueConnection: queueing connection from ${clientInfo.clientAddress}:${clientInfo.clientPort}, timeout: ${this.connectionQueueTimeout}ms`,
)
// Set a timeout for this queued connection
const timeoutId = setTimeout(() => {
this.log(
`enqueueConnection: timeout for connection from ${clientInfo.clientAddress}:${clientInfo.clientPort}`,
)
// Remove from queue
this.connectionQueue = this.connectionQueue.filter(
(queuedConn) => queuedConn.socket !== socket,
)
// End the connection if it's still open
if (socket.writable) {
this.log(`enqueueConnection: closing timed out connection`)
try {
socket.end()
} catch (err) {
this.log(`handleConnection: error closing socket:`, err)
}
this.dispatchEvent(
new CustomEvent('queueTimeout', {
detail: { ...clientInfo, queueSize: this.connectionQueue.length },
}),
)
}, this.connectionQueueTimeout)
// Add to queue
this.connectionQueue.push({ socket, clientInfo, timeoutId })
this.log(
`enqueueConnection: connection queued, queue size: ${this.connectionQueue.length}`,
)
this.dispatchEvent(
new CustomEvent('queuedConnection', {
detail: { ...clientInfo, queueSize: this.connectionQueue.length },
}),
)
}
/**
* Process the next connection in the queue
*/
private processNextInQueue(): void {
this.log(
`processNextInQueue: processing next connection, queue size: ${this.connectionQueue.length}`,
)
// No connections in queue or server not active
if (this.connectionQueue.length === 0 || !this.active) {
this.log(
`processNextInQueue: no connections in queue or server not active, nothing to do`,
)
return
}
// Get the next connection
const nextConn = this.connectionQueue.shift()
if (!nextConn) return
this.log(
`processNextInQueue: processing connection from ${nextConn.clientInfo.clientAddress}:${nextConn.clientInfo.clientPort}`,
)
// Clear the timeout
clearTimeout(nextConn.timeoutId)
// Check if the socket is still valid
if (!nextConn.socket.writable) {
this.log(
`processNextInQueue: socket no longer writable, skipping to next connection`,
)
// Socket closed while waiting, process next in queue
this.processNextInQueue()
// Check connection limit
if (this.handlers.size >= this.maxConnections) {
this.log(`handleConnection: max connections reached, rejecting`)
socket.write(Buffer.from('Too many connections\n'))
socket.end()
return
}
// Attach this socket to a new handler
this.attachSocketToNewHandler(nextConn.socket, nextConn.clientInfo).catch(
(err) => {
this.log(`processNextInQueue: error attaching socket:`, err)
this.dispatchEvent(new CustomEvent('error', { detail: err }))
// Try the next connection
this.processNextInQueue()
},
)
}
/**
* Attach a socket to a new handler
*/
private async attachSocketToNewHandler(
socket: Socket,
clientInfo: { clientAddress: string; clientPort: number },
): Promise<void> {
this.handlerCount++
this.log(
`attachSocketToNewHandler: creating new handler for ${clientInfo.clientAddress}:${clientInfo.clientPort} (handler #${this.handlerCount})`,
)
// Create a new handler for this connection
const handler = new PGLiteSocketHandler({
db: this.db,
queryQueue: this.queryQueue,
closeOnDetach: true,
inspect: this.inspect,
debug: this.debug,
idleTimeout: this.idleTimeout,
})
// Forward error events from the handler
// Track this handler
this.handlers.add(handler)
// Handle errors
handler.addEventListener('error', (event) => {
this.log(
`handler #${handler.handlerId}: error from handler:`,
(event as CustomEvent<Error>).detail,
)
this.dispatchEvent(
new CustomEvent('error', {
detail: (event as CustomEvent<Error>).detail,
}),
)
})
const error = (event as CustomEvent<Error>).detail
// Handle close event to process next queued connection
handler.addEventListener('close', () => {
this.log(`handler #${handler.handlerId}: closed`)
// If this is our active handler, clear it
if (this.activeHandler === handler) {
if (error?.message?.includes('ECONNRESET')) {
this.log(
`handler #${handler.handlerId}: was active handler, processing next connection in queue`,
`handler #${handler.handlerId}: client disconnected (ECONNRESET)`,
)
this.activeHandler = null
// Process next connection in queue
this.processNextInQueue()
} else if (error?.message?.includes('Idle timeout')) {
this.log(`handler #${handler.handlerId}: idle timeout`)
} else {
this.log(`handler #${handler.handlerId}: error:`, error)
}
})
// Handle close event
handler.addEventListener('close', () => {
this.log(`handler #${handler.handlerId}: closed`)
this.handlers.delete(handler)
this.log(`handleConnection: active connections: ${this.handlers.size}`)
})
try {
// Set as active handler
this.activeHandler = handler
this.log(`handler #${handler.handlerId}: attaching socket`)
// Attach the socket to the handler
await handler.attach(socket)
this.dispatchEvent(new CustomEvent('connection', { detail: clientInfo }))
} catch (err) {
// If there was an error attaching, clean up
this.log(`handler #${handler.handlerId}: error attaching socket:`, err)
this.activeHandler = null
if (socket.writable) {
this.log(`handleConnection: error attaching socket:`, err)
this.handlers.delete(handler)
this.dispatchEvent(new CustomEvent('error', { detail: err }))
try {
socket.end()
} catch (closeErr) {
this.log(`handleConnection: error closing socket:`, closeErr)
}
throw err
}
}
public getStats() {
return {
activeConnections: this.handlers.size,
queuedQueries: this.queryQueue.getQueueLength(),
maxConnections: this.maxConnections,
}
}
}

@@ -64,2 +64,8 @@ #!/usr/bin/env node

},
'max-connections': {
type: 'string',
short: 'm',
default: '1',
help: 'Maximum concurrent connections (default: 1)',
},
help: {

@@ -89,2 +95,3 @@ type: 'boolean',

--shutdown-timeout=MS Timeout for graceful subprocess shutdown in ms (default: 5000)
-m, --max-connections=N Maximum concurrent connections (default is no concurrency: 1)
`

@@ -102,2 +109,3 @@

shutdownTimeout: number
maxConnections: number
}

@@ -129,2 +137,3 @@

shutdownTimeout: parseInt(args.values['shutdown-timeout'] as string, 10),
maxConnections: parseInt(args.values['max-connections'] as string, 10),
}

@@ -288,2 +297,3 @@ }

inspect: this.config.debugLevel > 0,
maxConnections: this.config.maxConnections,
})

@@ -290,0 +300,0 @@

@@ -12,7 +12,3 @@ import {

import { PGlite } from '@electric-sql/pglite'
import {
PGLiteSocketHandler,
PGLiteSocketServer,
CONNECTION_QUEUE_TIMEOUT,
} from '../src'
import { PGLiteSocketHandler, PGLiteSocketServer } from '../src'
import { Socket, createConnection } from 'net'

@@ -59,2 +55,3 @@ import { existsSync } from 'fs'

remotePort: 12345,
setNoDelay: vi.fn(),

@@ -86,4 +83,13 @@ // Mock on method with tracking of handlers

// Create a mock QueryQueueManager for testing
const createMockQueryQueue = () => {
return {
enqueue: vi.fn().mockResolvedValue(new Uint8Array(0)),
clearQueueForHandler: vi.fn(),
clearTransactionIfNeeded: vi.fn(),
getQueueLength: vi.fn().mockReturnValue(0),
}
}
describe('PGLiteSocketHandler', () => {
let db: PGlite
let handler: PGLiteSocketHandler

@@ -93,7 +99,8 @@ let mockSocket: ReturnType<typeof createMockSocket> & {

}
let mockQueryQueue: ReturnType<typeof createMockQueryQueue>
beforeEach(async () => {
// Create a PGlite instance for testing
db = await PGlite.create()
handler = new PGLiteSocketHandler({ db })
// Create a mock query queue for testing
mockQueryQueue = createMockQueryQueue()
handler = new PGLiteSocketHandler({ queryQueue: mockQueryQueue as any })
mockSocket = createMockSocket() as any

@@ -103,9 +110,6 @@ })

afterEach(async () => {
// Ensure handler is detached before closing the database
// Ensure handler is detached
if (handler?.isAttached) {
handler.detach(true)
await handler.detach(true)
}
// Clean up
await db.close()
})

@@ -130,3 +134,3 @@

// Then detach
handler.detach(false)
await handler.detach(false)
expect(handler.isAttached).toBe(false)

@@ -141,3 +145,3 @@ expect(mockSocket.removeAllListeners).toHaveBeenCalled()

// Detach with close option
handler.detach(true)
await handler.detach(true)
expect(handler.isAttached).toBe(false)

@@ -309,11 +313,5 @@ expect(mockSocket.end).toHaveBeenCalled()

describe('Connection queuing', () => {
// Mock implementation details
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let handleConnectionSpy: any
let processNextInQueueSpy: any
let attachSocketToNewHandlerSpy: any
describe('Connection multiplexing', () => {
beforeEach(() => {
// Create a server with a short timeout for testing
// Create a server for testing
server = new PGLiteSocketServer({

@@ -324,12 +322,4 @@ db,

path: connOptions.path,
connectionQueueTimeout: 100, // Very short timeout for testing
maxConnections: 100,
})
// Spy on internal methods
handleConnectionSpy = vi.spyOn(server as any, 'handleConnection')
processNextInQueueSpy = vi.spyOn(server as any, 'processNextInQueue')
attachSocketToNewHandlerSpy = vi.spyOn(
server as any,
'attachSocketToNewHandler',
)
})

@@ -350,16 +340,13 @@

// Verify handler was created
expect(attachSocketToNewHandlerSpy).toHaveBeenCalledWith(
socket1,
expect.anything(),
)
// Verify handler was created and tracked
expect((server as any).handlers.size).toBe(1)
expect(connectionHandler).toHaveBeenCalled()
})
it('should queue a second connection when first is active', async () => {
it('should handle multiple simultaneous connections', async () => {
await server.start()
// Setup event listeners
const queuedConnectionHandler = vi.fn()
server.addEventListener('queuedConnection', queuedConnectionHandler)
const connectionHandler = vi.fn()
server.addEventListener('connection', connectionHandler)

@@ -369,27 +356,20 @@ // Create mock sockets

const socket2 = createMockSocket()
const socket3 = createMockSocket()
// Handle first connection
// Handle connections - all should be accepted simultaneously
await (server as any).handleConnection(socket1)
await (server as any).handleConnection(socket2)
await (server as any).handleConnection(socket3)
// The first socket should be attached directly
expect(attachSocketToNewHandlerSpy).toHaveBeenCalledWith(
socket1,
expect.anything(),
)
// All three sockets should have handlers (multiplexed)
expect((server as any).handlers.size).toBe(3)
expect(connectionHandler).toHaveBeenCalledTimes(3)
// Handle second connection - should be queued
await (server as any).handleConnection(socket2)
// The second connection should be queued
expect(queuedConnectionHandler).toHaveBeenCalledTimes(1)
expect(queuedConnectionHandler).toHaveBeenCalledWith(
expect.objectContaining({
detail: expect.objectContaining({
queueSize: 1,
}),
}),
)
// None should be closed - they're all active
expect(socket1.end).not.toHaveBeenCalled()
expect(socket2.end).not.toHaveBeenCalled()
expect(socket3.end).not.toHaveBeenCalled()
})
it('should process next connection when current connection closes', async () => {
it('should remove handler when connection closes', async () => {
await server.start()

@@ -401,69 +381,71 @@

// Setup event listener
const connectionHandler = vi.fn()
server.addEventListener('connection', connectionHandler)
// Handle first connection
// Handle connections
await (server as any).handleConnection(socket1)
// Handle second connection (will be queued)
await (server as any).handleConnection(socket2)
// First connection should be active, but clear the handler for next assertions
expect(connectionHandler).toHaveBeenCalled()
connectionHandler.mockClear()
// Both should be tracked
expect((server as any).handlers.size).toBe(2)
// Simulate closing the first connection
const activeHandler = (server as any).activeHandler
activeHandler.dispatchEvent(new CustomEvent('close'))
// Get the first handler and simulate close
const handlers = Array.from((server as any).handlers)
const handler1 = handlers[0] as PGLiteSocketHandler
handler1.dispatchEvent(new CustomEvent('close'))
// The next connection should be processed
expect(processNextInQueueSpy).toHaveBeenCalled()
expect(attachSocketToNewHandlerSpy).toHaveBeenCalledWith(
socket2,
expect.anything(),
)
// First handler should be removed, second still active
expect((server as any).handlers.size).toBe(1)
})
it('should timeout queued connections after specified time', async () => {
it('should reject connections when max connections reached', async () => {
// Create server with low max connections
server = new PGLiteSocketServer({
db,
host: connOptions.host,
port: connOptions.port,
path: connOptions.path,
maxConnections: 2,
})
await server.start()
// Setup event listeners
const queueTimeoutHandler = vi.fn()
server.addEventListener('queueTimeout', queueTimeoutHandler)
// Create mock sockets
const socket1 = createMockSocket()
const socket2 = createMockSocket()
const socket3 = createMockSocket()
// Handle first connection
// Handle first two connections - should succeed
await (server as any).handleConnection(socket1)
// Handle second connection (will be queued)
await (server as any).handleConnection(socket2)
// Fast-forward time to trigger timeout
vi.advanceTimersByTime(1001)
expect((server as any).handlers.size).toBe(2)
// The queued connection should timeout
expect(queueTimeoutHandler).toHaveBeenCalledTimes(1)
expect(socket2.end).toHaveBeenCalled()
// Third connection should be rejected
await (server as any).handleConnection(socket3)
// Third socket should be closed
expect(socket3.end).toHaveBeenCalled()
expect((server as any).handlers.size).toBe(2)
})
it('should use default timeout value from CONNECTION_QUEUE_TIMEOUT', async () => {
// Create server without specifying timeout
const defaultServer = new PGLiteSocketServer({
db,
host: connOptions.host,
port: connOptions.port,
path: connOptions.path,
})
it('should provide stats about active connections', async () => {
await server.start()
// Check that it's using the default timeout
expect((defaultServer as any).connectionQueueTimeout).toBe(
CONNECTION_QUEUE_TIMEOUT,
)
// Create mock sockets
const socket1 = createMockSocket()
const socket2 = createMockSocket()
// Check initial stats (maxConnections is set to 100 in beforeEach)
let stats = server.getStats()
expect(stats.activeConnections).toBe(0)
expect(stats.maxConnections).toBe(100)
// Handle connections
await (server as any).handleConnection(socket1)
await (server as any).handleConnection(socket2)
// Check updated stats
stats = server.getStats()
expect(stats.activeConnections).toBe(2)
})
it('should clean up queue when stopping the server', async () => {
it('should clean up all handlers when stopping the server', async () => {
await server.start()

@@ -474,9 +456,11 @@

const socket2 = createMockSocket()
const socket3 = createMockSocket()
// Handle first connection
// Handle connections
await (server as any).handleConnection(socket1)
// Handle second connection (will be queued)
await (server as any).handleConnection(socket2)
await (server as any).handleConnection(socket3)
expect((server as any).handlers.size).toBe(3)
// Stop the server

@@ -488,5 +472,6 @@ await server.stop()

expect(socket2.end).toHaveBeenCalled()
expect(socket3.end).toHaveBeenCalled()
// Queue should be emptied
expect((server as any).connectionQueue).toHaveLength(0)
// Handlers should be cleared
expect((server as any).handlers.size).toBe(0)
})

@@ -493,0 +478,0 @@

@@ -64,2 +64,3 @@ import {

host: '127.0.0.1',
maxConnections: 100,
})

@@ -543,6 +544,258 @@

})
it('should handle large queries that split across TCP packets', async () => {
// Create a table
await client.query(`CREATE TABLE test_users (id SERIAL, data TEXT)`)
// Generate >64KB payload to force TCP fragmentation
const largeData = 'x'.repeat(100_000) // 100KB string
// Insert large data
const result = await client.query(`
INSERT INTO test_users (data) VALUES ('${largeData}') RETURNING *
`)
expect(result.rows[0].data).toBe(largeData)
})
it('should handle concurrent clients with interleaved transaction and query', async () => {
// Create a second client connecting to the same server
let client2: typeof Client.prototype
if (DEBUG_TESTS) {
client2 = new Client({
connectionString: DEBUG_TESTS_REAL_SERVER,
connectionTimeoutMillis: 10000,
statement_timeout: 5000,
})
} else {
client2 = new Client(connectionConfig)
}
await client2.connect()
try {
// Client 1 starts a transaction (don't await yet)
const beginResult = await client.query('BEGIN')
// Client 2 makes a simple SELECT 1 query (don't await yet)
const selectPromise = client2.query('SELECT 999999 as one')
// Small delay to ensure SELECT is sent before ROLLBACK
await new Promise((r) => setTimeout(r, 10))
// Client 1 rolls back the transaction (don't await yet)
const rollbackResult = await client.query('ROLLBACK')
const selectResult = await selectPromise
// Verify results
expect(beginResult.command).toBe('BEGIN')
expect(selectResult.rows[0].one).toBe(999999)
expect(rollbackResult.command).toBe('ROLLBACK')
} finally {
await client2.end()
}
}, 30000)
it('should process pending queries when transaction owner disconnects', async () => {
// Create a second client connecting to the same server
let client2: typeof Client.prototype
if (DEBUG_TESTS) {
client2 = new Client({
connectionString: DEBUG_TESTS_REAL_SERVER,
connectionTimeoutMillis: 10000,
statement_timeout: 5000,
})
} else {
client2 = new Client(connectionConfig)
}
await client2.connect()
// Suppress the expected "Connection terminated unexpectedly" error
client2.on('error', () => {
// Expected when we destroy the connection
})
try {
// Client starts a transaction
const beginResult = await client2.query('BEGIN')
expect(beginResult.command).toBe('BEGIN')
// Client 2 sends a query (will be blocked because client is in transaction)
const selectPromise = client.query('SELECT 123456 as val')
// Small delay to ensure SELECT is enqueued
await new Promise((r) => setTimeout(r, 10))
// Client abruptly disconnects (simulating connection abort)
// This should trigger clearTransactionIfNeeded which rolls back
// the transaction and processes pending queries
;(client2 as any).connection.stream.destroy()
// Client 2's query should complete successfully after transaction is cleared
const selectResult = await selectPromise
expect(selectResult.rows[0].val).toBe(123456)
} catch {
expect(false, 'Should not happen')
}
}, 30000)
it('interleaved transactions should work', async () => {
const bob = client
// table that will be accessed by both clients
await client.query(`
CREATE TABLE test_users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`)
// Create a second bob connecting to the same server
let alice: typeof Client.prototype
if (DEBUG_TESTS) {
alice = new Client({
connectionString: DEBUG_TESTS_REAL_SERVER,
connectionTimeoutMillis: 10000,
statement_timeout: 5000,
})
} else {
alice = new Client(connectionConfig)
}
await alice.connect()
alice.on('error', () => {
// Suppress the expected "Connection terminated unexpectedly" error
})
// Client starts a transaction
const aliceBegin = await alice.query('BEGIN')
expect(aliceBegin.command).toBe('BEGIN')
// Client 2 begins its own transaction
const bobBegin = bob.query('BEGIN')
// Small delay to ensure client2.BEGIN is enqueued
await new Promise((r) => setTimeout(r, 10))
const aliceInsertPromise = alice.query(`
INSERT INTO test_users (name, email)
VALUES
('Alice', 'alice@example.com')
RETURNING *
`)
const bobInsertPromise = bob.query(`
INSERT INTO test_users (name, email)
VALUES
('Bob', 'bob@example.com')
RETURNING *
`)
// Small delay to ensure both inserts are enqueued
await new Promise((r) => setTimeout(r, 10))
// bob commits
const bobCommit = bob.query('COMMIT')
// alice rolls back
const aliceRollback = alice.query('ROLLBACK')
await Promise.all([
bobBegin,
aliceInsertPromise,
bobInsertPromise,
aliceRollback,
bobCommit,
])
// Verify only Bob was commited
const testUsers = await bob.query('SELECT * FROM test_users')
expect(testUsers.rows.length).toBe(1)
expect(testUsers.rows[0].name).toBe('Bob')
}, 30000)
it('interleaved transactions should work when one client crashes', async () => {
const bob = client
// table that will be accessed by both clients
await bob.query(`
CREATE TABLE test_users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`)
// Create a second client connecting to the same server
let alice: typeof Client.prototype
if (DEBUG_TESTS) {
alice = new Client({
connectionString: DEBUG_TESTS_REAL_SERVER,
connectionTimeoutMillis: 10000,
statement_timeout: 5000,
})
} else {
alice = new Client(connectionConfig)
}
await alice.connect()
// Suppress the expected "Connection terminated unexpectedly" error
alice.on('error', () => {
// Expected when we destroy the connection
})
try {
// alice starts a transaction
const aliceBegin = await alice.query('BEGIN')
expect(aliceBegin.command).toBe('BEGIN')
// bob begins its own transaction
const bobBegin = bob.query('BEGIN')
// Small delay to ensure client2.BEGIN is enqueued
await new Promise((r) => setTimeout(r, 10))
// alice inserts data
alice.query(`
INSERT INTO test_users (name, email)
VALUES
('Alice', 'alice@example.com')
RETURNING *
`)
// client inserts data
const bobInsert = bob.query(`
INSERT INTO test_users (name, email)
VALUES
('Bob', 'bob@example.com')
RETURNING *
`)
// Small delay to ensure both inserts are enqueued
await new Promise((r) => setTimeout(r, 10))
// Client2 abruptly disconnects (simulating connection abort)
// This should trigger clearTransactionIfNeeded which rolls back
// the transaction and processes pending queries
;(alice as any).connection.stream.destroy()
// bob commits
const bobCommit = bob.query('COMMIT')
await Promise.all([bobBegin, bobInsert, bobCommit])
// Verify only Bob was commited
const selectResult = await bob.query('SELECT * FROM test_users')
expect(selectResult.rows.length).toBe(1)
expect(selectResult.rows[0].name).toBe('Bob')
} catch {
// swallow
}
}, 30000)
})
describe('with extensions via CLI', () => {
const UNIX_SOCKET_DIR_PATH = `/tmp/${Date.now().toString()}`
const UNIX_SOCKET_DIR_PATH = `/tmp/${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
fs.mkdirSync(UNIX_SOCKET_DIR_PATH)

@@ -549,0 +802,0 @@ const UNIX_SOCKET_PATH = `${UNIX_SOCKET_DIR_PATH}/.s.PGSQL.5432`

import{createServer as u}from"net";var g=6e4,a=class a 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=a.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,o)=>o("no data"));let i=new Promise((n,o)=>{this.log("handleData: writing response to socket"),this.socket?this.socket.write(Buffer.from(t),r=>{r?o(`Error while writing to the socket ${r.toString()}`):n(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,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),o="";for(let s=0;s<16;s++)if(s<n){let c=t[i+s];o+=c.toString(16).padStart(2,"0")+" "}else o+=" ";let r="";for(let s=0;s<n;s++){let c=t[i+s];r+=c>=32&&c<=126?String.fromCharCode(c):"."}console.log(`${i.toString(16).padStart(8,"0")} ${o} ${r}`)}}}};a.nextHandlerId=1;var h=a,l=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||"127.0.0.1"),this.inspect=e.inspect??!1,this.debug=e.debug??!1,this.connectionQueueTimeout=e.connectionQueueTimeout??g,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=u(e=>this.handleConnection(e)),new Promise((e,t)=>{if(!this.server)return t(new Error("Server not initialized"));if(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()});else{let i=this.server;i.listen(this.port,this.host,()=>{let n=i.address();if(n===null||typeof n!="object")throw Error("Expected address info");this.port=n.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(){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 h({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}}};export{g as a,h as b,l as c};
//# sourceMappingURL=chunk-F6KLIXM7.js.map
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { PGlite } from '@electric-sql/pglite'\nimport { createServer, Server, Socket } from 'net'\n\n// Connection queue timeout in milliseconds\nexport const CONNECTION_QUEUE_TIMEOUT = 60000 // 60 seconds\n\n/**\n * Options for creating a PGLiteSocketHandler\n */\nexport interface PGLiteSocketHandlerOptions {\n /** The PGlite database instance */\n db: PGlite\n /** Whether to close the socket when detached (default: false) */\n closeOnDetach?: boolean\n /** Print the incoming and outgoing data to the console in hex and ascii */\n inspect?: boolean\n /** Enable debug logging of method calls */\n debug?: boolean\n}\n\n/**\n * Low-level handler for a single socket connection to PGLite\n * Handles the raw protocol communication between a socket and PGLite\n */\nexport class PGLiteSocketHandler extends EventTarget {\n readonly db: PGlite\n private socket: Socket | null = null\n private active = false\n private closeOnDetach: boolean\n private resolveLock?: () => void\n private rejectLock?: (err: Error) => void\n private inspect: boolean\n private debug: boolean\n private readonly id: number\n\n // Static counter for generating unique handler IDs\n private static nextHandlerId = 1\n\n /**\n * Create a new PGLiteSocketHandler\n * @param options Options for the handler\n */\n constructor(options: PGLiteSocketHandlerOptions) {\n super()\n this.db = options.db\n this.closeOnDetach = options.closeOnDetach ?? false\n this.inspect = options.inspect ?? false\n this.debug = options.debug ?? false\n this.id = PGLiteSocketHandler.nextHandlerId++\n\n this.log('constructor: created new handler')\n }\n\n /**\n * Get the unique ID of this handler\n */\n public get handlerId(): number {\n return this.id\n }\n\n /**\n * Log a message if debug is enabled\n * @private\n */\n private log(message: string, ...args: any[]): void {\n if (this.debug) {\n console.log(`[PGLiteSocketHandler#${this.id}] ${message}`, ...args)\n }\n }\n\n /**\n * Attach a socket to this handler\n * @param socket The socket to attach\n * @returns this handler instance\n * @throws Error if a socket is already attached\n */\n public async attach(socket: Socket): Promise<PGLiteSocketHandler> {\n this.log(\n `attach: attaching socket from ${socket.remoteAddress}:${socket.remotePort}`,\n )\n\n if (this.socket) {\n throw new Error('Socket already attached')\n }\n\n this.socket = socket\n this.active = true\n\n // Ensure the PGlite instance is ready\n this.log(`attach: waiting for PGlite to be ready`)\n await this.db.waitReady\n\n // Hold the lock on the PGlite instance\n this.log(`attach: acquiring exclusive lock on PGlite instance`)\n await new Promise<void>((resolve) => {\n this.db.runExclusive(() => {\n // Ensure we have the lock on the PGlite instance\n resolve()\n\n // Use a promise to hold the lock on the PGlite instance\n // this can be resolved or rejected by the handler to release the lock\n return new Promise<void>((resolveLock, rejectLock) => {\n this.resolveLock = resolveLock\n this.rejectLock = rejectLock\n })\n })\n })\n\n // Setup event handlers\n this.log(`attach: setting up socket event handlers`)\n socket.on('data', async (data) => {\n try {\n const result = await this.handleData(data)\n this.log(`socket on data sent: ${result} bytes`)\n } catch (err) {\n this.log('socket on data error: ', err)\n }\n })\n socket.on('error', (err) => this.handleError(err))\n socket.on('close', () => this.handleClose())\n\n return this\n }\n\n /**\n * Detach the current socket from this handler\n * @param close Whether to close the socket when detaching (overrides constructor option)\n * @returns this handler instance\n */\n public detach(close?: boolean): PGLiteSocketHandler {\n this.log(`detach: detaching socket, close=${close ?? this.closeOnDetach}`)\n\n if (!this.socket) {\n this.log(`detach: no socket attached, nothing to do`)\n return this\n }\n\n // Remove all listeners\n this.socket.removeAllListeners('data')\n this.socket.removeAllListeners('error')\n this.socket.removeAllListeners('close')\n\n // Close the socket if requested\n if (close ?? this.closeOnDetach) {\n if (this.socket.writable) {\n this.log(`detach: closing socket`)\n this.socket.end()\n this.socket.destroy()\n }\n }\n\n // Release the lock on the PGlite instance\n this.log(`detach: releasing exclusive lock on PGlite instance`)\n this.resolveLock?.()\n\n this.socket = null\n this.active = false\n return this\n }\n\n /**\n * Check if a socket is currently attached\n */\n public get isAttached(): boolean {\n return this.socket !== null\n }\n\n /**\n * Handle incoming data from the socket\n */\n private async handleData(data: Buffer): Promise<number> {\n if (!this.socket || !this.active) {\n this.log(`handleData: no active socket, ignoring data`)\n return new Promise((_, reject) => reject(`no active socket`))\n }\n\n this.log(`handleData: received ${data.length} bytes`)\n\n // Print the incoming data to the console\n this.inspectData('incoming', data)\n\n try {\n // Process the raw protocol data\n this.log(`handleData: sending data to PGlite for processing`)\n const result = await this.db.execProtocolRaw(new Uint8Array(data))\n\n this.log(`handleData: received ${result.length} bytes from PGlite`)\n\n // Print the outgoing data to the console\n this.inspectData('outgoing', result)\n\n // Send the result back if the socket is still connected\n if (this.socket && this.socket.writable && this.active) {\n if (result.length <= 0) {\n this.log(`handleData: cowardly refusing to send empty packet`)\n return new Promise((_, reject) => reject('no data'))\n }\n\n const promise = new Promise<number>((resolve, reject) => {\n this.log(`handleData: writing response to socket`)\n if (this.socket) {\n this.socket.write(Buffer.from(result), (err?: Error) => {\n if (err) {\n reject(`Error while writing to the socket ${err.toString()}`)\n } else {\n resolve(result.length)\n }\n })\n } else {\n reject(`No socket`)\n }\n })\n\n // Emit data event with byte sizes\n this.dispatchEvent(\n new CustomEvent('data', {\n detail: { incoming: data.length, outgoing: result.length },\n }),\n )\n return promise\n } else {\n this.log(\n `handleData: socket no longer writable or active, discarding response`,\n )\n return new Promise((_, reject) =>\n reject(`No socket, not active or not writeable`),\n )\n }\n } catch (err) {\n this.log(`handleData: error processing data:`, err)\n this.handleError(err as Error)\n return new Promise((_, reject) =>\n reject(`Error while processing data ${(err as Error).toString()}`),\n )\n }\n }\n\n /**\n * Handle errors from the socket\n */\n private handleError(err: Error): void {\n this.log(`handleError:`, err)\n\n // Emit error event\n this.dispatchEvent(new CustomEvent('error', { detail: err }))\n\n // Reject the lock on the PGlite instance\n this.log(`handleError: rejecting exclusive lock on PGlite instance`)\n this.rejectLock?.(err)\n this.resolveLock = undefined\n this.rejectLock = undefined\n\n // Close the connection on error\n this.detach(true)\n }\n\n /**\n * Handle socket close event\n */\n private handleClose(): void {\n this.log(`handleClose: socket closed`)\n\n this.dispatchEvent(new CustomEvent('close'))\n this.detach(false) // Already closed, just clean up\n }\n\n /**\n * Print data in hex and ascii to the console\n */\n private inspectData(\n direction: 'incoming' | 'outgoing',\n data: Buffer | Uint8Array,\n ): void {\n if (!this.inspect) return\n console.log('-'.repeat(75))\n if (direction === 'incoming') {\n console.log('-> incoming', data.length, 'bytes')\n } else {\n console.log('<- outgoing', data.length, 'bytes')\n }\n\n // Process 16 bytes per line\n for (let offset = 0; offset < data.length; offset += 16) {\n // Calculate current chunk size (may be less than 16 for the last chunk)\n const chunkSize = Math.min(16, data.length - offset)\n\n // Build the hex representation\n let hexPart = ''\n for (let i = 0; i < 16; i++) {\n if (i < chunkSize) {\n const byte = data[offset + i]\n hexPart += byte.toString(16).padStart(2, '0') + ' '\n } else {\n hexPart += ' ' // 3 spaces for missing bytes\n }\n }\n\n // Build the ASCII representation\n let asciiPart = ''\n for (let i = 0; i < chunkSize; i++) {\n const byte = data[offset + i]\n // Use printable characters (32-126), replace others with a dot\n asciiPart += byte >= 32 && byte <= 126 ? String.fromCharCode(byte) : '.'\n }\n\n // Print the line with offset in hex, hex values, and ASCII representation\n console.log(\n `${offset.toString(16).padStart(8, '0')} ${hexPart} ${asciiPart}`,\n )\n }\n }\n}\n\n/**\n * Represents a queued connection with timeout\n */\ninterface QueuedConnection {\n socket: Socket\n clientInfo: {\n clientAddress: string\n clientPort: number\n }\n timeoutId: NodeJS.Timeout\n}\n\n/**\n * Options for creating a PGLiteSocketServer\n */\nexport interface PGLiteSocketServerOptions {\n /** The PGlite database instance */\n db: PGlite\n /** The port to listen on (default: 5432) */\n port?: number\n /** The host to bind to (default: 127.0.0.1) */\n host?: string\n /** Unix socket path to bind to (default: undefined). If specified, takes precedence over host:port */\n path?: string\n /** Print the incoming and outgoing data to the console in hex and ascii */\n inspect?: boolean\n /** Connection queue timeout in milliseconds (default: 10000) */\n connectionQueueTimeout?: number\n /** Enable debug logging of method calls */\n debug?: boolean\n}\n\n/**\n * High-level server that manages socket connections to PGLite\n * Creates and manages a TCP server and handles client connections\n */\nexport class PGLiteSocketServer extends EventTarget {\n readonly db: PGlite\n private server: Server | null = null\n private port?: number\n private host?: string\n private path?: string\n private active = false\n private inspect: boolean\n private debug: boolean\n private connectionQueueTimeout: number\n private activeHandler: PGLiteSocketHandler | null = null\n private connectionQueue: QueuedConnection[] = []\n private handlerCount: number = 0\n\n /**\n * Create a new PGLiteSocketServer\n * @param options Options for the server\n */\n constructor(options: PGLiteSocketServerOptions) {\n super()\n this.db = options.db\n if (options.path) {\n this.path = options.path\n } else {\n if (typeof options.port === 'number') {\n // Keep port undefined on port 0, will be set by the OS when we start the server.\n this.port = options.port ?? options.port\n } else {\n this.port = 5432\n }\n this.host = options.host || '127.0.0.1'\n }\n this.inspect = options.inspect ?? false\n this.debug = options.debug ?? false\n this.connectionQueueTimeout =\n options.connectionQueueTimeout ?? CONNECTION_QUEUE_TIMEOUT\n\n this.log(`constructor: created server on ${this.host}:${this.port}`)\n this.log(\n `constructor: connection queue timeout: ${this.connectionQueueTimeout}ms`,\n )\n }\n\n /**\n * Log a message if debug is enabled\n * @private\n */\n private log(message: string, ...args: any[]): void {\n if (this.debug) {\n console.log(`[PGLiteSocketServer] ${message}`, ...args)\n }\n }\n\n /**\n * Start the socket server\n * @returns Promise that resolves when the server is listening\n */\n public async start(): Promise<void> {\n this.log(`start: starting server on ${this.getServerConn()}`)\n\n if (this.server) {\n throw new Error('Socket server already started')\n }\n\n this.active = true\n this.server = createServer((socket) => this.handleConnection(socket))\n\n return new Promise<void>((resolve, reject) => {\n if (!this.server) return reject(new Error('Server not initialized'))\n\n this.server.on('error', (err) => {\n this.log(`start: server error:`, err)\n this.dispatchEvent(new CustomEvent('error', { detail: err }))\n reject(err)\n })\n\n if (this.path) {\n this.server.listen(this.path, () => {\n this.log(`start: server listening on ${this.getServerConn()}`)\n this.dispatchEvent(\n new CustomEvent('listening', {\n detail: { path: this.path },\n }),\n )\n resolve()\n })\n } else {\n const server = this.server\n server.listen(this.port, this.host, () => {\n const address = server.address()\n // We are not using pipes, so return type should be AddressInfo\n if (address === null || typeof address !== 'object') {\n throw Error('Expected address info')\n }\n // Assign the new port number\n this.port = address.port\n this.log(`start: server listening on ${this.getServerConn()}`)\n this.dispatchEvent(\n new CustomEvent('listening', {\n detail: { port: this.port, host: this.host },\n }),\n )\n resolve()\n })\n }\n })\n }\n\n public getServerConn(): string {\n if (this.path) return this.path\n return `${this.host}:${this.port}`\n }\n\n /**\n * Stop the socket server\n * @returns Promise that resolves when the server is closed\n */\n public async stop(): Promise<void> {\n this.log(`stop: stopping server`)\n\n this.active = false\n\n // Clear connection queue\n this.log(\n `stop: clearing connection queue (${this.connectionQueue.length} connections)`,\n )\n\n this.connectionQueue.forEach((queuedConn) => {\n clearTimeout(queuedConn.timeoutId)\n if (queuedConn.socket.writable) {\n this.log(\n `stop: closing queued connection from ${queuedConn.clientInfo.clientAddress}:${queuedConn.clientInfo.clientPort}`,\n )\n queuedConn.socket.end()\n }\n })\n this.connectionQueue = []\n\n // Detach active handler if exists\n if (this.activeHandler) {\n this.log(`stop: detaching active handler #${this.activeHandlerId}`)\n this.activeHandler.detach(true)\n this.activeHandler = null\n }\n\n if (!this.server) {\n this.log(`stop: server not running, nothing to do`)\n return Promise.resolve()\n }\n\n return new Promise<void>((resolve) => {\n if (!this.server) return resolve()\n\n this.server.close(() => {\n this.log(`stop: server closed`)\n this.server = null\n this.dispatchEvent(new CustomEvent('close'))\n resolve()\n })\n })\n }\n\n /**\n * Get the active handler ID, or null if no active handler\n */\n private get activeHandlerId(): number | null {\n return this.activeHandler?.handlerId ?? null\n }\n\n /**\n * Handle a new client connection\n */\n private async handleConnection(socket: Socket): Promise<void> {\n const clientInfo = {\n clientAddress: socket.remoteAddress || 'unknown',\n clientPort: socket.remotePort || 0,\n }\n\n this.log(\n `handleConnection: new connection from ${clientInfo.clientAddress}:${clientInfo.clientPort}`,\n )\n\n // If server is not active, close the connection immediately\n if (!this.active) {\n this.log(`handleConnection: server not active, closing connection`)\n socket.end()\n return\n }\n\n // If we don't have an active handler or it's not attached, we can use this connection immediately\n if (!this.activeHandler || !this.activeHandler.isAttached) {\n this.log(`handleConnection: no active handler, attaching socket directly`)\n this.dispatchEvent(new CustomEvent('connection', { detail: clientInfo }))\n await this.attachSocketToNewHandler(socket, clientInfo)\n return\n }\n\n // Otherwise, queue the connection\n this.log(\n `handleConnection: active handler #${this.activeHandlerId} exists, queueing connection`,\n )\n this.enqueueConnection(socket, clientInfo)\n }\n\n /**\n * Add a connection to the queue\n */\n private enqueueConnection(\n socket: Socket,\n clientInfo: { clientAddress: string; clientPort: number },\n ): void {\n this.log(\n `enqueueConnection: queueing connection from ${clientInfo.clientAddress}:${clientInfo.clientPort}, timeout: ${this.connectionQueueTimeout}ms`,\n )\n\n // Set a timeout for this queued connection\n const timeoutId = setTimeout(() => {\n this.log(\n `enqueueConnection: timeout for connection from ${clientInfo.clientAddress}:${clientInfo.clientPort}`,\n )\n\n // Remove from queue\n this.connectionQueue = this.connectionQueue.filter(\n (queuedConn) => queuedConn.socket !== socket,\n )\n\n // End the connection if it's still open\n if (socket.writable) {\n this.log(`enqueueConnection: closing timed out connection`)\n socket.end()\n }\n\n this.dispatchEvent(\n new CustomEvent('queueTimeout', {\n detail: { ...clientInfo, queueSize: this.connectionQueue.length },\n }),\n )\n }, this.connectionQueueTimeout)\n\n // Add to queue\n this.connectionQueue.push({ socket, clientInfo, timeoutId })\n\n this.log(\n `enqueueConnection: connection queued, queue size: ${this.connectionQueue.length}`,\n )\n\n this.dispatchEvent(\n new CustomEvent('queuedConnection', {\n detail: { ...clientInfo, queueSize: this.connectionQueue.length },\n }),\n )\n }\n\n /**\n * Process the next connection in the queue\n */\n private processNextInQueue(): void {\n this.log(\n `processNextInQueue: processing next connection, queue size: ${this.connectionQueue.length}`,\n )\n\n // No connections in queue or server not active\n if (this.connectionQueue.length === 0 || !this.active) {\n this.log(\n `processNextInQueue: no connections in queue or server not active, nothing to do`,\n )\n return\n }\n\n // Get the next connection\n const nextConn = this.connectionQueue.shift()\n if (!nextConn) return\n\n this.log(\n `processNextInQueue: processing connection from ${nextConn.clientInfo.clientAddress}:${nextConn.clientInfo.clientPort}`,\n )\n\n // Clear the timeout\n clearTimeout(nextConn.timeoutId)\n\n // Check if the socket is still valid\n if (!nextConn.socket.writable) {\n this.log(\n `processNextInQueue: socket no longer writable, skipping to next connection`,\n )\n // Socket closed while waiting, process next in queue\n this.processNextInQueue()\n return\n }\n\n // Attach this socket to a new handler\n this.attachSocketToNewHandler(nextConn.socket, nextConn.clientInfo).catch(\n (err) => {\n this.log(`processNextInQueue: error attaching socket:`, err)\n this.dispatchEvent(new CustomEvent('error', { detail: err }))\n // Try the next connection\n this.processNextInQueue()\n },\n )\n }\n\n /**\n * Attach a socket to a new handler\n */\n private async attachSocketToNewHandler(\n socket: Socket,\n clientInfo: { clientAddress: string; clientPort: number },\n ): Promise<void> {\n this.handlerCount++\n\n this.log(\n `attachSocketToNewHandler: creating new handler for ${clientInfo.clientAddress}:${clientInfo.clientPort} (handler #${this.handlerCount})`,\n )\n\n // Create a new handler for this connection\n const handler = new PGLiteSocketHandler({\n db: this.db,\n closeOnDetach: true,\n inspect: this.inspect,\n debug: this.debug,\n })\n\n // Forward error events from the handler\n handler.addEventListener('error', (event) => {\n this.log(\n `handler #${handler.handlerId}: error from handler:`,\n (event as CustomEvent<Error>).detail,\n )\n this.dispatchEvent(\n new CustomEvent('error', {\n detail: (event as CustomEvent<Error>).detail,\n }),\n )\n })\n\n // Handle close event to process next queued connection\n handler.addEventListener('close', () => {\n this.log(`handler #${handler.handlerId}: closed`)\n\n // If this is our active handler, clear it\n if (this.activeHandler === handler) {\n this.log(\n `handler #${handler.handlerId}: was active handler, processing next connection in queue`,\n )\n this.activeHandler = null\n // Process next connection in queue\n this.processNextInQueue()\n }\n })\n\n try {\n // Set as active handler\n this.activeHandler = handler\n\n this.log(`handler #${handler.handlerId}: attaching socket`)\n\n // Attach the socket to the handler\n await handler.attach(socket)\n\n this.dispatchEvent(new CustomEvent('connection', { detail: clientInfo }))\n } catch (err) {\n // If there was an error attaching, clean up\n this.log(`handler #${handler.handlerId}: error attaching socket:`, err)\n this.activeHandler = null\n if (socket.writable) {\n socket.end()\n }\n throw err\n }\n }\n}\n"],"mappings":"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","names":["createServer","CONNECTION_QUEUE_TIMEOUT","_PGLiteSocketHandler","options","message","args","socket","resolve","resolveLock","rejectLock","data","result","err","close","_","reject","promise","direction","offset","chunkSize","hexPart","i","byte","asciiPart","PGLiteSocketHandler","PGLiteSocketServer","server","address","queuedConn","clientInfo","timeoutId","nextConn","handler","event"]}