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.

513 lines 24.1 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 _Savepoint_instance_count, _Savepoint_release_callback, _Savepoint_update_callback, _Transaction_instances, _Transaction_client, _Transaction_executeQuery, _Transaction_isolation_level, _Transaction_read_only, _Transaction_savepoints, _Transaction_snapshot, _Transaction_updateClientLock, _Transaction_assertTransactionOpen, _Transaction_resetTransaction; import { Query, ResultType, templateStringToQuery, } from "./query.js"; import { isTemplateString } from "../utils/utils.js"; import { PostgresError, TransactionError } from "../client/error.js"; export class Savepoint { constructor(name, update_callback, release_callback) { Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: name }); /** * This is the count of the current savepoint instances in the transaction */ _Savepoint_instance_count.set(this, 0); _Savepoint_release_callback.set(this, void 0); _Savepoint_update_callback.set(this, void 0); __classPrivateFieldSet(this, _Savepoint_release_callback, release_callback, "f"); __classPrivateFieldSet(this, _Savepoint_update_callback, update_callback, "f"); } get instances() { return __classPrivateFieldGet(this, _Savepoint_instance_count, "f"); } /** * Releasing a savepoint will remove it's last instance in the transaction * * ```ts * import { Client } from "../client.ts"; * * const client = new Client(); * const transaction = client.createTransaction("transaction"); * * const savepoint = await transaction.savepoint("n1"); * await savepoint.release(); * transaction.rollback(savepoint); // Error, can't rollback because the savepoint was released * ``` * * It will also allow you to set the savepoint to the position it had before the last update * * ```ts * import { Client } from "../client.ts"; * * const client = new Client(); * const transaction = client.createTransaction("transaction"); * * const savepoint = await transaction.savepoint("n1"); * await savepoint.update(); * await savepoint.release(); // This drops the update of the last statement * transaction.rollback(savepoint); // Will rollback to the first instance of the savepoint * ``` * * This function will throw if there are no savepoint instances to drop */ async release() { var _a; if (__classPrivateFieldGet(this, _Savepoint_instance_count, "f") === 0) { throw new Error("This savepoint has no instances to release"); } await __classPrivateFieldGet(this, _Savepoint_release_callback, "f").call(this, this.name); __classPrivateFieldSet(this, _Savepoint_instance_count, (_a = __classPrivateFieldGet(this, _Savepoint_instance_count, "f"), --_a), "f"); } /** * Updating a savepoint will update its position in the transaction execution * * ```ts * import { Client } from "../client.ts"; * * const client = new Client(); * const transaction = client.createTransaction("transaction"); * * const my_value = "some value"; * * const savepoint = await transaction.savepoint("n1"); * transaction.queryArray`INSERT INTO MY_TABLE (X) VALUES (${my_value})`; * await savepoint.update(); // Rolling back will now return you to this point on the transaction * ``` * * You can also undo a savepoint update by using the `release` method * * ```ts * import { Client } from "../client.ts"; * * const client = new Client(); * const transaction = client.createTransaction("transaction"); * * const savepoint = await transaction.savepoint("n1"); * transaction.queryArray`DELETE FROM VERY_IMPORTANT_TABLE`; * await savepoint.update(); // Oops, shouldn't have updated the savepoint * await savepoint.release(); // This will undo the last update and return the savepoint to the first instance * await transaction.rollback(); // Will rollback before the table was deleted * ``` */ async update() { var _a; await __classPrivateFieldGet(this, _Savepoint_update_callback, "f").call(this, this.name); __classPrivateFieldSet(this, _Savepoint_instance_count, (_a = __classPrivateFieldGet(this, _Savepoint_instance_count, "f"), ++_a), "f"); } } _Savepoint_instance_count = new WeakMap(), _Savepoint_release_callback = new WeakMap(), _Savepoint_update_callback = new WeakMap(); export class Transaction { constructor(name, options, client, execute_query_callback, update_client_lock_callback) { var _a, _b; _Transaction_instances.add(this); Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: name }); _Transaction_client.set(this, void 0); _Transaction_executeQuery.set(this, void 0); _Transaction_isolation_level.set(this, void 0); _Transaction_read_only.set(this, void 0); _Transaction_savepoints.set(this, []); _Transaction_snapshot.set(this, void 0); _Transaction_updateClientLock.set(this, void 0); __classPrivateFieldSet(this, _Transaction_client, client, "f"); __classPrivateFieldSet(this, _Transaction_executeQuery, execute_query_callback, "f"); __classPrivateFieldSet(this, _Transaction_isolation_level, (_a = options === null || options === void 0 ? void 0 : options.isolation_level) !== null && _a !== void 0 ? _a : "read_committed", "f"); __classPrivateFieldSet(this, _Transaction_read_only, (_b = options === null || options === void 0 ? void 0 : options.read_only) !== null && _b !== void 0 ? _b : false, "f"); __classPrivateFieldSet(this, _Transaction_snapshot, options === null || options === void 0 ? void 0 : options.snapshot, "f"); __classPrivateFieldSet(this, _Transaction_updateClientLock, update_client_lock_callback, "f"); } get isolation_level() { return __classPrivateFieldGet(this, _Transaction_isolation_level, "f"); } get savepoints() { return __classPrivateFieldGet(this, _Transaction_savepoints, "f"); } /** * The begin method will officially begin the transaction, and it must be called before * any query or transaction operation is executed in order to lock the session * ```ts * import { Client } from "../client.ts"; * * const client = new Client(); * const transaction = client.createTransaction("transaction_name"); * * await transaction.begin(); // Session is locked, transaction operations are now safe * // Important operations * await transaction.commit(); // Session is unlocked, external operations can now take place * ``` * https://www.postgresql.org/docs/14/sql-begin.html */ async begin() { if (__classPrivateFieldGet(this, _Transaction_client, "f").session.current_transaction !== null) { if (__classPrivateFieldGet(this, _Transaction_client, "f").session.current_transaction === this.name) { throw new Error("This transaction is already open"); } throw new Error(`This client already has an ongoing transaction "${__classPrivateFieldGet(this, _Transaction_client, "f").session.current_transaction}"`); } let isolation_level; switch (__classPrivateFieldGet(this, _Transaction_isolation_level, "f")) { case "read_committed": { isolation_level = "READ COMMITTED"; break; } case "repeatable_read": { isolation_level = "REPEATABLE READ"; break; } case "serializable": { isolation_level = "SERIALIZABLE"; break; } default: throw new Error(`Unexpected isolation level "${__classPrivateFieldGet(this, _Transaction_isolation_level, "f")}"`); } let permissions; if (__classPrivateFieldGet(this, _Transaction_read_only, "f")) { permissions = "READ ONLY"; } else { permissions = "READ WRITE"; } let snapshot = ""; if (__classPrivateFieldGet(this, _Transaction_snapshot, "f")) { snapshot = `SET TRANSACTION SNAPSHOT '${__classPrivateFieldGet(this, _Transaction_snapshot, "f")}'`; } try { await __classPrivateFieldGet(this, _Transaction_client, "f").queryArray(`BEGIN ${permissions} ISOLATION LEVEL ${isolation_level};${snapshot}`); } catch (e) { if (e instanceof PostgresError) { throw new TransactionError(this.name, e); } else { throw e; } } __classPrivateFieldGet(this, _Transaction_updateClientLock, "f").call(this, this.name); } /** * The commit method will make permanent all changes made to the database in the * current transaction and end the current transaction * * ```ts * import { Client } from "../client.ts"; * * const client = new Client(); * const transaction = client.createTransaction("transaction"); * * await transaction.begin(); * // Important operations * await transaction.commit(); // Will terminate the transaction and save all changes * ``` * * The commit method allows you to specify a "chain" option, that allows you to both commit the current changes and * start a new with the same transaction parameters in a single statement * * ```ts * import { Client } from "../client.ts"; * * const client = new Client(); * const transaction = client.createTransaction("transaction"); * * // Transaction operations I want to commit * await transaction.commit({ chain: true }); // All changes are saved, following statements will be executed inside a transaction * await transaction.queryArray`DELETE SOMETHING FROM SOMEWHERE`; // Still inside the transaction * await transaction.commit(); // The transaction finishes for good * ``` * * https://www.postgresql.org/docs/14/sql-commit.html */ async commit(options) { var _a; __classPrivateFieldGet(this, _Transaction_instances, "m", _Transaction_assertTransactionOpen).call(this); const chain = (_a = options === null || options === void 0 ? void 0 : options.chain) !== null && _a !== void 0 ? _a : false; try { await this.queryArray(`COMMIT ${chain ? "AND CHAIN" : ""}`); } catch (e) { if (e instanceof PostgresError) { throw new TransactionError(this.name, e); } else { throw e; } } __classPrivateFieldGet(this, _Transaction_instances, "m", _Transaction_resetTransaction).call(this); if (!chain) { __classPrivateFieldGet(this, _Transaction_updateClientLock, "f").call(this, null); } } /** * This method will search for the provided savepoint name and return a * reference to the requested savepoint, otherwise it will return undefined */ getSavepoint(name) { return __classPrivateFieldGet(this, _Transaction_savepoints, "f").find((sv) => sv.name === name.toLowerCase()); } /** * This method will list you all of the active savepoints in this transaction */ getSavepoints() { return __classPrivateFieldGet(this, _Transaction_savepoints, "f") .filter(({ instances }) => instances > 0) .map(({ name }) => name); } /** * This method returns the snapshot id of the on going transaction, allowing you to share * the snapshot state between two transactions * * ```ts * import { Client } from "../client.ts"; * * const client_1 = new Client(); * const client_2 = new Client(); * const transaction_1 = client_1.createTransaction("transaction"); * * 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/functions-admin.html#FUNCTIONS-SNAPSHOT-SYNCHRONIZATION */ async getSnapshot() { __classPrivateFieldGet(this, _Transaction_instances, "m", _Transaction_assertTransactionOpen).call(this); const { rows } = await this.queryObject `SELECT PG_EXPORT_SNAPSHOT() AS SNAPSHOT;`; return rows[0].snapshot; } async queryArray(query_template_or_config, ...args) { __classPrivateFieldGet(this, _Transaction_instances, "m", _Transaction_assertTransactionOpen).call(this); let query; if (typeof query_template_or_config === "string") { query = new Query(query_template_or_config, ResultType.ARRAY, args); } 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); } try { return await __classPrivateFieldGet(this, _Transaction_executeQuery, "f").call(this, query); } catch (e) { if (e instanceof PostgresError) { await this.commit(); throw new TransactionError(this.name, e); } else { throw e; } } } async queryObject(query_template_or_config, ...args) { __classPrivateFieldGet(this, _Transaction_instances, "m", _Transaction_assertTransactionOpen).call(this); 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); } try { return await __classPrivateFieldGet(this, _Transaction_executeQuery, "f").call(this, query); } catch (e) { if (e instanceof PostgresError) { await this.commit(); throw new TransactionError(this.name, e); } else { throw e; } } } async rollback(savepoint_or_options) { var _a; __classPrivateFieldGet(this, _Transaction_instances, "m", _Transaction_assertTransactionOpen).call(this); let savepoint_option; if (typeof savepoint_or_options === "string" || savepoint_or_options instanceof Savepoint) { savepoint_option = savepoint_or_options; } else { savepoint_option = savepoint_or_options === null || savepoint_or_options === void 0 ? void 0 : savepoint_or_options.savepoint; } let savepoint_name; if (savepoint_option instanceof Savepoint) { savepoint_name = savepoint_option.name; } else if (typeof savepoint_option === "string") { savepoint_name = savepoint_option.toLowerCase(); } let chain_option = false; if (typeof savepoint_or_options === "object") { chain_option = (_a = savepoint_or_options === null || savepoint_or_options === void 0 ? void 0 : savepoint_or_options.chain) !== null && _a !== void 0 ? _a : false; } if (chain_option && savepoint_name) { throw new Error("The chain option can't be used alongside a savepoint on a rollback operation"); } // If a savepoint is provided, rollback to that savepoint, continue the transaction if (typeof savepoint_option !== "undefined") { const ts_savepoint = __classPrivateFieldGet(this, _Transaction_savepoints, "f").find(({ name }) => name === savepoint_name); if (!ts_savepoint) { throw new Error(`There is no "${savepoint_name}" savepoint registered in this transaction`); } if (!ts_savepoint.instances) { throw new Error(`There are no savepoints of "${savepoint_name}" left to rollback to`); } await this.queryArray(`ROLLBACK TO ${savepoint_name}`); return; } // If no savepoint is provided, rollback the whole transaction and check for the chain operator // in order to decide whether to restart the transaction or end it try { await this.queryArray(`ROLLBACK ${chain_option ? "AND CHAIN" : ""}`); } catch (e) { if (e instanceof PostgresError) { await this.commit(); throw new TransactionError(this.name, e); } else { throw e; } } __classPrivateFieldGet(this, _Transaction_instances, "m", _Transaction_resetTransaction).call(this); if (!chain_option) { __classPrivateFieldGet(this, _Transaction_updateClientLock, "f").call(this, null); } } /** * This method will generate a savepoint, which will allow you to reset transaction states * to a previous point of time * * Each savepoint has a unique name used to identify it, and it must abide the following rules * * - Savepoint names must start with a letter or an underscore * - Savepoint names are case insensitive * - Savepoint names can't be longer than 63 characters * - Savepoint names can only have alphanumeric characters * * A savepoint can be easily created like this * ```ts * import { Client } from "../client.ts"; * * const client = new Client(); * const transaction = client.createTransaction("transaction"); * * const savepoint = await transaction.savepoint("MY_savepoint"); // returns a `Savepoint` with name "my_savepoint" * await transaction.rollback(savepoint); * await savepoint.release(); // The savepoint will be removed * ``` * All savepoints can have multiple positions in a transaction, and you can change or update * this positions by using the `update` and `release` methods * ```ts * import { Client } from "../client.ts"; * * const client = new Client(); * const transaction = client.createTransaction("transaction"); * * const savepoint = await transaction.savepoint("n1"); * await transaction.queryArray`INSERT INTO MY_TABLE VALUES (${'A'}, ${2})`; * await savepoint.update(); // The savepoint will continue from here * await transaction.queryArray`DELETE FROM MY_TABLE`; * await transaction.rollback(savepoint); // The transaction will rollback before the delete, but after the insert * await savepoint.release(); // The last savepoint will be removed, the original one will remain * await transaction.rollback(savepoint); // It rolls back before the insert * await savepoint.release(); // All savepoints are released * ``` * * Creating a new savepoint with an already used name will return you a reference to * the original savepoint * ```ts * import { Client } from "../client.ts"; * * const client = new Client(); * const transaction = client.createTransaction("transaction"); * * const savepoint_a = await transaction.savepoint("a"); * await transaction.queryArray`DELETE FROM MY_TABLE`; * const savepoint_b = await transaction.savepoint("a"); // They will be the same savepoint, but the savepoint will be updated to this position * await transaction.rollback(savepoint_a); // Rolls back to savepoint_b * ``` * https://www.postgresql.org/docs/14/sql-savepoint.html */ async savepoint(name) { __classPrivateFieldGet(this, _Transaction_instances, "m", _Transaction_assertTransactionOpen).call(this); if (!/^[a-zA-Z_]{1}[\w]{0,62}$/.test(name)) { if (!Number.isNaN(Number(name[0]))) { throw new Error("The savepoint name can't begin with a number"); } if (name.length > 63) { throw new Error("The savepoint name can't be longer than 63 characters"); } throw new Error("The savepoint name can only contain alphanumeric characters"); } name = name.toLowerCase(); let savepoint = __classPrivateFieldGet(this, _Transaction_savepoints, "f").find((sv) => sv.name === name); if (savepoint) { try { await savepoint.update(); } catch (e) { if (e instanceof PostgresError) { await this.commit(); throw new TransactionError(this.name, e); } else { throw e; } } } else { savepoint = new Savepoint(name, async (name) => { await this.queryArray(`SAVEPOINT ${name}`); }, async (name) => { await this.queryArray(`RELEASE SAVEPOINT ${name}`); }); try { await savepoint.update(); } catch (e) { if (e instanceof PostgresError) { await this.commit(); throw new TransactionError(this.name, e); } else { throw e; } } __classPrivateFieldGet(this, _Transaction_savepoints, "f").push(savepoint); } return savepoint; } } _Transaction_client = new WeakMap(), _Transaction_executeQuery = new WeakMap(), _Transaction_isolation_level = new WeakMap(), _Transaction_read_only = new WeakMap(), _Transaction_savepoints = new WeakMap(), _Transaction_snapshot = new WeakMap(), _Transaction_updateClientLock = new WeakMap(), _Transaction_instances = new WeakSet(), _Transaction_assertTransactionOpen = function _Transaction_assertTransactionOpen() { if (__classPrivateFieldGet(this, _Transaction_client, "f").session.current_transaction !== this.name) { throw new Error(`This transaction has not been started yet, make sure to use the "begin" method to do so`); } }, _Transaction_resetTransaction = function _Transaction_resetTransaction() { __classPrivateFieldSet(this, _Transaction_savepoints, [], "f"); }; //# sourceMappingURL=transaction.js.map