@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.
287 lines • 13.7 kB
JavaScript
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 _QueryClient_instances, _QueryClient_connection, _QueryClient_terminated, _QueryClient_transaction, _QueryClient_assertOpenConnection, _QueryClient_executeQuery, _PoolClient_release;
import { Connection } from "./connection/connection.js";
import { createParams, } from "./connection/connection_params.js";
import { Query, ResultType, templateStringToQuery, } from "./query/query.js";
import { Transaction } from "./query/transaction.js";
import { isTemplateString } from "./utils/utils.js";
export class QueryClient {
constructor(connection) {
_QueryClient_instances.add(this);
_QueryClient_connection.set(this, void 0);
_QueryClient_terminated.set(this, false);
_QueryClient_transaction.set(this, null);
__classPrivateFieldSet(this, _QueryClient_connection, connection, "f");
}
get connected() {
return __classPrivateFieldGet(this, _QueryClient_connection, "f").connected;
}
get session() {
return {
current_transaction: __classPrivateFieldGet(this, _QueryClient_transaction, "f"),
pid: __classPrivateFieldGet(this, _QueryClient_connection, "f").pid,
tls: __classPrivateFieldGet(this, _QueryClient_connection, "f").tls,
transport: __classPrivateFieldGet(this, _QueryClient_connection, "f").transport,
};
}
async closeConnection() {
if (this.connected) {
await __classPrivateFieldGet(this, _QueryClient_connection, "f").end();
}
this.resetSessionMetadata();
}
/**
* Transactions are a powerful feature that guarantees safe operations by allowing you to control
* the outcome of a series of statements and undo, reset, and step back said operations to
* your liking
*
* In order to create a transaction, use the `createTransaction` method in your client as follows:
*
* ```ts
* import { Client } from "./client.ts";
*
* const client = new Client();
* const transaction = client.createTransaction("my_transaction_name");
*
* await transaction.begin();
* // All statements between begin and commit will happen inside the transaction
* await transaction.commit(); // All changes are saved
* ```
*
* All statements that fail in query execution will cause the current transaction to abort and release
* the client without applying any of the changes that took place inside it
*
* ```ts
* import { Client } from "./client.ts";
*
* const client = new Client();
* const transaction = client.createTransaction("transaction");
*
* await transaction.begin();
* await transaction.queryArray`INSERT INTO MY_TABLE (X) VALUES ${"some_value"}`;
* try {
* await transaction.queryArray`SELECT []`; // Invalid syntax, transaction aborted, changes won't be applied
* }catch(e){
* await transaction.commit(); // Will throw, current transaction has already finished
* }
* ```
*
* This however, only happens if the error is of execution in nature, validation errors won't abort
* the transaction
*
* ```ts
* import { Client } from "./client.ts";
*
* const client = new Client();
* const transaction = client.createTransaction("transaction");
*
* await transaction.begin();
* await transaction.queryArray`INSERT INTO MY_TABLE (X) VALUES ${"some_value"}`;
* try {
* await transaction.rollback("unexistent_savepoint"); // Validation error
* } catch(e) {
* await transaction.commit(); // Transaction will end, changes will be saved
* }
* ```
*
* A transaction has many options to ensure modifications made to the database are safe and
* have the expected outcome, which is a hard thing to accomplish in a database with many concurrent users,
* and it does so by allowing you to set local levels of isolation to the transaction you are about to begin
*
* Each transaction can execute with the following levels of isolation:
*
* - Read committed: This is the normal behavior of a transaction. External changes to the database
* will be visible inside the transaction once they are committed.
*
* - Repeatable read: This isolates the transaction in a way that any external changes to the data we are reading
* won't be visible inside the transaction until it has finished
* ```ts
* import { Client } from "./client.ts";
*
* const client = new Client();
* const transaction = await client.createTransaction("my_transaction", { isolation_level: "repeatable_read" });
* ```
*
* - Serializable: This isolation level prevents the current transaction from making persistent changes
* if the data they were reading at the beginning of the transaction has been modified (recommended)
* ```ts
* import { Client } from "./client.ts";
*
* const client = new Client();
* const transaction = await client.createTransaction("my_transaction", { isolation_level: "serializable" });
* ```
*
* Additionally, each transaction allows you to set two levels of access to the data:
*
* - Read write: This is the default mode, it allows you to execute all commands you have access to normally
*
* - Read only: Disables all commands that can make changes to the database. Main use for the read only mode
* is to in conjuction with the repeatable read isolation, ensuring the data you are reading does not change
* during the transaction, specially useful for data extraction
* ```ts
* import { Client } from "./client.ts";
*
* const client = new Client();
* const transaction = await client.createTransaction("my_transaction", { read_only: true });
* ```
*
* Last but not least, transactions allow you to share starting point snapshots between them.
* For example, if you initialized a repeatable read transaction before a particularly sensible change
* in the database, and you would like to start several transactions with that same before the change state
* you can do the following:
*
* ```ts
* import { Client } from "./client.ts";
*
* const client_1 = new Client();
* const client_2 = new Client();
* const transaction_1 = client_1.createTransaction("transaction_1");
*
* const snapshot = await transaction_1.getSnapshot();
* const transaction_2 = client_2.createTransaction("new_transaction", { isolation_level: "repeatable_read", snapshot });
* // transaction_2 now shares the same starting state that transaction_1 had
* ```
*
* https://www.postgresql.org/docs/14/tutorial-transactions.html
* https://www.postgresql.org/docs/14/sql-set-transaction.html
*/
createTransaction(name, options) {
__classPrivateFieldGet(this, _QueryClient_instances, "m", _QueryClient_assertOpenConnection).call(this);
return new Transaction(name, options, this,
// Bind context so function can be passed as is
__classPrivateFieldGet(this, _QueryClient_instances, "m", _QueryClient_executeQuery).bind(this), (name) => {
__classPrivateFieldSet(this, _QueryClient_transaction, name, "f");
});
}
/**
* Every client must initialize their connection previously to the
* execution of any statement
*/
async connect() {
if (!this.connected) {
await __classPrivateFieldGet(this, _QueryClient_connection, "f").startup(false);
__classPrivateFieldSet(this, _QueryClient_terminated, false, "f");
}
}
/**
* Closing your PostgreSQL connection will delete all non-persistent data
* that may have been created in the course of the session and will require
* you to reconnect in order to execute further queries
*/
async end() {
await this.closeConnection();
__classPrivateFieldSet(this, _QueryClient_terminated, true, "f");
}
queryArray(query_template_or_config, ...args) {
__classPrivateFieldGet(this, _QueryClient_instances, "m", _QueryClient_assertOpenConnection).call(this);
if (__classPrivateFieldGet(this, _QueryClient_transaction, "f") !== null) {
throw new Error(`This connection is currently locked by the "${__classPrivateFieldGet(this, _QueryClient_transaction, "f")}" transaction`);
}
let query;
if (typeof query_template_or_config === "string") {
query = new Query(query_template_or_config, ResultType.ARRAY, args[0]);
}
else if (isTemplateString(query_template_or_config)) {
query = templateStringToQuery(query_template_or_config, args, ResultType.ARRAY);
}
else {
query = new Query(query_template_or_config, ResultType.ARRAY);
}
return __classPrivateFieldGet(this, _QueryClient_instances, "m", _QueryClient_executeQuery).call(this, query);
}
queryObject(query_template_or_config, ...args) {
__classPrivateFieldGet(this, _QueryClient_instances, "m", _QueryClient_assertOpenConnection).call(this);
if (__classPrivateFieldGet(this, _QueryClient_transaction, "f") !== null) {
throw new Error(`This connection is currently locked by the "${__classPrivateFieldGet(this, _QueryClient_transaction, "f")}" transaction`);
}
let query;
if (typeof query_template_or_config === "string") {
query = new Query(query_template_or_config, ResultType.OBJECT, args[0]);
}
else if (isTemplateString(query_template_or_config)) {
query = templateStringToQuery(query_template_or_config, args, ResultType.OBJECT);
}
else {
query = new Query(query_template_or_config, ResultType.OBJECT);
}
return __classPrivateFieldGet(this, _QueryClient_instances, "m", _QueryClient_executeQuery).call(this, query);
}
resetSessionMetadata() {
__classPrivateFieldSet(this, _QueryClient_transaction, null, "f");
}
}
_QueryClient_connection = new WeakMap(), _QueryClient_terminated = new WeakMap(), _QueryClient_transaction = new WeakMap(), _QueryClient_instances = new WeakSet(), _QueryClient_assertOpenConnection = function _QueryClient_assertOpenConnection() {
if (__classPrivateFieldGet(this, _QueryClient_terminated, "f")) {
throw new Error("Connection to the database has been terminated");
}
}, _QueryClient_executeQuery = function _QueryClient_executeQuery(query) {
return __classPrivateFieldGet(this, _QueryClient_connection, "f").query(query);
};
/**
* Clients allow you to communicate with your PostgreSQL database and execute SQL
* statements asynchronously
*
* ```ts
* import { Client } from "./client.ts";
*
* const client = new Client();
* await client.connect();
* await client.queryArray`UPDATE MY_TABLE SET MY_FIELD = 0`;
* await client.end();
* ```
*
* A client will execute all their queries in a sequencial fashion,
* for concurrency capabilities check out connection pools
*
* ```ts
* import { Client } from "./client.ts";
*
* const client_1 = new Client();
* await client_1.connect();
* // Even if operations are not awaited, they will be executed in the order they were
* // scheduled
* client_1.queryArray`UPDATE MY_TABLE SET MY_FIELD = 0`;
* client_1.queryArray`DELETE FROM MY_TABLE`;
*
* const client_2 = new Client();
* await client_2.connect();
* // `client_2` will execute it's queries in parallel to `client_1`
* const {rows: result} = await client_2.queryArray`SELECT * FROM MY_TABLE`;
*
* await client_1.end();
* await client_2.end();
* ```
*/
export class Client extends QueryClient {
constructor(config) {
super(new Connection(createParams(config), async () => {
await this.closeConnection();
}));
}
}
export class PoolClient extends QueryClient {
constructor(config, releaseCallback) {
super(new Connection(config, async () => {
await this.closeConnection();
}));
_PoolClient_release.set(this, void 0);
__classPrivateFieldSet(this, _PoolClient_release, releaseCallback, "f");
}
release() {
__classPrivateFieldGet(this, _PoolClient_release, "f").call(this);
// Cleanup all session related metadata
this.resetSessionMetadata();
}
}
_PoolClient_release = new WeakMap();
//# sourceMappingURL=client.js.map