@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
JavaScript
/*!
* 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