UNPKG

@worker-tools/deno-kv-storage

Version:

An implementation of the StorageArea (1,2,3) interface for Deno with an extensible system for supporting various database backends.

754 lines 41.8 kB
/*! * Substantial parts adapted from https://github.com/brianc/node-postgres * which is licensed as follows: * * The MIT License (MIT) * * Copyright (c) 2010 - 2019 Brian Carlson * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * 'Software'), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var _Connection_instances, _Connection_bufReader, _Connection_bufWriter, _Connection_conn, _Connection_connection_params, _Connection_message_header, _Connection_onDisconnection, _Connection_packetWriter, _Connection_pid, _Connection_queryLock, _Connection_secretKey, _Connection_tls, _Connection_transport, _Connection_readMessage, _Connection_serverAcceptsTLS, _Connection_sendStartupMessage, _Connection_openConnection, _Connection_openSocketConnection, _Connection_openTlsConnection, _Connection_resetConnectionMetadata, _Connection_closeConnection, _Connection_startup, _Connection_authenticate, _Connection_authenticateWithClearPassword, _Connection_authenticateWithMd5, _Connection_authenticateWithSasl, _Connection_simpleQuery, _Connection_appendQueryToMessage, _Connection_appendArgumentsToMessage, _Connection_appendDescribeToMessage, _Connection_appendExecuteToMessage, _Connection_appendSyncToMessage, _Connection_processErrorUnsafe, _Connection_preparedQuery; import { bold, BufReader, BufWriter, joinPath, yellow } from "../deps.js"; import { DeferredStack } from "../utils/deferred.js"; import { getSocketName, readUInt32BE } from "../utils/utils.js"; import { PacketWriter } from "./packet.js"; import { Message, parseBackendKeyMessage, parseCommandCompleteMessage, parseNoticeMessage, parseRowDataMessage, parseRowDescriptionMessage, } from "./message.js"; import { QueryArrayResult, QueryObjectResult, ResultType, } from "../query/query.js"; import * as scram from "./scram.js"; import { ConnectionError, ConnectionParamsError, PostgresError, } from "../client/error.js"; import { AUTHENTICATION_TYPE, ERROR_MESSAGE, INCOMING_AUTHENTICATION_MESSAGES, INCOMING_QUERY_MESSAGES, INCOMING_TLS_MESSAGES, } from "./message_code.js"; import { hashMd5Password } from "./auth.js"; function assertSuccessfulStartup(msg) { switch (msg.type) { case ERROR_MESSAGE: throw new PostgresError(parseNoticeMessage(msg)); } } function assertSuccessfulAuthentication(auth_message) { if (auth_message.type === ERROR_MESSAGE) { throw new PostgresError(parseNoticeMessage(auth_message)); } if (auth_message.type !== INCOMING_AUTHENTICATION_MESSAGES.AUTHENTICATION) { throw new Error(`Unexpected auth response: ${auth_message.type}.`); } const responseCode = auth_message.reader.readInt32(); if (responseCode !== 0) { throw new Error(`Unexpected auth response code: ${responseCode}.`); } } function logNotice(notice) { console.error(`${bold(yellow(notice.severity))}: ${notice.message}`); } const decoder = new TextDecoder(); const encoder = new TextEncoder(); // TODO // - Refactor properties to not be lazily initialized // or to handle their undefined value export class Connection { constructor(connection_params, disconnection_callback) { _Connection_instances.add(this); _Connection_bufReader.set(this, void 0); _Connection_bufWriter.set(this, void 0); _Connection_conn.set(this, void 0); Object.defineProperty(this, "connected", { enumerable: true, configurable: true, writable: true, value: false }); _Connection_connection_params.set(this, void 0); _Connection_message_header.set(this, new Uint8Array(5)); _Connection_onDisconnection.set(this, void 0); _Connection_packetWriter.set(this, new PacketWriter()); _Connection_pid.set(this, void 0); _Connection_queryLock.set(this, new DeferredStack(1, [undefined])); // TODO // Find out what the secret key is for _Connection_secretKey.set(this, void 0); _Connection_tls.set(this, void 0); _Connection_transport.set(this, void 0); __classPrivateFieldSet(this, _Connection_connection_params, connection_params, "f"); __classPrivateFieldSet(this, _Connection_onDisconnection, disconnection_callback, "f"); } get pid() { return __classPrivateFieldGet(this, _Connection_pid, "f"); } /** Indicates if the connection is carried over TLS */ get tls() { return __classPrivateFieldGet(this, _Connection_tls, "f"); } /** Indicates the connection protocol used */ get transport() { return __classPrivateFieldGet(this, _Connection_transport, "f"); } /** * Calling startup on a connection twice will create a new session and overwrite the previous one * * @param is_reconnection This indicates whether the startup should behave as if there was * a connection previously established, or if it should attempt to create a connection first * * https://www.postgresql.org/docs/14/protocol-flow.html#id-1.10.5.7.3 */ async startup(is_reconnection) { if (is_reconnection && __classPrivateFieldGet(this, _Connection_connection_params, "f").connection.attempts === 0) { throw new Error("The client has been disconnected from the database. Enable reconnection in the client to attempt reconnection after failure"); } let reconnection_attempts = 0; const max_reconnections = __classPrivateFieldGet(this, _Connection_connection_params, "f").connection.attempts; let error; // If no connection has been established and the reconnection attempts are // set to zero, attempt to connect at least once if (!is_reconnection && __classPrivateFieldGet(this, _Connection_connection_params, "f").connection.attempts === 0) { try { await __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_startup).call(this); } catch (e) { error = e; } } else { // If the reconnection attempts are set to zero the client won't attempt to // reconnect, but it won't error either, this "no reconnections" behavior // should be handled wherever the reconnection is requested while (reconnection_attempts < max_reconnections) { try { await __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_startup).call(this); break; } catch (e) { // TODO // Eventually distinguish between connection errors and normal errors reconnection_attempts++; if (reconnection_attempts === max_reconnections) { error = e; } } } } if (error) { await this.end(); throw error; } } async query(query) { if (!this.connected) { await this.startup(true); } await __classPrivateFieldGet(this, _Connection_queryLock, "f").pop(); try { if (query.args.length === 0) { return await __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_simpleQuery).call(this, query); } else { return await __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_preparedQuery).call(this, query); } } catch (e) { if (e instanceof ConnectionError) { await this.end(); } throw e; } finally { __classPrivateFieldGet(this, _Connection_queryLock, "f").push(undefined); } } async end() { if (this.connected) { const terminationMessage = new Uint8Array([0x58, 0x00, 0x00, 0x00, 0x04]); await __classPrivateFieldGet(this, _Connection_bufWriter, "f").write(terminationMessage); try { await __classPrivateFieldGet(this, _Connection_bufWriter, "f").flush(); } catch (_e) { // This steps can fail if the underlying connection was closed ungracefully } finally { __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_closeConnection).call(this); __classPrivateFieldGet(this, _Connection_onDisconnection, "f").call(this); } } } } _Connection_bufReader = new WeakMap(), _Connection_bufWriter = new WeakMap(), _Connection_conn = new WeakMap(), _Connection_connection_params = new WeakMap(), _Connection_message_header = new WeakMap(), _Connection_onDisconnection = new WeakMap(), _Connection_packetWriter = new WeakMap(), _Connection_pid = new WeakMap(), _Connection_queryLock = new WeakMap(), _Connection_secretKey = new WeakMap(), _Connection_tls = new WeakMap(), _Connection_transport = new WeakMap(), _Connection_instances = new WeakSet(), _Connection_readMessage = /** * Read single message sent by backend */ async function _Connection_readMessage() { // Clear buffer before reading the message type __classPrivateFieldGet(this, _Connection_message_header, "f").fill(0); await __classPrivateFieldGet(this, _Connection_bufReader, "f").readFull(__classPrivateFieldGet(this, _Connection_message_header, "f")); const type = decoder.decode(__classPrivateFieldGet(this, _Connection_message_header, "f").slice(0, 1)); // TODO // Investigate if the ascii terminator is the best way to check for a broken // session if (type === "\x00") { // This error means that the database terminated the session without notifying // the library // TODO // This will be removed once we move to async handling of messages by the frontend // However, unnotified disconnection will remain a possibility, that will likely // be handled in another place throw new ConnectionError("The session was terminated unexpectedly"); } const length = readUInt32BE(__classPrivateFieldGet(this, _Connection_message_header, "f"), 1) - 4; const body = new Uint8Array(length); await __classPrivateFieldGet(this, _Connection_bufReader, "f").readFull(body); return new Message(type, length, body); }, _Connection_serverAcceptsTLS = async function _Connection_serverAcceptsTLS() { const writer = __classPrivateFieldGet(this, _Connection_packetWriter, "f"); writer.clear(); writer .addInt32(8) .addInt32(80877103) .join(); await __classPrivateFieldGet(this, _Connection_bufWriter, "f").write(writer.flush()); await __classPrivateFieldGet(this, _Connection_bufWriter, "f").flush(); const response = new Uint8Array(1); await __classPrivateFieldGet(this, _Connection_conn, "f").read(response); switch (String.fromCharCode(response[0])) { case INCOMING_TLS_MESSAGES.ACCEPTS_TLS: return true; case INCOMING_TLS_MESSAGES.NO_ACCEPTS_TLS: return false; default: throw new Error(`Could not check if server accepts SSL connections, server responded with: ${response}`); } }, _Connection_sendStartupMessage = async function _Connection_sendStartupMessage() { const writer = __classPrivateFieldGet(this, _Connection_packetWriter, "f"); writer.clear(); // protocol version - 3.0, written as writer.addInt16(3).addInt16(0); const connParams = __classPrivateFieldGet(this, _Connection_connection_params, "f"); // TODO: recognize other parameters writer.addCString("user").addCString(connParams.user); writer.addCString("database").addCString(connParams.database); writer.addCString("application_name").addCString(connParams.applicationName); // eplicitly set utf-8 encoding writer.addCString("client_encoding").addCString("'utf-8'"); // terminator after all parameters were writter writer.addCString(""); const bodyBuffer = writer.flush(); const bodyLength = bodyBuffer.length + 4; writer.clear(); const finalBuffer = writer .addInt32(bodyLength) .add(bodyBuffer) .join(); await __classPrivateFieldGet(this, _Connection_bufWriter, "f").write(finalBuffer); await __classPrivateFieldGet(this, _Connection_bufWriter, "f").flush(); return await __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_readMessage).call(this); }, _Connection_openConnection = async function _Connection_openConnection(options) { // @ts-ignore This will throw in runtime if the options passed to it are socket related and deno is running // on stable __classPrivateFieldSet(this, _Connection_conn, await Deno.connect(options), "f"); __classPrivateFieldSet(this, _Connection_bufWriter, new BufWriter(__classPrivateFieldGet(this, _Connection_conn, "f")), "f"); __classPrivateFieldSet(this, _Connection_bufReader, new BufReader(__classPrivateFieldGet(this, _Connection_conn, "f")), "f"); }, _Connection_openSocketConnection = async function _Connection_openSocketConnection(path, port) { if (Deno.build.os === "windows") { throw new Error("Socket connection is only available on UNIX systems"); } const socket = await Deno.stat(path); if (socket.isFile) { await __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_openConnection).call(this, { path, transport: "unix" }); } else { const socket_guess = joinPath(path, getSocketName(port)); try { await __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_openConnection).call(this, { path: socket_guess, transport: "unix", }); } catch (e) { if (e instanceof Deno.errors.NotFound) { throw new ConnectionError(`Could not open socket in path "${socket_guess}"`); } throw e; } } }, _Connection_openTlsConnection = async function _Connection_openTlsConnection(connection, options) { // TODO // Remove unstable check on 1.17.0 if ("startTls" in Deno) { // @ts-ignore This API should be available on unstable __classPrivateFieldSet(this, _Connection_conn, await Deno.startTls(connection, options), "f"); __classPrivateFieldSet(this, _Connection_bufWriter, new BufWriter(__classPrivateFieldGet(this, _Connection_conn, "f")), "f"); __classPrivateFieldSet(this, _Connection_bufReader, new BufReader(__classPrivateFieldGet(this, _Connection_conn, "f")), "f"); } else { throw new Error("You need to execute Deno with the `--unstable` argument in order to stablish a TLS connection"); } }, _Connection_resetConnectionMetadata = function _Connection_resetConnectionMetadata() { this.connected = false; __classPrivateFieldSet(this, _Connection_packetWriter, new PacketWriter(), "f"); __classPrivateFieldSet(this, _Connection_pid, undefined, "f"); __classPrivateFieldSet(this, _Connection_queryLock, new DeferredStack(1, [undefined]), "f"); __classPrivateFieldSet(this, _Connection_secretKey, undefined, "f"); __classPrivateFieldSet(this, _Connection_tls, undefined, "f"); __classPrivateFieldSet(this, _Connection_transport, undefined, "f"); }, _Connection_closeConnection = function _Connection_closeConnection() { try { __classPrivateFieldGet(this, _Connection_conn, "f").close(); } catch (_e) { // Swallow if the connection had errored or been closed beforehand } finally { __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_resetConnectionMetadata).call(this); } }, _Connection_startup = async function _Connection_startup() { __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_closeConnection).call(this); const { hostname, host_type, port, tls: { enabled: tls_enabled, enforce: tls_enforced, caCertificates, }, } = __classPrivateFieldGet(this, _Connection_connection_params, "f"); if (host_type === "socket") { await __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_openSocketConnection).call(this, hostname, port); __classPrivateFieldSet(this, _Connection_tls, undefined, "f"); __classPrivateFieldSet(this, _Connection_transport, "socket", "f"); } else { // A BufWriter needs to be available in order to check if the server accepts TLS connections await __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_openConnection).call(this, { hostname, port, transport: "tcp" }); __classPrivateFieldSet(this, _Connection_tls, false, "f"); __classPrivateFieldSet(this, _Connection_transport, "tcp", "f"); if (tls_enabled) { // If TLS is disabled, we don't even try to connect. const accepts_tls = await __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_serverAcceptsTLS).call(this) .catch((e) => { // Make sure to close the connection if the TLS validation throws __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_closeConnection).call(this); throw e; }); // https://www.postgresql.org/docs/14/protocol-flow.html#id-1.10.5.7.11 if (accepts_tls) { try { await __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_openTlsConnection).call(this, __classPrivateFieldGet(this, _Connection_conn, "f"), { hostname, caCerts: caCertificates, }); __classPrivateFieldSet(this, _Connection_tls, true, "f"); } catch (e) { if (!tls_enforced) { console.error(bold(yellow("TLS connection failed with message: ")) + e.message + "\n" + bold("Defaulting to non-encrypted connection")); await __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_openConnection).call(this, { hostname, port, transport: "tcp" }); __classPrivateFieldSet(this, _Connection_tls, false, "f"); } else { throw e; } } } else if (tls_enforced) { // Make sure to close the connection before erroring __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_closeConnection).call(this); throw new Error("The server isn't accepting TLS connections. Change the client configuration so TLS configuration isn't required to connect"); } } } try { let startup_response; try { startup_response = await __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_sendStartupMessage).call(this); } catch (e) { // Make sure to close the connection before erroring or reseting __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_closeConnection).call(this); if (e instanceof Deno.errors.InvalidData && tls_enabled) { if (tls_enforced) { throw new Error("The certificate used to secure the TLS connection is invalid."); } else { console.error(bold(yellow("TLS connection failed with message: ")) + e.message + "\n" + bold("Defaulting to non-encrypted connection")); await __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_openConnection).call(this, { hostname, port, transport: "tcp" }); __classPrivateFieldSet(this, _Connection_tls, false, "f"); __classPrivateFieldSet(this, _Connection_transport, "tcp", "f"); startup_response = await __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_sendStartupMessage).call(this); } } else { throw e; } } assertSuccessfulStartup(startup_response); await __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_authenticate).call(this, startup_response); // Handle connection status // Process connection initialization messages until connection returns ready let message = await __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_readMessage).call(this); while (message.type !== INCOMING_AUTHENTICATION_MESSAGES.READY) { switch (message.type) { // Connection error (wrong database or user) case ERROR_MESSAGE: await __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_processErrorUnsafe).call(this, message, false); break; case INCOMING_AUTHENTICATION_MESSAGES.BACKEND_KEY: { const { pid, secret_key } = parseBackendKeyMessage(message); __classPrivateFieldSet(this, _Connection_pid, pid, "f"); __classPrivateFieldSet(this, _Connection_secretKey, secret_key, "f"); break; } case INCOMING_AUTHENTICATION_MESSAGES.PARAMETER_STATUS: break; default: throw new Error(`Unknown response for startup: ${message.type}`); } message = await __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_readMessage).call(this); } this.connected = true; } catch (e) { __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_closeConnection).call(this); throw e; } }, _Connection_authenticate = /** * Will attempt to authenticate with the database using the provided * password credentials */ async function _Connection_authenticate(authentication_request) { const authentication_type = authentication_request.reader.readInt32(); let authentication_result; switch (authentication_type) { case AUTHENTICATION_TYPE.NO_AUTHENTICATION: authentication_result = authentication_request; break; case AUTHENTICATION_TYPE.CLEAR_TEXT: authentication_result = await __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_authenticateWithClearPassword).call(this); break; case AUTHENTICATION_TYPE.MD5: { const salt = authentication_request.reader.readBytes(4); authentication_result = await __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_authenticateWithMd5).call(this, salt); break; } case AUTHENTICATION_TYPE.SCM: throw new Error("Database server expected SCM authentication, which is not supported at the moment"); case AUTHENTICATION_TYPE.GSS_STARTUP: throw new Error("Database server expected GSS authentication, which is not supported at the moment"); case AUTHENTICATION_TYPE.GSS_CONTINUE: throw new Error("Database server expected GSS authentication, which is not supported at the moment"); case AUTHENTICATION_TYPE.SSPI: throw new Error("Database server expected SSPI authentication, which is not supported at the moment"); case AUTHENTICATION_TYPE.SASL_STARTUP: authentication_result = await __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_authenticateWithSasl).call(this); break; default: throw new Error(`Unknown auth message code ${authentication_type}`); } await assertSuccessfulAuthentication(authentication_result); }, _Connection_authenticateWithClearPassword = async function _Connection_authenticateWithClearPassword() { __classPrivateFieldGet(this, _Connection_packetWriter, "f").clear(); const password = __classPrivateFieldGet(this, _Connection_connection_params, "f").password || ""; const buffer = __classPrivateFieldGet(this, _Connection_packetWriter, "f").addCString(password).flush(0x70); await __classPrivateFieldGet(this, _Connection_bufWriter, "f").write(buffer); await __classPrivateFieldGet(this, _Connection_bufWriter, "f").flush(); return __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_readMessage).call(this); }, _Connection_authenticateWithMd5 = async function _Connection_authenticateWithMd5(salt) { __classPrivateFieldGet(this, _Connection_packetWriter, "f").clear(); if (!__classPrivateFieldGet(this, _Connection_connection_params, "f").password) { throw new ConnectionParamsError("Attempting MD5 authentication with unset password"); } const password = await hashMd5Password(__classPrivateFieldGet(this, _Connection_connection_params, "f").password, __classPrivateFieldGet(this, _Connection_connection_params, "f").user, salt); const buffer = __classPrivateFieldGet(this, _Connection_packetWriter, "f").addCString(password).flush(0x70); await __classPrivateFieldGet(this, _Connection_bufWriter, "f").write(buffer); await __classPrivateFieldGet(this, _Connection_bufWriter, "f").flush(); return __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_readMessage).call(this); }, _Connection_authenticateWithSasl = /** * https://www.postgresql.org/docs/14/sasl-authentication.html */ async function _Connection_authenticateWithSasl() { if (!__classPrivateFieldGet(this, _Connection_connection_params, "f").password) { throw new ConnectionParamsError("Attempting SASL auth with unset password"); } const client = new scram.Client(__classPrivateFieldGet(this, _Connection_connection_params, "f").user, __classPrivateFieldGet(this, _Connection_connection_params, "f").password); const utf8 = new TextDecoder("utf-8"); // SASLInitialResponse const clientFirstMessage = client.composeChallenge(); __classPrivateFieldGet(this, _Connection_packetWriter, "f").clear(); __classPrivateFieldGet(this, _Connection_packetWriter, "f").addCString("SCRAM-SHA-256"); __classPrivateFieldGet(this, _Connection_packetWriter, "f").addInt32(clientFirstMessage.length); __classPrivateFieldGet(this, _Connection_packetWriter, "f").addString(clientFirstMessage); __classPrivateFieldGet(this, _Connection_bufWriter, "f").write(__classPrivateFieldGet(this, _Connection_packetWriter, "f").flush(0x70)); __classPrivateFieldGet(this, _Connection_bufWriter, "f").flush(); const maybe_sasl_continue = await __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_readMessage).call(this); switch (maybe_sasl_continue.type) { case INCOMING_AUTHENTICATION_MESSAGES.AUTHENTICATION: { const authentication_type = maybe_sasl_continue.reader.readInt32(); if (authentication_type !== AUTHENTICATION_TYPE.SASL_CONTINUE) { throw new Error(`Unexpected authentication type in SASL negotiation: ${authentication_type}`); } break; } case ERROR_MESSAGE: throw new PostgresError(parseNoticeMessage(maybe_sasl_continue)); default: throw new Error(`Unexpected message in SASL negotiation: ${maybe_sasl_continue.type}`); } const sasl_continue = utf8.decode(maybe_sasl_continue.reader.readAllBytes()); await client.receiveChallenge(sasl_continue); __classPrivateFieldGet(this, _Connection_packetWriter, "f").clear(); __classPrivateFieldGet(this, _Connection_packetWriter, "f").addString(await client.composeResponse()); __classPrivateFieldGet(this, _Connection_bufWriter, "f").write(__classPrivateFieldGet(this, _Connection_packetWriter, "f").flush(0x70)); __classPrivateFieldGet(this, _Connection_bufWriter, "f").flush(); const maybe_sasl_final = await __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_readMessage).call(this); switch (maybe_sasl_final.type) { case INCOMING_AUTHENTICATION_MESSAGES.AUTHENTICATION: { const authentication_type = maybe_sasl_final.reader.readInt32(); if (authentication_type !== AUTHENTICATION_TYPE.SASL_FINAL) { throw new Error(`Unexpected authentication type in SASL finalization: ${authentication_type}`); } break; } case ERROR_MESSAGE: throw new PostgresError(parseNoticeMessage(maybe_sasl_final)); default: throw new Error(`Unexpected message in SASL finalization: ${maybe_sasl_continue.type}`); } const sasl_final = utf8.decode(maybe_sasl_final.reader.readAllBytes()); await client.receiveResponse(sasl_final); // Return authentication result return __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_readMessage).call(this); }, _Connection_simpleQuery = async function _Connection_simpleQuery(query) { __classPrivateFieldGet(this, _Connection_packetWriter, "f").clear(); const buffer = __classPrivateFieldGet(this, _Connection_packetWriter, "f").addCString(query.text).flush(0x51); await __classPrivateFieldGet(this, _Connection_bufWriter, "f").write(buffer); await __classPrivateFieldGet(this, _Connection_bufWriter, "f").flush(); let result; if (query.result_type === ResultType.ARRAY) { result = new QueryArrayResult(query); } else { result = new QueryObjectResult(query); } let error; let current_message = await __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_readMessage).call(this); // Process messages until ready signal is sent // Delay error handling until after the ready signal is sent while (current_message.type !== INCOMING_QUERY_MESSAGES.READY) { switch (current_message.type) { case ERROR_MESSAGE: error = new PostgresError(parseNoticeMessage(current_message)); break; case INCOMING_QUERY_MESSAGES.COMMAND_COMPLETE: { result.handleCommandComplete(parseCommandCompleteMessage(current_message)); break; } case INCOMING_QUERY_MESSAGES.DATA_ROW: { const row_data = parseRowDataMessage(current_message); try { result.insertRow(row_data); } catch (e) { error = e; } break; } case INCOMING_QUERY_MESSAGES.EMPTY_QUERY: break; case INCOMING_QUERY_MESSAGES.NOTICE_WARNING: { const notice = parseNoticeMessage(current_message); logNotice(notice); result.warnings.push(notice); break; } case INCOMING_QUERY_MESSAGES.PARAMETER_STATUS: break; case INCOMING_QUERY_MESSAGES.READY: break; case INCOMING_QUERY_MESSAGES.ROW_DESCRIPTION: { result.loadColumnDescriptions(parseRowDescriptionMessage(current_message)); break; } default: throw new Error(`Unexpected simple query message: ${current_message.type}`); } current_message = await __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_readMessage).call(this); } if (error) throw error; return result; }, _Connection_appendQueryToMessage = async function _Connection_appendQueryToMessage(query) { __classPrivateFieldGet(this, _Connection_packetWriter, "f").clear(); const buffer = __classPrivateFieldGet(this, _Connection_packetWriter, "f") .addCString("") // TODO: handle named queries (config.name) .addCString(query.text) .addInt16(0) .flush(0x50); await __classPrivateFieldGet(this, _Connection_bufWriter, "f").write(buffer); }, _Connection_appendArgumentsToMessage = async function _Connection_appendArgumentsToMessage(query) { __classPrivateFieldGet(this, _Connection_packetWriter, "f").clear(); const hasBinaryArgs = query.args.some((arg) => arg instanceof Uint8Array); // bind statement __classPrivateFieldGet(this, _Connection_packetWriter, "f").clear(); __classPrivateFieldGet(this, _Connection_packetWriter, "f") .addCString("") // TODO: unnamed portal .addCString(""); // TODO: unnamed prepared statement if (hasBinaryArgs) { __classPrivateFieldGet(this, _Connection_packetWriter, "f").addInt16(query.args.length); query.args.forEach((arg) => { __classPrivateFieldGet(this, _Connection_packetWriter, "f").addInt16(arg instanceof Uint8Array ? 1 : 0); }); } else { __classPrivateFieldGet(this, _Connection_packetWriter, "f").addInt16(0); } __classPrivateFieldGet(this, _Connection_packetWriter, "f").addInt16(query.args.length); query.args.forEach((arg) => { if (arg === null || typeof arg === "undefined") { __classPrivateFieldGet(this, _Connection_packetWriter, "f").addInt32(-1); } else if (arg instanceof Uint8Array) { __classPrivateFieldGet(this, _Connection_packetWriter, "f").addInt32(arg.length); __classPrivateFieldGet(this, _Connection_packetWriter, "f").add(arg); } else { const byteLength = encoder.encode(arg).length; __classPrivateFieldGet(this, _Connection_packetWriter, "f").addInt32(byteLength); __classPrivateFieldGet(this, _Connection_packetWriter, "f").addString(arg); } }); __classPrivateFieldGet(this, _Connection_packetWriter, "f").addInt16(0); const buffer = __classPrivateFieldGet(this, _Connection_packetWriter, "f").flush(0x42); await __classPrivateFieldGet(this, _Connection_bufWriter, "f").write(buffer); }, _Connection_appendDescribeToMessage = /** * This function appends the query type (in this case prepared statement) * to the message */ async function _Connection_appendDescribeToMessage() { __classPrivateFieldGet(this, _Connection_packetWriter, "f").clear(); const buffer = __classPrivateFieldGet(this, _Connection_packetWriter, "f").addCString("P").flush(0x44); await __classPrivateFieldGet(this, _Connection_bufWriter, "f").write(buffer); }, _Connection_appendExecuteToMessage = async function _Connection_appendExecuteToMessage() { __classPrivateFieldGet(this, _Connection_packetWriter, "f").clear(); const buffer = __classPrivateFieldGet(this, _Connection_packetWriter, "f") .addCString("") // unnamed portal .addInt32(0) .flush(0x45); await __classPrivateFieldGet(this, _Connection_bufWriter, "f").write(buffer); }, _Connection_appendSyncToMessage = async function _Connection_appendSyncToMessage() { __classPrivateFieldGet(this, _Connection_packetWriter, "f").clear(); const buffer = __classPrivateFieldGet(this, _Connection_packetWriter, "f").flush(0x53); await __classPrivateFieldGet(this, _Connection_bufWriter, "f").write(buffer); }, _Connection_processErrorUnsafe = // TODO // Rename process function to a more meaningful name and move out of class async function _Connection_processErrorUnsafe(msg, recoverable = true) { const error = new PostgresError(parseNoticeMessage(msg)); if (recoverable) { let maybe_ready_message = await __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_readMessage).call(this); while (maybe_ready_message.type !== INCOMING_QUERY_MESSAGES.READY) { maybe_ready_message = await __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_readMessage).call(this); } } throw error; }, _Connection_preparedQuery = /** * https://www.postgresql.org/docs/14/protocol-flow.html#PROTOCOL-FLOW-EXT-QUERY */ async function _Connection_preparedQuery(query) { // The parse messages declares the statement, query arguments and the cursor used in the transaction // The database will respond with a parse response await __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_appendQueryToMessage).call(this, query); await __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_appendArgumentsToMessage).call(this, query); // The describe message will specify the query type and the cursor in which the current query will be running // The database will respond with a bind response await __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_appendDescribeToMessage).call(this); // The execute response contains the portal in which the query will be run and how many rows should it return await __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_appendExecuteToMessage).call(this); await __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_appendSyncToMessage).call(this); // send all messages to backend await __classPrivateFieldGet(this, _Connection_bufWriter, "f").flush(); let result; if (query.result_type === ResultType.ARRAY) { result = new QueryArrayResult(query); } else { result = new QueryObjectResult(query); } let error; let current_message = await __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_readMessage).call(this); while (current_message.type !== INCOMING_QUERY_MESSAGES.READY) { switch (current_message.type) { case ERROR_MESSAGE: { error = new PostgresError(parseNoticeMessage(current_message)); break; } case INCOMING_QUERY_MESSAGES.BIND_COMPLETE: break; case INCOMING_QUERY_MESSAGES.COMMAND_COMPLETE: { result.handleCommandComplete(parseCommandCompleteMessage(current_message)); break; } case INCOMING_QUERY_MESSAGES.DATA_ROW: { const row_data = parseRowDataMessage(current_message); try { result.insertRow(row_data); } catch (e) { error = e; } break; } case INCOMING_QUERY_MESSAGES.NO_DATA: break; case INCOMING_QUERY_MESSAGES.NOTICE_WARNING: { const notice = parseNoticeMessage(current_message); logNotice(notice); result.warnings.push(notice); break; } case INCOMING_QUERY_MESSAGES.PARAMETER_STATUS: break; case INCOMING_QUERY_MESSAGES.PARSE_COMPLETE: // TODO: add to already parsed queries if // query has name, so it's not parsed again break; case INCOMING_QUERY_MESSAGES.ROW_DESCRIPTION: { result.loadColumnDescriptions(parseRowDescriptionMessage(current_message)); break; } default: throw new Error(`Unexpected prepared query message: ${current_message.type}`); } current_message = await __classPrivateFieldGet(this, _Connection_instances, "m", _Connection_readMessage).call(this); } if (error) throw error; return result; }; //# sourceMappingURL=connection.js.map