src/main/generic/SynchronousTransaction.js
/**
* Synchronous transactions avoid unnecessary async/await calls by preloading and caching
* all necessary key-value-pairs.
*
* WARNING: If not all required key-value-pairs are preloaded, the results of any call on a synchronous transaction
* might be wrong. Only use synchronous transactions, if unavoidable.
* @implements {ISynchronousObjectStore}
* @implements {ICommittable}
* @extends {Transaction}
*/
class SynchronousTransaction extends Transaction {
/**
* This constructor should only be called by an ObjectStore object.
* Our transactions have a watchdog enabled by default,
* logging a warning after a certain time specified by WATCHDOG_TIMER.
* This helps to detect unclosed transactions preventing to store the state in
* the persistent backend.
* @param {ObjectStore} objectStore The object store this transaction belongs to.
* @param {IObjectStore} parent The backend on which the transaction is based,
* i.e., another transaction or the real database.
* @param {ICommittable} [managingBackend] The object store managing the transactions,
* i.e., the ObjectStore object.
* @param {boolean} [enableWatchdog] If this is is set to true (default),
* a warning will be logged if left open for longer than WATCHDOG_TIMER.
* @protected
*/
constructor(objectStore, parent, managingBackend, enableWatchdog=true) {
super(objectStore, parent, managingBackend, enableWatchdog);
/** @type {Map.<string,*>} */
this._cache = new Map();
}
/**
* This method preloads a set of keys and caches them.
* It can be called as often as needed.
* @param {Array.<string>} keys The keys to preload.
* @return {Promise}
*/
preload(keys) {
keys = keys.filter(key => !this.isCached(key));
return Promise.all(keys.map(key => this.get(key)));
}
/**
* A check whether a certain key is cached.
* @param {string} key The key to check.
* @return {boolean} A boolean indicating whether the key is already in the cache.
*/
isCached(key) {
// This also prevents double caching.
return this._cache.has(key) || (this._parent.isSynchronous() ? this._parent.isCached(key) : false);
}
/**
* @param {string} key
* @param {RetrievalConfig} [options] Advanced retrieval options.
*/
async get(key, options = {}) {
options.expectPresence = false;
// Use cache or ask parent.
let value;
if (this.isCached(key)) {
value = this.getSync(key, options);
} else {
value = await Transaction.prototype.get.call(this, key, options);
if (options && options.raw) {
this._cache.set(key, this.decode(value, key));
} else {
this._cache.set(key, value);
}
}
return value;
}
/**
* Internal method to query cache.
* @param {string} key
* @param {SyncRetrievalConfig} [options] Advanced retrieval options.
* @return {*} The cached value.
* @private
*/
_getCached(key, options = {}) {
const { expectPresence = true } = options || {};
let value = this._cache.get(key);
// Use cache only if the parent is not synchronous.
if (!value && this._parent.isSynchronous()) {
return this._parent.getSync(key, options);
}
if (expectPresence && !value) {
throw new Error(`Missing key in cache: ${key}`);
}
// Raw requests
if (options && options.raw) {
value = this.encode(value);
}
return value;
}
/**
* Returns the object stored under the given primary key.
* Resolves to undefined if the key is not present in the object store.
* @param {string} key The primary key to look for.
* @param {SyncRetrievalConfig} [options] Advanced retrieval options.
* @returns {*} The object stored under the given key, or undefined if not present.
*/
getSync(key, options = {}) {
// Order is as follows:
// 1. check if removed,
// 2. check if modified,
// 3. check if truncated
// 4. request from backend
if (this._removed.has(key)) {
return undefined;
}
if (this._modified.has(key)) {
if (options && options.raw) {
return this.encode(this._modified.get(key));
}
return this._modified.get(key);
}
if (this._truncated) {
return undefined;
}
return this._getCached(key, options);
}
/**
* Checks whether an object store implements the ISynchronousObjectStore interface.
* @override
* @returns {boolean} The transaction object.
*/
isSynchronous() {
return true;
}
}
Class.register(SynchronousTransaction);