diff --git a/apps/cloud/scripts/dev-db.ts b/apps/cloud/scripts/dev-db.ts index 482c869007..039ff3dc76 100644 --- a/apps/cloud/scripts/dev-db.ts +++ b/apps/cloud/scripts/dev-db.ts @@ -104,8 +104,14 @@ await migrate(drizzle(db), { migrationsFolder: MIGRATIONS_FOLDER }); // but prepared statement requires M" -> random 500s on whichever request lost // the race). The patch in patches/@electric-sql%2Fpglite-socket@0.1.4.patch // batches each socket data event into one queue entry and holds handler -// affinity while a pipeline is open; -// src/db/dev-db-socket-concurrency.node.test.ts is the regression test. +// affinity while a pipeline is open. The patch also fixes the queue's failure +// path: stock 0.1.4 `return`ed out of the drain loop when a query REJECTED at +// the JS level, leaving its `processing` flag latched true — after one such +// throw nothing was ever dequeued again, so new connections' startup packets +// sat unanswered (postgres.js CONNECT_TIMEOUT) and the whole stack was bricked +// until restart: the CI e2e "cloud signIn: callback set no session (500)" +// cascade. src/db/dev-db-socket-concurrency.node.test.ts is the regression +// test for all of the above. const server = new PGLiteSocketServer({ db, port: PORT, @@ -115,7 +121,11 @@ const server = new PGLiteSocketServer({ // sent, no Sync) with its socket still OPEN would hold the queue's handler // affinity forever and starve every other connection, since affinity only // releases on detach and detach needs close/error/idle-timeout. In ms; the - // timer resets on every data event, so only a genuinely dead client trips it. + // timer resets on every data event. The patch scopes the reap to connections + // actually HOLDING affinity (open pipeline or transaction): an idle-at-rest + // connection is the normal state of a healthy postgres.js pool held by a + // long-lived scope (SSE), and reaping those raced live queries into + // sporadic `write CONNECTION_ENDED` 500s. idleTimeout: Number(process.env.DEV_DB_IDLE_TIMEOUT_MS ?? 30_000), }); diff --git a/apps/cloud/src/db/dev-db-socket-concurrency.node.test.ts b/apps/cloud/src/db/dev-db-socket-concurrency.node.test.ts index cbbec59fcf..57db00314e 100644 --- a/apps/cloud/src/db/dev-db-socket-concurrency.node.test.ts +++ b/apps/cloud/src/db/dev-db-socket-concurrency.node.test.ts @@ -21,6 +21,8 @@ // with DIFFERENT parameter counts (the exact drizzle/postgres-js shape) through // one PGLiteSocketServer and asserts zero protocol corruption. +import { setTimeout as sleep } from "node:timers/promises"; +import { connect, type Socket } from "node:net"; import { describe, expect, it } from "@effect/vitest"; import { PGlite } from "@electric-sql/pglite"; import { PGLiteSocketServer } from "@electric-sql/pglite-socket"; @@ -30,6 +32,16 @@ const PORT = 45998; const CLIENTS = 6; const QUERIES_PER_CLIENT = 40; +const makeClient = (port: number, connectTimeout = 5) => + postgres(`postgres://postgres:postgres@127.0.0.1:${port}/postgres`, { + max: 1, + idle_timeout: 0, + connect_timeout: connectTimeout, + fetch_types: false, + prepare: true, + onnotice: () => undefined, + }); + describe("dev-db PGlite socket under concurrent connections", () => { it( "serves interleaved multi-connection pipelines without protocol corruption", @@ -48,14 +60,7 @@ describe("dev-db PGlite socket under concurrent connections", () => { const errors: string[] = []; const worker = async (id: number) => { - const sql = postgres(`postgres://postgres:postgres@127.0.0.1:${PORT}/postgres`, { - max: 1, - idle_timeout: 0, - connect_timeout: 10, - fetch_types: false, - prepare: true, - onnotice: () => undefined, - }); + const sql = makeClient(PORT, 10); // oxlint-disable-next-line executor/no-try-catch-or-throw -- test boundary: postgres.js is promise-native and the socket must be closed on every path try { for (let q = 0; q < QUERIES_PER_CLIENT; q++) { @@ -91,4 +96,153 @@ describe("dev-db PGlite socket under concurrent connections", () => { expect(ok).toBe(CLIENTS * QUERIES_PER_CLIENT); }, ); + + // Regression for the CI e2e "cloud signIn: callback set no session (500)" + // cascade: QueryQueueManager.processQueue used to `return` out of its drain + // loop when a query REJECTED (as opposed to returning a wire-level + // ErrorResponse), leaving `processing` latched true. From then on every + // enqueue — including brand-new connections' startup packets — sat in the + // queue forever: in-flight requests hung, postgres.js reconnects died with + // CONNECT_TIMEOUT, and the whole dev stack was bricked until restart. The + // patch rejects the one entry, drops pipeline affinity, and keeps draining. + it( + "a rejected query fails one client, not the whole socket server", + { timeout: 30_000 }, + async () => { + const port = 45997; + const db = await PGlite.create(); + const server = new PGLiteSocketServer({ db, port, host: "127.0.0.1", maxConnections: 100 }); + await server.start(); + + const first = makeClient(port); + // oxlint-disable-next-line executor/no-try-catch-or-throw -- test boundary: sockets must be closed on every path + try { + expect((await first.unsafe(`select 1 as one`))[0]).toEqual({ one: 1 }); + + // Force the NEXT protocol exchange to reject at the JS level, the shape + // PGlite produces when the shared session is broken mid-run. + const real = db.execProtocolRawStream.bind(db); + let arm = true; + (db as { execProtocolRawStream: typeof real }).execProtocolRawStream = (...args) => { + if (arm) { + arm = false; + // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- test boundary: simulating a PGlite internal failure requires a raw throw + throw new Error("synthetic PGlite failure"); + } + return real(...args); + }; + + await expect(first.unsafe(`select 2 as two`)).rejects.toThrow(); + + // The poisoned entry must take down only its own connection: a fresh + // client (new socket, full startup handshake) still gets served. + const second = makeClient(port); + // oxlint-disable-next-line executor/no-try-catch-or-throw -- test boundary: sockets must be closed on every path + try { + expect((await second.unsafe(`select 3 as three`))[0]).toEqual({ three: 3 }); + } finally { + // oxlint-disable-next-line executor/no-promise-catch -- test boundary: a failed teardown must not mask the assertion + await second.end({ timeout: 5 }).catch(() => {}); + } + } finally { + // oxlint-disable-next-line executor/no-promise-catch -- test boundary: a failed teardown must not mask the assertion + await first.end({ timeout: 5 }).catch(() => {}); + await server.stop(); + await db.close(); + } + }, + ); + + // Regression for the sporadic `write CONNECTION_ENDED` 500s: the server's + // idleTimeout backstop used to kill ANY connection with no traffic for the + // window, which is the resting state of every healthy postgres.js pool + // connection (idle_timeout: 0) held by a long-lived scope. The backstop now + // only fires on a connection that is actually blocking the shared session — + // an open pipeline or an open transaction. + it("an idle-at-rest connection outlives the idle backstop", { timeout: 30_000 }, async () => { + const port = 45996; + const db = await PGlite.create(); + const server = new PGLiteSocketServer({ + db, + port, + host: "127.0.0.1", + maxConnections: 100, + idleTimeout: 250, + }); + await server.start(); + + const sql = makeClient(port); + // oxlint-disable-next-line executor/no-try-catch-or-throw -- test boundary: sockets must be closed on every path + try { + expect((await sql.unsafe(`select 1 as one`))[0]).toEqual({ one: 1 }); + await sleep(900); + expect((await sql.unsafe(`select 2 as two`))[0]).toEqual({ two: 2 }); + } finally { + // oxlint-disable-next-line executor/no-promise-catch -- test boundary: a failed teardown must not mask the assertion + await sql.end({ timeout: 5 }).catch(() => {}); + await server.stop(); + await db.close(); + } + }); + + // The backstop's actual job still works: a client that opens a pipeline + // (Parse sent, never Sync) and goes silent holds queue affinity, which + // starves every other connection. The idle timer must reap exactly that + // client and hand the queue back. + it( + "a client stalled mid-pipeline is reaped and the queue recovers", + { timeout: 30_000 }, + async () => { + const port = 45995; + const db = await PGlite.create(); + const server = new PGLiteSocketServer({ + db, + port, + host: "127.0.0.1", + maxConnections: 100, + idleTimeout: 250, + }); + await server.start(); + + // Hand-rolled wire client: complete the trust-auth startup, then send a + // lone Parse. Its last frame type ('P') marks the pipeline open, so the + // handler takes affinity and every other connection queues behind it. + const staller: Socket = connect(port, "127.0.0.1"); + await new Promise((res, rej) => { + staller.once("connect", res); + staller.once("error", rej); + }); + const startupBody = Buffer.concat([ + Buffer.from([0, 3, 0, 0]), + Buffer.from("user\0postgres\0database\0postgres\0\0"), + ]); + const startup = Buffer.concat([Buffer.alloc(4), startupBody]); + startup.writeInt32BE(startup.length, 0); + staller.write(startup); + // Wait for AuthenticationOk + ReadyForQuery before opening the pipeline, + // so the Parse is its own data event (and its own queue entry). + await new Promise((res) => { + staller.on("data", (chunk: Buffer) => { + if (chunk.includes(0x5a)) res(); // 'Z' = ReadyForQuery + }); + }); + const parseBody = Buffer.from("\0select 1\0\0\0"); + const parse = Buffer.concat([Buffer.from("P"), Buffer.alloc(4), parseBody]); + parse.writeInt32BE(4 + parseBody.length, 1); + staller.write(parse); + + const bystander = makeClient(port, 10); + // oxlint-disable-next-line executor/no-try-catch-or-throw -- test boundary: sockets must be closed on every path + try { + // Connects and queries only once the staller is reaped (~250ms). + expect((await bystander.unsafe(`select 4 as four`))[0]).toEqual({ four: 4 }); + } finally { + // oxlint-disable-next-line executor/no-promise-catch -- test boundary: a failed teardown must not mask the assertion + await bystander.end({ timeout: 5 }).catch(() => {}); + staller.destroy(); + await server.stop(); + await db.close(); + } + }, + ); }); diff --git a/e2e/scenarios/artifacts.test.ts b/e2e/scenarios/artifacts.test.ts index 63b0cbba82..29f4be3e90 100644 --- a/e2e/scenarios/artifacts.test.ts +++ b/e2e/scenarios/artifacts.test.ts @@ -143,18 +143,31 @@ const recordHandshakeOrdering = async (page: Page): Promise => { const readHandshakeOrdering = (page: Page): Promise> => page.evaluate(() => globalThis.__handshakeOrder ?? []); -const readConsoleStyle = ( - page: Page, -): Promise<{ primary: string; buttonBg: string; styleSheets: number }> => +const readConsoleStyle = (page: Page): Promise<{ primary: string; buttonBg: string }> => page.evaluate(() => { const button = document.querySelector("button"); return { primary: getComputedStyle(document.documentElement).getPropertyValue("--primary").trim(), buttonBg: button ? getComputedStyle(button).backgroundColor : "", - styleSheets: document.styleSheets.length, }; }); +// The shell's compiled stylesheet declares `--mcp-apps-shell-stylesheet: 1` +// on `:root` as a provenance marker (see the shell's globals.css): the shell's +// tokens deliberately mirror the console's, so this marker is the only +// declaration that identifies the sheet. Reading it as a computed value on a +// document's root element answers "did the shell's stylesheet land in THIS +// document?" — unlike counting document.styleSheets, which moves on its own in +// dev (TanStack Start swaps its route-styles as matches settle, and a +// swapped-in link only counts once loaded), which made an equality-of-counts +// assertion flaky. +const readShellStylesheetMarker = (page: Page): Promise => + page.evaluate(() => + getComputedStyle(document.documentElement) + .getPropertyValue("--mcp-apps-shell-stylesheet") + .trim(), + ); + scenario( "Artifacts · create-artifact hands a non-Apps client a deep link that renders the live component", { timeout: 180_000 }, @@ -242,10 +255,11 @@ scenario( yield* browser.session(identity, async ({ page, step }) => { // The console's own styling, sampled BEFORE any artifact is opened. - // The shell ships its own Tailwind build and its own palette (a teal - // `--primary` against the console's near-black), so if its stylesheet - // ever reaches the top-level document again these values move. - let consoleStyleBefore: { primary: string; buttonBg: string; styleSheets: number }; + // The shell ships its own Tailwind build; if its stylesheet ever + // reaches the top-level document again, its base/utility layers move + // these computed values (and its provenance marker appears, asserted + // below). + let consoleStyleBefore: { primary: string; buttonBg: string }; await step("Open the artifact link the agent handed over", async () => { await recordHandshakeOrdering(page); @@ -338,10 +352,6 @@ scenario( expect(after.buttonBg, "a console button keeps its own background").toBe( consoleStyleBefore.buttonBg, ); - expect( - after.styleSheets, - "the shell injected no stylesheet into the console document", - ).toBe(consoleStyleBefore.styleSheets); // And positively: the shell's stylesheet IS present, one document // down. Without this the assertions above would also pass if the @@ -349,18 +359,26 @@ scenario( const shellHasOwnStyles = await page .frameLocator('[data-testid="artifact-shell-frame"]') .locator("html") - .evaluate((html) => { - const primary = getComputedStyle(html).getPropertyValue("--primary").trim(); - return { primary, sheets: html.ownerDocument.styleSheets.length }; - }); + .evaluate((html) => ({ + marker: getComputedStyle(html).getPropertyValue("--mcp-apps-shell-stylesheet").trim(), + sheets: html.ownerDocument.styleSheets.length, + })); expect( shellHasOwnStyles.sheets, "the shell document carries its own stylesheets", ).toBeGreaterThan(0); expect( - shellHasOwnStyles.primary, - "the shell keeps its own palette inside its own document", - ).not.toBe(""); + shellHasOwnStyles.marker, + "the shell document carries the shell's own compiled stylesheet", + ).toBe("1"); + + // The marker is the injection fingerprint: even a shell sheet that + // lost the cascade race (so the computed values above stayed put) + // would still surface it on the console's root element. + expect( + await readShellStylesheetMarker(page), + "the shell injected no stylesheet into the console document", + ).toBe(""); }); await step("The artifact fills the page and scrolls inside itself", async () => { diff --git a/packages/core/execution/src/tool-invoker.test.ts b/packages/core/execution/src/tool-invoker.test.ts index dd25e61689..4d7d6681aa 100644 --- a/packages/core/execution/src/tool-invoker.test.ts +++ b/packages/core/execution/src/tool-invoker.test.ts @@ -989,48 +989,54 @@ describe("tool discovery", () => { }), ); - it.effect("describes built-in discovery tool shapes that accept their runtime output", () => - Effect.gen(function* () { - const executor = yield* makeSearchExecutor(); - const engine = createExecutionEngine({ executor, codeExecutor }); + it.effect( + "describes built-in discovery tool shapes that accept their runtime output", + () => + Effect.gen(function* () { + const executor = yield* makeSearchExecutor(); + const engine = createExecutionEngine({ executor, codeExecutor }); - const execution = yield* engine.execute( - [ - "const searchDetails = await tools.describe.tool({ path: 'search' });", - "const integrationDetails = await tools.describe.tool({ path: 'executor.integrations.list' });", - "const describeDetails = await tools.describe.tool({ path: 'describe.tool' });", - "return {", - " searchDetails,", - " searchResult: await tools.search({ query: 'repo details', limit: 2 }),", - " integrationDetails,", - " integrationResult: await tools.executor.integrations.list({ limit: 2 }),", - " describeDetails,", - " describeResult: await tools.describe.tool({ path: 'github.org.main.getRepositoryDetails' }),", - "};", - ].join("\n"), - { onElicitation: acceptAll }, - ); + const execution = yield* engine.execute( + [ + "const searchDetails = await tools.describe.tool({ path: 'search' });", + "const integrationDetails = await tools.describe.tool({ path: 'executor.integrations.list' });", + "const describeDetails = await tools.describe.tool({ path: 'describe.tool' });", + "return {", + " searchDetails,", + " searchResult: await tools.search({ query: 'repo details', limit: 2 }),", + " integrationDetails,", + " integrationResult: await tools.executor.integrations.list({ limit: 2 }),", + " describeDetails,", + " describeResult: await tools.describe.tool({ path: 'github.org.main.getRepositoryDetails' }),", + "};", + ].join("\n"), + { onElicitation: acceptAll }, + ); - expect(execution.error).toBeUndefined(); - const observed = execution.result as { - readonly searchDetails: DescribedToolContract; - readonly searchResult: unknown; - readonly integrationDetails: DescribedToolContract; - readonly integrationResult: unknown; - readonly describeDetails: DescribedToolContract; - readonly describeResult: unknown; - }; + expect(execution.error).toBeUndefined(); + const observed = execution.result as { + readonly searchDetails: DescribedToolContract; + readonly searchResult: unknown; + readonly integrationDetails: DescribedToolContract; + readonly integrationResult: unknown; + readonly describeDetails: DescribedToolContract; + readonly describeResult: unknown; + }; - expect( - typeCheckDescribedInvocation(observed.searchDetails, observed.searchResult, ""), - ).toEqual([]); - expect( - typeCheckDescribedInvocation(observed.integrationDetails, observed.integrationResult, ""), - ).toEqual([]); - expect( - typeCheckDescribedInvocation(observed.describeDetails, observed.describeResult, ""), - ).toEqual([]); - }), + expect( + typeCheckDescribedInvocation(observed.searchDetails, observed.searchResult, ""), + ).toEqual([]); + expect( + typeCheckDescribedInvocation(observed.integrationDetails, observed.integrationResult, ""), + ).toEqual([]); + expect( + typeCheckDescribedInvocation(observed.describeDetails, observed.describeResult, ""), + ).toEqual([]); + }), + // Three sandboxed describe.tool round-trips plus three type-checks of the + // described contracts routinely clear vitest's 5s default on a loaded CI + // runner; the same ceiling the file's other sandbox-heavy tests use. + { timeout: 10000 }, ); it.effect("rejects malformed discover calls inside the sandbox", () => diff --git a/packages/hosts/mcp-apps-shell/src/shell/globals.css b/packages/hosts/mcp-apps-shell/src/shell/globals.css index 09d5fd90b4..797144e97f 100644 --- a/packages/hosts/mcp-apps-shell/src/shell/globals.css +++ b/packages/hosts/mcp-apps-shell/src/shell/globals.css @@ -41,3 +41,13 @@ /* Executor's tokens, variants and base layer. Shared verbatim with the inner frame's Tailwind compiler — keep anything build-resolved out of it. */ @import "./theme.css"; + +/* Provenance marker. The shell's tokens deliberately mirror the console's + (theme.css is pinned against the console's globals.css), so no token name or + value distinguishes this compiled stylesheet from the console's own. This + property is the one declaration unique to it: e2e proves style containment + by finding it computed inside the shell document and absent from the console + document — see e2e/scenarios/artifacts.test.ts. */ +:root { + --mcp-apps-shell-stylesheet: 1; +} diff --git a/patches/@electric-sql%2Fpglite-socket@0.1.4.patch b/patches/@electric-sql%2Fpglite-socket@0.1.4.patch index 1209240c3f..f84d44d139 100644 --- a/patches/@electric-sql%2Fpglite-socket@0.1.4.patch +++ b/patches/@electric-sql%2Fpglite-socket@0.1.4.patch @@ -1,13 +1,16 @@ +diff --git a/node_modules/@electric-sql/pglite-socket/.bun-tag-a8fabe72c1056a8f b/.bun-tag-a8fabe72c1056a8f +new file mode 100644 +index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/node_modules/@electric-sql/pglite-socket/.bun-tag-eaa11f63ffd98a26 b/.bun-tag-eaa11f63ffd98a26 new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/dist/chunk-NSUMFCRM.js b/dist/chunk-NSUMFCRM.js -index 37d45ebc5150c43c919fbdb1c7fffb51b18fda8c..dd6cd0fb7ab26b3911778ddc427a02d7c4d6ebd3 100644 +index 37d45ebc5150c43c919fbdb1c7fffb51b18fda8c..cc2a8f6e35c6a676f4fdecb7d580fc1202b74ace 100644 --- a/dist/chunk-NSUMFCRM.js +++ b/dist/chunk-NSUMFCRM.js @@ -1,3 +1,3 @@ -import{createServer as m}from"net";var b=6e4,c=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,i){return new Promise((t,r)=>{let o={handlerId:s,message:e,resolve:t,reject:r,timestamp:Date.now(),onData:i};this.queue.push(o),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(r=>r.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)`);let i=0;try{await this.db.runExclusive(async()=>await this.db.execProtocolRawStream(s.message,{onRawData:t=>{i+=t.length,s.onData(t)}}))}catch(t){this.log(`query from handler #${s.handlerId} failed:`,t),s.reject(t);return}this.log(`query from handler #${s.handlerId} completed, ${i} bytes`),this.lastHandlerId=s.handlerId,s.resolve(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())}},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,...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;if(await this.queryQueue.enqueue(this.id,new Uint8Array(o),n=>{this.log(`handleData: received ${n.length} bytes from PGlite`),this.inspectData("outgoing",n),n.length>0&&this.socket&&this.socket.writable&&this.active&&(this.log("handleData: writing response to socket"),this.socket?.writable?this.socket.write(Buffer.from(n),a=>{a?(this.log("handleData: error writing to socket:",a),h=a):this.log(`handleData: socket sent: ${n.length} bytes`)}):this.log("handleData: socket no longer writable")),i+=n.length}),h)throw h}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=32&&a<=126?String.fromCharCode(a):"."}console.log(`${t.toString(16).padStart(8,"0")} ${o} ${h}`)}}}};l.nextHandlerId=1;var d=l,u=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 c(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=m(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 -+import{createServer as m}from"net";var b=6e4,c=class{constructor(s,e=!1){this.queue=[];this.processing=!1;this.lastHandlerId=null;this.pipelineHandlerId=null;this.db=s,this.debug=e}log(s,...e){this.debug&&console.log(`[QueryQueueManager] ${s}`,...e)}async enqueue(s,e,i,S=!0){return new Promise((t,r)=>{let o={handlerId:s,message:e,resolve:t,reject:r,timestamp:Date.now(),onData:i,closes:S};this.queue.push(o),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;let __affine=this.db.isInTransaction()&&this.lastHandlerId?this.lastHandlerId:this.pipelineHandlerId;if(__affine){let t=this.queue.findIndex(r=>r.handlerId===__affine);t===-1?(this.log("affinity held, waiting for handler",__affine),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)`);let i=0;try{await this.db.runExclusive(async()=>await this.db.execProtocolRawStream(s.message,{onRawData:t=>{i+=t.length,s.onData(t)}}))}catch(t){this.log(`query from handler #${s.handlerId} failed:`,t),s.reject(t);return}this.log(`query from handler #${s.handlerId} completed, ${i} bytes`),this.lastHandlerId=s.handlerId,this.pipelineHandlerId=s.closes?null:s.handlerId,s.resolve(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 clearPipelineIfNeeded(s){this.pipelineHandlerId===s&&(this.pipelineHandlerId=null,await this.db.runExclusive(async()=>await this.db.execProtocolRawStream(new Uint8Array([83,0,0,0,4]),{onRawData:()=>{}})).catch(()=>{}),this.processQueue())}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,...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),await this.queryQueue.clearPipelineIfNeeded(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;const __frames=[];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}__frames.push(o)}if(__frames.length===0)return this.dispatchEvent(new CustomEvent("data",{detail:{incoming:e.length,outgoing:i}})),i;{let o=__frames.length===1?__frames[0]:Buffer.concat(__frames);const __lastF=__frames[__frames.length-1];const __lt=__lastF[0]>=65?__lastF[0]:null;const __closes=__lt===null||__lt===83||__lt===81||__lt===88;let h;if(await this.queryQueue.enqueue(this.id,new Uint8Array(o),n=>{this.log(`handleData: received ${n.length} bytes from PGlite`),this.inspectData("outgoing",n),n.length>0&&this.socket&&this.socket.writable&&this.active&&(this.log("handleData: writing response to socket"),this.socket?.writable?this.socket.write(Buffer.from(n),a=>{a?(this.log("handleData: error writing to socket:",a),h=a):this.log(`handleData: socket sent: ${n.length} bytes`)}):this.log("handleData: socket no longer writable")),i+=n.length},__closes),h)throw h}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=32&&a<=126?String.fromCharCode(a):"."}console.log(`${t.toString(16).padStart(8,"0")} ${o} ${h}`)}}}};l.nextHandlerId=1;var d=l,u=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 c(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=m(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 ++import{createServer as m}from"net";var b=6e4,c=class{constructor(s,e=!1){this.queue=[];this.processing=!1;this.lastHandlerId=null;this.pipelineHandlerId=null;this.db=s,this.debug=e}log(s,...e){this.debug&&console.log(`[QueryQueueManager] ${s}`,...e)}async enqueue(s,e,i,S=!0){return new Promise((t,r)=>{let o={handlerId:s,message:e,resolve:t,reject:r,timestamp:Date.now(),onData:i,closes:S};this.queue.push(o),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;let __affine=this.db.isInTransaction()&&this.lastHandlerId?this.lastHandlerId:this.pipelineHandlerId;if(__affine){let t=this.queue.findIndex(r=>r.handlerId===__affine);t===-1?(this.log("affinity held, waiting for handler",__affine),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)`);let i=0;try{await this.db.runExclusive(async()=>await this.db.execProtocolRawStream(s.message,{onRawData:t=>{i+=t.length,s.onData(t)}}))}catch(t){this.log(`query from handler #${s.handlerId} failed:`,t),s.reject(t),this.pipelineHandlerId=null;continue}this.log(`query from handler #${s.handlerId} completed, ${i} bytes`),this.lastHandlerId=s.handlerId,this.pipelineHandlerId=s.closes?null:s.handlerId,s.resolve(i)}this.processing=!1,this.log("queue processing complete, queue length is",this.queue.length)}}getQueueLength(){return this.queue.length}holdsAffinity(s){return this.pipelineHandlerId===s||this.db.isInTransaction()&&this.lastHandlerId===s}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 clearPipelineIfNeeded(s){this.pipelineHandlerId===s&&(this.pipelineHandlerId=null,await this.db.runExclusive(async()=>await this.db.execProtocolRawStream(new Uint8Array([83,0,0,0,4]),{onRawData:()=>{}})).catch(()=>{}),this.processQueue())}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,...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(()=>{if(!this.queryQueue.holdsAffinity(this.id)){this.resetIdleTimer();return}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),await this.queryQueue.clearPipelineIfNeeded(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;const __frames=[];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}__frames.push(o)}if(__frames.length===0)return this.dispatchEvent(new CustomEvent("data",{detail:{incoming:e.length,outgoing:i}})),i;{let o=__frames.length===1?__frames[0]:Buffer.concat(__frames);const __lastF=__frames[__frames.length-1];const __lt=__lastF[0]>=65?__lastF[0]:null;const __closes=__lt===null||__lt===83||__lt===81||__lt===88;let h;if(await this.queryQueue.enqueue(this.id,new Uint8Array(o),n=>{this.log(`handleData: received ${n.length} bytes from PGlite`),this.inspectData("outgoing",n),n.length>0&&this.socket&&this.socket.writable&&this.active&&(this.log("handleData: writing response to socket"),this.socket?.writable?this.socket.write(Buffer.from(n),a=>{a?(this.log("handleData: error writing to socket:",a),h=a):this.log(`handleData: socket sent: ${n.length} bytes`)}):this.log("handleData: socket no longer writable")),i+=n.length},__closes),h)throw h}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).catch(()=>{})}handleClose(){this.log("handleClose: socket closed"),this.active=!1,this.dispatchEvent(new CustomEvent("close")),this.detach(!1).catch(()=>{})}inspectData(e,i){if(this.inspect){console.log("-".repeat(75)),console.log(e==="incoming"?"-> incoming":"<- outgoing",i.length,"bytes");for(let t=0;t=32&&a<=126?String.fromCharCode(a):"."}console.log(`${t.toString(16).padStart(8,"0")} ${o} ${h}`)}}}};l.nextHandlerId=1;var d=l,u=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 c(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=m(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).catch(()=>{});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 d({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}}};export{b as a,d as b,u as c}; //# sourceMappingURL=chunk-NSUMFCRM.js.map \ No newline at end of file