| import { createServer } from "node:net"; | |
| import { PGlite } from "@electric-sql/pglite"; | |
| const PORT = 5432; | |
| const db = new PGlite(); | |
| await db.exec(` | |
| CREATE TABLE IF NOT EXISTS test ( | |
| id SERIAL PRIMARY KEY, | |
| name TEXT | |
| ); | |
| INSERT INTO test (name) VALUES ('test'); | |
| `); | |
| function isSSLRequest(buffer) { | |
| return ( | |
| buffer.at(4) === 0x04 && | |
| buffer.at(5) === 0xd2 && | |
| buffer.at(6) === 0x16 && | |
| buffer.at(7) === 0x2f | |
| ); | |
| } | |
| function isStartupMessage(buffer) { | |
| return ( | |
| buffer.at(4) === 0x00 && | |
| buffer.at(5) === 0x03 && | |
| buffer.at(6) === 0x00 && | |
| buffer.at(7) === 0x00 | |
| ); | |
| } | |
| function isExitMessage(buffer) { | |
| return buffer.at(0) === 0x58; // 'X' | |
| } | |
| function isQueryMessage(buffer) { | |
| return buffer.at(0) === 0x51; // 'Q' | |
| } | |
| const server = createServer(); | |
| server.on("connection", function (socket) { | |
| const clientAddr = `${socket.remoteAddress}:${socket.remotePort}`; | |
| console.log(`Client connected: ${clientAddr}`); | |
| // https://www.postgresql.org/docs/current/protocol-message-formats.html | |
| socket.on("data", async (data) => { | |
| if (isSSLRequest(data)) { | |
| // SSL negotiation | |
| const sslNegotiation = Buffer.alloc(1); | |
| sslNegotiation.write("N"); | |
| socket.write(sslNegotiation); | |
| } else if (isStartupMessage(data)) { | |
| // AuthenticationOk | |
| const authOk = Buffer.alloc(9); | |
| authOk.write("R"); // 'R' for AuthenticationOk | |
| authOk.writeInt8(8, 4); // Length | |
| authOk.writeInt8(0, 7); // AuthenticationOk | |
| // BackendKeyData | |
| const backendKeyData = Buffer.alloc(13); | |
| backendKeyData.write("K"); // Message type | |
| backendKeyData.writeInt8(12, 4); // Message length | |
| backendKeyData.writeInt16BE(1234, 7); // Process ID | |
| backendKeyData.writeInt16BE(5679, 11); // Secret key | |
| // ReadyForQuery | |
| const readyForQuery = Buffer.alloc(6); | |
| readyForQuery.write("Z"); // 'Z' for ReadyForQuery | |
| readyForQuery.writeInt8(5, 4); // Length | |
| readyForQuery.write("I", 5); // Transaction status indicator, 'I' for idle | |
| socket.write(Buffer.concat([authOk, backendKeyData, readyForQuery])); | |
| } else if (isExitMessage(data)) { | |
| socket.end(); | |
| } else if (isQueryMessage(data)) { | |
| const result = await db.execProtocol(data); | |
| socket.write(Buffer.concat(result.map(([_, buffer]) => buffer))); | |
| } else { | |
| console.log("Unknown message:", data); | |
| } | |
| }); | |
| socket.on("end", () => { | |
| console.log(`Client disconnected: ${clientAddr}`); | |
| }); | |
| socket.on("error", (err) => { | |
| console.log(`Client ${clientAddr} error:`, err); | |
| socket.end(); | |
| }); | |
| }); | |
| server.on("error", (err) => { | |
| console.log(`Server error:`, err); | |
| }); | |
| server.listen(PORT, () => { | |
| console.log(`Server bound to port ${PORT}`); | |
| }); |