@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
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 _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