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.

287 lines 13.7 kB
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