Home Reference Source Test

src/main/generic/Snapshot.js

/**
 * Snapshots present a read-only version of a specific state.
 * As long as a snapshot is not aborted, the object store will reflect changes to the state
 * in form of the differences to the originating state in the snapshot.
 * This makes efficient queries against a fixed state possible without blocking other transactions
 * to commit.
 * @extends {Transaction}
 */
class Snapshot extends Transaction {
    /**
     * This constructor should only be called by an ObjectStore object.
     * @param {ObjectStore} objectStore The object store this transaction belongs to.
     * @param {IObjectStore} backend The backend this transaction is based on.
     * @protected
     */
    constructor(objectStore, backend) {
        super(objectStore, backend, objectStore, false);
    }

    /**
     * A specific set of changes can be assumed to be already applied by providing a Transaction or Snapshot.
     * These differences will be inherited while the backend of the snapshot remains the current state.
     * This is useful, if we have a transaction/snapshot to a previous state, which we do not want to commit.
     * Then, we can still base our snapshot on this earlier state although the current backend is already ahead.
     * @param {Transaction} tx A transaction or snapshot containing changes that have already been applied.
     * @protected
     */
    inherit(tx) {
        if (!(tx instanceof Transaction)) {
            throw new Error('Can only inherit transactions');
        }

        return super._applySync(tx);
    }

    /**
     * Internally applies a transaction to the snapshot state.
     * In contrast to transactions, this tries to reflect the old state in the snapshot.
     * @param {Transaction} tx The transaction to apply.
     * @returns {Promise} The promise resolves after applying the transaction.
     * @protected
     */
    async _apply(tx) {
        if (!(tx instanceof Transaction)) {
            throw new Error('Can only apply transactions');
        }
        if (tx._truncated) {
            // Need to copy complete old state.
            await this.valueStream((value, key) => {
                if (!this._modified.has(key)) {
                    this._put(key, value);
                }
                return true;
            });
        }
        for (const [key, value] of tx._modified) {
            // Continue if we already have the old value for this key.
            if (this._modified.has(key)) {
                continue;
            }
            let oldValue = await this.get(key);
            // If this key is newly introduced,
            // we have to mark it as removed to maintain our state.
            if (!oldValue) {
                this._remove(key, value);
            } else {
                // Otherwise store oldValue.
                this._put(key, oldValue, value);
            }
        }
        for (const key of tx._removed) {
            // Continue if we already have the old value for this key.
            if (this._modified.has(key)) {
                continue;
            }
            // Removed values have to be remembered.
            let oldValue = await this.get(key);
            this._put(key, oldValue);
        }
    }

    /**
     * Unsupported operation for snapshots.
     * @returns {Promise}
     * @override
     */
    async truncate() {
        throw new Error('Unsupported operation on snapshots: truncate');
    }

    /**
     * Unsupported operation for snapshots.
     * @override
     */
    truncateSync() {
        throw new Error('Unsupported operation on snapshots: truncateSync');
    }

    /**
     * Unsupported operation for snapshots.
     * @override
     * @throws
     */
    async commit(tx) {
        throw new Error('Cannot commit snapshots: commit');
    }

    /**
     * Unsupported operation for snapshots.
     * @override
     * @protected
     * @param {Transaction} [tx] The transaction to be applied, if not given checks for the this transaction.
     * @returns {boolean} Whether a commit will be successful.
     */
    _isCommittable(tx) {
        return false;
    }

    /**
     * Unsupported operation for snapshots.
     * @override
     * @protected
     * @param {Transaction} tx The transaction to be applied.
     * @returns {Promise} A promise that resolves upon successful application of the transaction.
     */
    async _commitInternal(tx) {
        throw new Error('Cannot commit snapshots');
    }

    /**
     * Commits the transaction to the backend.
     * @override
     * @returns {Promise.<boolean>} A promise of the success outcome.
     * @protected
     */
    async _commitBackend() {
        throw new Error('Cannot commit snapshots');
    }

    /**
     * Aborts a snapshot and stops updating its diff.
     * @override
     * @param [tx]
     * @returns {Promise.<boolean>} A promise of the success outcome.
     */
    abort(tx) {
        return this._abortBackend();
    }

    /**
     * Aborts a transaction on the backend.
     * @returns {Promise.<boolean>} A promise of the success outcome.
     * @override
     */
    async _abortBackend() {
        if (this._state !== Transaction.STATE.OPEN) {
            throw new Error('Snapshot already closed');
        }
        const result = await this._managingBackend.abort(this);
        if (!result) {
            return false;
        }

        this._state = Transaction.STATE.ABORTED;

        // Cleanup.
        this._truncated = true;
        this._modified.clear();
        this._removed.clear();

        // Update indices.
        for (const index of this._indices.values()) {
            index.truncate();
        }

        return true;
    }

    /**
     * Unsupported operation for snapshots.
     * @override
     * @returns {Promise}
     */
    async put(key, value) {
        throw new Error('Unsupported operation on snapshots: put');
    }

    /**
     * Unsupported operation for snapshots.
     * @override
     */
    putSync(key, value) {
        throw new Error('Unsupported operation on snapshots: putSync');
    }

    /**
     * Unsupported operation for snapshots.
     * @override
     * @returns {Promise}
     */
    async remove(key) {
        throw new Error('Unsupported operation on snapshots: remove');
    }

    /**
     * Unsupported operation for snapshots.
     * @override
     */
    removeSync(key) {
        throw new Error('Unsupported operation on snapshots: removeSync');
    }

    /**
     * Alias for abort.
     * @returns {Promise} The promise resolves after successful abortion of the transaction.
     */
    close() {
        return this.abort();
    }

    /**
     * Unsupported operation for snapshots.
     * @override
     */
    transaction() {
        throw new Error('Unsupported operation on snapshots: transaction');
    }

    /**
     * Unsupported operation for snapshots.
     * @override
     */
    synchronousTransaction() {
        throw new Error('Unsupported operation on snapshots: synchronousTransaction');
    }

    /**
     * Unsupported operation for snapshots.
     * @override
     */
    snapshot() {
        throw new Error('Unsupported operation on snapshots: snapshot');
    }
}
Class.register(Snapshot);