pglite-server
Advanced tools
+100
-95
| // src/server.ts | ||
| import * as net from "node:net"; | ||
| import * as net from "net"; | ||
@@ -14,7 +14,9 @@ // src/messages.ts | ||
| ["F".charCodeAt(0)]: "FunctionCall", | ||
| ["p".charCodeAt(0)]: "GSSResponse", | ||
| ["P".charCodeAt(0)]: "Parse", | ||
| ["p".charCodeAt(0)]: "PasswordMessage", | ||
| // GSSResponse and SASLResponse share the same 0x70 identifier as PasswordMessage. | ||
| // They cannot be distinguished by identifier alone and require connection state context. | ||
| // ["p".charCodeAt(0)]: "GSSResponse", | ||
| // ["p".charCodeAt(0)]: "SASLResponse", | ||
| ["Q".charCodeAt(0)]: "Query", | ||
| ["p".charCodeAt(0)]: "SASLResponse", | ||
| ["S".charCodeAt(0)]: "Sync", | ||
@@ -29,3 +31,3 @@ ["X".charCodeAt(0)]: "Terminate" | ||
| var INSUFFICIENT_DATA = { | ||
| name: "Unknown", | ||
| name: "InsufficientData", | ||
| length: 0, | ||
@@ -47,39 +49,33 @@ buffer: Buffer.alloc(0) | ||
| function parseMessage(buffer) { | ||
| if (buffer.length === 0) { | ||
| if (buffer.length < 5) { | ||
| return INSUFFICIENT_DATA; | ||
| } | ||
| if (isCancelRequest(buffer)) { | ||
| const length2 = buffer.readUint32BE(0); | ||
| return { | ||
| name: "CancelRequest", | ||
| length: length2, | ||
| buffer: Buffer.from(buffer.subarray(0, length2)) | ||
| }; | ||
| if (buffer.length >= 8) { | ||
| let unidentifiedName = null; | ||
| if (isCancelRequest(buffer)) { | ||
| unidentifiedName = "CancelRequest"; | ||
| } else if (isGSSENCRequest(buffer)) { | ||
| unidentifiedName = "GSSENCRequest"; | ||
| } else if (isSSLRequest(buffer)) { | ||
| unidentifiedName = "SSLRequest"; | ||
| } else if (isStartupMessage(buffer)) { | ||
| unidentifiedName = "StartupMessage"; | ||
| } | ||
| if (unidentifiedName) { | ||
| const length2 = buffer.readUint32BE(0); | ||
| if (buffer.length < length2) { | ||
| return INSUFFICIENT_DATA; | ||
| } | ||
| return { | ||
| name: unidentifiedName, | ||
| length: length2, | ||
| buffer: Buffer.from(buffer.subarray(0, length2)) | ||
| }; | ||
| } | ||
| } | ||
| if (isGSSENCRequest(buffer)) { | ||
| const length2 = buffer.readUint32BE(0); | ||
| return { | ||
| name: "GSSENCRequest", | ||
| length: length2, | ||
| buffer: Buffer.from(buffer.subarray(0, length2)) | ||
| }; | ||
| } | ||
| if (isSSLRequest(buffer)) { | ||
| const length2 = buffer.readUint32BE(0); | ||
| return { | ||
| name: "SSLRequest", | ||
| length: length2, | ||
| buffer: Buffer.from(buffer.subarray(0, length2)) | ||
| }; | ||
| } | ||
| if (isStartupMessage(buffer)) { | ||
| const length2 = buffer.readUint32BE(0); | ||
| return { | ||
| name: "StartupMessage", | ||
| length: length2, | ||
| buffer: Buffer.from(buffer.subarray(0, length2)) | ||
| }; | ||
| } | ||
| const name = IDENT_TO_MESSAGE_NAME[buffer.at(0)]; | ||
| if (!name) { | ||
| if (buffer.length < 8) { | ||
| return INSUFFICIENT_DATA; | ||
| } | ||
| return UNKNOWN_MESSAGE; | ||
@@ -103,5 +99,6 @@ } | ||
| write(data) { | ||
| this.updateCapacity(data.length); | ||
| const byteLength = Buffer.byteLength(data); | ||
| this.updateCapacity(byteLength); | ||
| this.#buffer.write(data, this.#offset); | ||
| this.#offset += data.length; | ||
| this.#offset += byteLength; | ||
| } | ||
@@ -131,14 +128,6 @@ writeUint8(data) { | ||
| // src/responses.ts | ||
| function createCancelRequest() { | ||
| return new GrowableOffsetBuffer().toBuffer(); | ||
| } | ||
| function createGSSENCRequest() { | ||
| return new GrowableOffsetBuffer().toBuffer(); | ||
| } | ||
| function createSSLRequestReponse() { | ||
| const sslNegotiation = new GrowableOffsetBuffer(); | ||
| sslNegotiation.write("N"); | ||
| return sslNegotiation.toBuffer(); | ||
| } | ||
| function createStartupMessageReponse() { | ||
| var GSSENC_REQUEST_RESPONSE = Buffer.from("N"); | ||
| var SSL_REQUEST_RESPONSE = Buffer.from("N"); | ||
| var STARTUP_MESSAGE_RESPONSE = buildStartupMessageResponse(); | ||
| function buildStartupMessageResponse() { | ||
| const authOk = new GrowableOffsetBuffer(); | ||
@@ -152,3 +141,3 @@ authOk.write("R"); | ||
| parameterStatus.write("S"); | ||
| parameterStatus.writeUint32BE(6 + paramKey.length + paramValue.length); | ||
| parameterStatus.writeUint32BE(6 + Buffer.byteLength(paramKey) + Buffer.byteLength(paramValue)); | ||
| parameterStatus.write(paramKey); | ||
@@ -174,6 +163,6 @@ parameterStatus.writeUint8(0); | ||
| } | ||
| function createErrorReponse(message) { | ||
| function createErrorResponse(message) { | ||
| const errorResponse = new GrowableOffsetBuffer(); | ||
| errorResponse.write("E"); | ||
| errorResponse.writeUint32BE(7 + message.length); | ||
| errorResponse.writeUint32BE(7 + Buffer.byteLength(message)); | ||
| errorResponse.write("M"); | ||
@@ -191,13 +180,16 @@ errorResponse.write(message); | ||
| switch (message.name) { | ||
| // https://www.postgresql.org/docs/current/protocol-flow.html#PROTOCOL-FLOW-CANCELING-REQUESTS | ||
| // CancelRequest is sent on a separate connection and expects no response. | ||
| // The server should simply close the connection after receiving it. | ||
| case "CancelRequest": { | ||
| return createCancelRequest(); | ||
| return null; | ||
| } | ||
| case "GSSENCRequest": { | ||
| return createGSSENCRequest(); | ||
| return GSSENC_REQUEST_RESPONSE; | ||
| } | ||
| case "SSLRequest": { | ||
| return createSSLRequestReponse(); | ||
| return SSL_REQUEST_RESPONSE; | ||
| } | ||
| case "StartupMessage": { | ||
| return createStartupMessageReponse(); | ||
| return STARTUP_MESSAGE_RESPONSE; | ||
| } | ||
@@ -209,4 +201,4 @@ default: { | ||
| } catch (e) { | ||
| const message2 = e instanceof Error ? e.message : "Unknown error message"; | ||
| return createErrorReponse(message2); | ||
| const errorMessage = e instanceof Error ? e.message : "Unknown error message"; | ||
| return createErrorResponse(errorMessage); | ||
| } | ||
@@ -249,41 +241,57 @@ } | ||
| // src/server.ts | ||
| async function processBuffer(clientBuffer, socket, db, logger) { | ||
| while (clientBuffer.length > 0) { | ||
| const message = parseMessage(clientBuffer); | ||
| logger.debug(`${"-".repeat(42)} | ||
| `); | ||
| logger.debug(`> Current buffer`); | ||
| logger.debug(`> Length: ${clientBuffer.length}`); | ||
| logger.debug(`> Raw:`, clientBuffer); | ||
| logger.debug(`> Text: ${clientBuffer.toString()}`); | ||
| logger.debug(``); | ||
| logger.debug(`>> Message name: ${message.name}`); | ||
| logger.debug(`>> Message length: ${message.length}`); | ||
| logger.debug(`>> Message buffer raw:`, message.buffer); | ||
| logger.debug(`>> Message buffer text: ${message.buffer.toString()}`); | ||
| logger.debug(``); | ||
| if (message.name === "InsufficientData") { | ||
| break; | ||
| } | ||
| if (message.name === "Unknown" || message.name === "Terminate") { | ||
| socket.end(); | ||
| return clientBuffer; | ||
| } | ||
| const response = await createMessageResponse(message, db); | ||
| if (response === null) { | ||
| socket.end(); | ||
| return clientBuffer; | ||
| } | ||
| socket.write(response); | ||
| clientBuffer = Buffer.from(clientBuffer.subarray(message.length)); | ||
| logger.debug(`> Remaining buffer`); | ||
| logger.debug(`> Length: ${clientBuffer.length}`); | ||
| logger.debug(`> Raw:`, clientBuffer); | ||
| logger.debug(`> Text: ${clientBuffer.toString() || "<empty>"}`); | ||
| logger.debug(``); | ||
| } | ||
| return clientBuffer; | ||
| } | ||
| function createServer2(db, options = { logLevel: 2 /* Info */ }) { | ||
| const server = net.createServer(options); | ||
| const { logLevel, ...serverOptions } = options; | ||
| const server = net.createServer(serverOptions); | ||
| server.on("connection", function(socket) { | ||
| let clientBuffer = Buffer.allocUnsafe(0); | ||
| const clientAddr = `${socket.remoteAddress}:${socket.remotePort}`; | ||
| const logger = new Logger(options.logLevel, clientAddr); | ||
| const logger = new Logger(logLevel, clientAddr); | ||
| logger.info(`Client connected`); | ||
| socket.on("data", async (data) => { | ||
| socket.on("data", (data) => { | ||
| clientBuffer = Buffer.concat([clientBuffer, data]); | ||
| while (clientBuffer.length > 0) { | ||
| const message = parseMessage(clientBuffer); | ||
| logger.debug(`${"-".repeat(42)} | ||
| `); | ||
| logger.debug(`> Current buffer`); | ||
| logger.debug(`> Length: ${clientBuffer.length}`); | ||
| logger.debug(`> Raw:`, clientBuffer); | ||
| logger.debug(`> Text: ${clientBuffer.toString()}`); | ||
| logger.debug(``); | ||
| logger.debug(`>> Message name: ${message.name}`); | ||
| logger.debug(`>> Message length: ${message.length}`); | ||
| logger.debug(`>> Message buffer raw:`, message.buffer); | ||
| logger.debug(`>> Message buffer text: ${message.buffer.toString()}`); | ||
| logger.debug(``); | ||
| if (message.name === "InsufficientData") { | ||
| continue; | ||
| } | ||
| if (message.name === "Unknown" || message.name === "Terminate") { | ||
| socket.end(); | ||
| return; | ||
| } | ||
| const response = await createMessageResponse(message, db); | ||
| socket.write(response); | ||
| clientBuffer = Buffer.from(clientBuffer.subarray(message.length)); | ||
| logger.debug(`> Remaining buffer`); | ||
| logger.debug(`> Length: ${clientBuffer.length}`); | ||
| logger.debug(`> Raw:`, clientBuffer); | ||
| logger.debug(`> Text: ${clientBuffer.toString() || "<empty>"}`); | ||
| logger.debug(``); | ||
| } | ||
| socket.pause(); | ||
| processBuffer(clientBuffer, socket, db, logger).then((remaining) => { | ||
| clientBuffer = remaining; | ||
| socket.resume(); | ||
| }).catch((err) => { | ||
| logger.error("Unhandled error processing buffer:", err); | ||
| socket.end(); | ||
| }); | ||
| }); | ||
@@ -298,5 +306,2 @@ socket.on("end", () => { | ||
| }); | ||
| server.on("error", (err) => { | ||
| throw err; | ||
| }); | ||
| return server; | ||
@@ -303,0 +308,0 @@ } |
+10
-7
| { | ||
| "name": "pglite-server", | ||
| "description": "Wire Protocol Server for PGlite", | ||
| "version": "0.1.4", | ||
| "version": "0.1.5", | ||
| "repository": { | ||
@@ -26,10 +26,13 @@ "type": "git", | ||
| }, | ||
| "peerDependencies": { | ||
| "@electric-sql/pglite": ">=0.2.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@electric-sql/pglite": "^0.2.13", | ||
| "@types/node": "^22.9.3", | ||
| "@types/pg": "^8.11.10", | ||
| "pg": "^8.13.1", | ||
| "tsup": "^8.3.5", | ||
| "typescript": "^5.7.2" | ||
| "@electric-sql/pglite": "^0.3.15", | ||
| "@types/node": "^25.2.1", | ||
| "@types/pg": "^8.16.0", | ||
| "pg": "^8.18.0", | ||
| "tsup": "^8.5.1", | ||
| "typescript": "^5.9.3" | ||
| } | ||
| } |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
15214
4.59%309
1.64%1
Infinity%1
Infinity%