UNPKG

@hirosystems/api-toolkit

Version:
92 lines 3.78 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BasePgStoreModule = exports.BasePgStore = exports.sqlTransactionContext = void 0; const async_hooks_1 = require("async_hooks"); const values_1 = require("../helpers/values"); /** * AsyncLocalStorage used to determine if the current async context is running inside a SQL * transaction. */ exports.sqlTransactionContext = new async_hooks_1.AsyncLocalStorage(); /** * Base class that provides access to a SQL client and SQL transaction management. */ class BasePgStore { /** * Getter for a SQL client. If used inside `sqlTransaction`, the scoped client within the current * async context will be returned to guarantee transaction consistency. */ get sql() { const dbName = this._sql.options.database?.toString() ?? 'default'; const sqlContext = exports.sqlTransactionContext.getStore(); return sqlContext ? sqlContext[dbName] : this._sql; } _sql; constructor(sql) { this._sql = sql; } async close(args) { await this._sql.end({ timeout: args?.timeout }); } /** * Start a SQL transaction. If any SQL client used within the callback was already scoped inside a * `BEGIN` transaction, no new transaction will be opened. This flexibility allows us to avoid * repeating code while making sure we don't arrive at SQL errors such as * `WARNING: there is already a transaction in progress` which may cause result inconsistencies. * @param callback - Callback with a scoped SQL client * @param readOnly - If a `BEGIN` transaction should be marked as `READ ONLY` * @returns Transaction results */ async sqlTransaction(callback, readOnly = true) { // Do we have a scoped client already? Use it directly. Key is the database name. const dbName = this._sql.options.database?.toString() ?? 'default'; const sql = exports.sqlTransactionContext.getStore()?.[dbName]; if (sql) { return callback(sql); } // Otherwise, start a transaction and store the scoped connection in the current async context. return this._sql.begin(readOnly ? 'read only' : 'read write', sql => { const currentStore = exports.sqlTransactionContext.getStore() ?? {}; return exports.sqlTransactionContext.run({ ...currentStore, [dbName]: sql }, () => callback(sql)); }); } /** * Start a SQL write transaction. See `sqlTransaction`. * @param callback - Callback with a scoped SQL client * @returns Transaction results */ async sqlWriteTransaction(callback) { return this.sqlTransaction(callback, false); } /** * Refreshes a materialized view concurrently depending on the current environment. * @param viewName - Materialized view name */ async refreshMaterializedView(viewName) { await this.sql `REFRESH MATERIALIZED VIEW ${values_1.isProdEnv ? this.sql `CONCURRENTLY` : this.sql ``} ${this.sql(viewName)}`; } } exports.BasePgStore = BasePgStore; /** * Base module that extends PgStore functionality and allows organizing queries in separate files. */ class BasePgStoreModule { parent; constructor(db) { this.parent = db; } get sql() { return this.parent.sql; } async sqlTransaction(callback, readOnly = true) { return this.parent.sqlTransaction(callback, readOnly); } async sqlWriteTransaction(callback) { return this.sqlTransaction(callback, false); } async refreshMaterializedView(viewName) { return this.parent.refreshMaterializedView(viewName); } } exports.BasePgStoreModule = BasePgStoreModule; //# sourceMappingURL=base-pg-store.js.map