|
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}`); |
|
}); |