@hirosystems/api-toolkit
Version:
API development toolkit
92 lines • 3.78 kB
JavaScript
;
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