daemonraco/dfdb

View on GitHub
src/includes/collection/index.sl.dfdb.ts

Summary

Maintainability
A
0 mins
Test Coverage
/**
 * @file index.sl.dfdb.ts
 * @author Alejandro D. Simi
 */

import { Promise } from 'es6-promise';

import { BasicDictionary, DBDocument, DBDocumentID } from '../basic-types.dfdb';
import { Collection } from './collection.dfdb';
import { Index } from '../index.dfdb';
import { IOpenCollectionIndex } from './open-collection.i.dfdb';
import { Rejection } from '../rejection.dfdb';
import { RejectionCodes } from '../rejection-codes.dfdb';
import { SubLogic } from '../sub-logic.dfdb';
import { Tools, IPromiseStep } from '../tools.dfdb';

/**
 * This class holds Collection's logic related to its indexes.
 *
 * @class SubLogicIndex
 */
export class SubLogicIndex extends SubLogic<IOpenCollectionIndex> {
    //
    // Public methods.
    /**
     * This method adds certain document to all field indexes.
     *
     * @method addDocToIndexes
     * @param {DBDocument} doc Document to be added.
     * @returns {Promise<void>} Return a promise that gets resolved when the
     * operation finishes.
     */
    public addDocToIndexes(doc: DBDocument): Promise<void> {
        //
        // Building promise to return.
        return new Promise<void>((resolve: () => void, reject: (err: Rejection) => void) => {
            //
            // List of operations.
            let steps: any[] = [];
            //
            // Generating a step for each field index.
            Object.keys(this._mainObject._indexes).forEach(name => {
                steps.push({
                    params: { doc, name },
                    stepFunction: (params: any) => this.addDocToIndex(params)
                });
            });
            //
            // Indexing.
            Tools.ProcessPromiseSteps(steps)
                .then(resolve)
                .catch(reject);
        });
    }
    /**
     * This method associates a new index to a root document field and trigger
     * it's first indexation.
     *
     * @method addFieldIndex
     * @param {name} name Field to index. It also acts as index name.
     * @returns {Promise<void>} Return a promise that gets resolved when the
     * operation finishes.
     */
    public addFieldIndex(name: string): Promise<void> {
        //
        // Restarting error messages.
        this._mainObject._subLogicErrors.resetError();
        //
        // Building promise to return.
        return new Promise<void>((resolve: () => void, reject: (err: Rejection) => void) => {
            //
            // Is it connected and is it a new index?
            if (this._mainObject._connected && typeof this._mainObject._indexes[name] === 'undefined') {
                //
                // Adding index to the internal manifest.
                this._mainObject._manifest.indexes[name] = { name, field: name };
                //
                // Reloading all indexes into memory to include the new one.
                this.loadIndexes(null)
                    .then(() => {
                        // Adding all documents.
                        //
                        // List of ids to analyse.
                        let ids = Object.keys(this._mainObject._data);
                        //
                        // Recursive add-and-wait of all documents to the new
                        // index.
                        const processIds = () => {
                            const id: DBDocumentID = ids.shift();
                            if (id) {
                                //
                                // Skiping physical save, that will be done when
                                // all documents are added.
                                this._mainObject._indexes[name].skipSave();
                                this._mainObject._indexes[name].addDocument(this._mainObject._data[id])
                                    .then(processIds)
                                    .catch(reject);
                            } else {
                                //
                                // Saving all changes to this collection and also
                                // saving all changes made on the new index.
                                this._mainObject.save()
                                    .then(resolve)
                                    .catch(reject);
                            }
                        };
                        processIds();
                    })
                    .catch(reject);
            } else if (!this._mainObject._connected) {
                this._mainObject._subLogicErrors.setLastRejection(new Rejection(RejectionCodes.CollectionNotConnected));
                reject(this._mainObject._subLogicErrors.lastRejection());
            } else {
                this._mainObject._subLogicErrors.setLastRejection(new Rejection(RejectionCodes.DuplicatedIndex, { index: name }));
                reject(this._mainObject._subLogicErrors.lastRejection());
            }
        });
    }
    /**
     * This method closes all field indexes.
     *
     * @method closeIndexes
     * @param {any} params This parameter is provided for compatibility, but it's
     * not used.
     * @returns {Promise<void>} Return a promise that gets resolved when the
     * operation finishes.
     */
    public closeIndexes(params: any): Promise<void> {
        //
        // Building promise to return.
        return new Promise<void>((resolve: () => void, reject: (err: Rejection) => void) => {
            //
            // List of operations.
            let steps: any[] = [];
            //
            // Generating a step for each field index.
            Object.keys(this._mainObject._indexes).forEach(name => {
                steps.push({
                    params: { name },
                    stepFunction: (params: any) => this.closeIndex(params)
                });
            });
            //
            // Closing.
            Tools.ProcessPromiseSteps(steps)
                .then(resolve)
                .catch(reject);
        });
    }
    /**
     * Removes a field associated index and triggers the removal of its physical
     * data inside the zip file.
     *
     * @method dropFieldIndex
     * @returns {Promise<void>} Return a promise that gets resolved when the
     * operation finishes.
     */
    public dropFieldIndex(name: string): Promise<void> {
        //
        // Restarting error messages.
        this._mainObject._subLogicErrors.resetError();
        //
        // Building promise to return.
        return new Promise<void>((resolve: () => void, reject: (err: Rejection) => void) => {
            //
            // Is it connected and does it have the requested index?
            if (this._mainObject._connected && typeof this._mainObject._indexes[name] !== 'undefined') {
                //
                // Ask index to get dropped.
                this._mainObject._indexes[name].drop()
                    .then(() => {
                        //
                        // Updates the information inside the zip file.
                        this._mainObject.save()
                            .then(() => {
                                //
                                // Forgets everything about this index.
                                delete this._mainObject._manifest.indexes[name];
                                delete this._mainObject._indexes[name];

                                resolve();
                            })
                            .catch(reject);
                    })
                    .catch(reject);
            } else {
                //
                // If it's not connected or it's not a known index, nothing is
                // done.
                resolve();
            }
        });
    }
    /**
     * This method drops all field indexes.
     *
     * @method dropIndexes
     * @param {any} params This parameter is provided for compatibility, but it's
     * not used.
     * @returns {Promise<void>} Return a promise that gets resolved when the
     * operation finishes.
     */
    public dropIndexes(params: any): Promise<void> {
        //
        // Building promise to return.
        return new Promise<void>((resolve: () => void, reject: (err: Rejection) => void) => {
            //
            // List of operations.
            let steps: any[] = [];
            //
            // Generating a step for each field index.
            Object.keys(this._mainObject._indexes).forEach(name => {
                steps.push({
                    params: { name },
                    stepFunction: (params: any) => this.dropIndex(params)
                });
            });
            //
            // Dropping.
            Tools.ProcessPromiseSteps(steps)
                .then(resolve)
                .catch(reject);
        });
    }
    /**
     * Checks if this collection has a specific index.
     *
     * @method hasIndex
     * @param {string} name Index name to search.
     * @returns {boolean} Returns TRUE when it's a known index.
     */
    public hasIndex(name: string): boolean {
        return typeof this._mainObject._manifest.indexes[name] !== 'undefined';
    }
    /**
     * List all indexes of this collection
     *
     * @method indexes
     * @returns {BasicDictionary} Retruns a simple object listing indexes.
     */
    public indexes(): BasicDictionary {
        return Tools.DeepCopy(this._mainObject._manifest.indexes);
    }
    /**
     * This method loads all associated field indexes.
     *
     * @protected
     * @method loadIndexes
     * @param {any} params This parameter is provided for compatibility, but it's
     * not used.
     * @returns {Promise<void>} Return a promise that gets resolved when the
     * operation finishes.
     */
    public loadIndexes(params: any): Promise<void> {
        //
        // Building promise to return.
        return new Promise<void>((resolve: () => void, reject: (err: Rejection) => void) => {
            //
            // List of operations.
            let steps: any[] = [];
            //
            // Generating a step for each field index.
            Object.keys(this._mainObject._manifest.indexes).forEach(key => {
                steps.push({
                    params: this._mainObject._manifest.indexes[key],
                    stepFunction: (params: any) => this.loadIndex(params)
                });
            })
            //
            // Loading.
            Tools.ProcessPromiseSteps(steps)
                .then(resolve)
                .catch(reject);
        });
    }
    /**
     * This method removes a document from a specific index.
     *
     * @method rebuildAllIndexes
     * @param {any} params This parameter is provided for compatibility, but it's
     * not used.
     * @returns {Promise<void>} Return a promise that gets resolved when the
     * operation finishes.
     */
    public rebuildAllIndexes(params: any): Promise<void> {
        //
        // Building promise to return.
        return new Promise<void>((resolve: () => void, reject: (err: Rejection) => void) => {
            //
            // List of operations.
            let steps: any[] = [];
            //
            // Generating a step for each field index.
            Object.keys(this._mainObject._indexes).forEach(name => {
                steps.push({
                    params: name,
                    stepFunction: (params: any) => this.rebuildFieldIndex(params)
                });
            });
            //
            // Closing.
            Tools.ProcessPromiseSteps(steps)
                .then(resolve)
                .catch(reject);
        });
    }
    /**
     * This method forces a index to reload and reindex all documents.
     *
     * @method rebuildFieldIndex
     * @param {string} name Name of the field index to rebuild.
     * @returns {Promise<void>} Return a promise that gets resolved when the
     * operation finishes.
     */
    public rebuildFieldIndex(name: string): Promise<void> {
        //
        // Restarting error messages.
        this._mainObject._subLogicErrors.resetError();
        //
        // Building promise to return.
        return new Promise<void>((resolve: () => void, reject: (err: Rejection) => void) => {
            //
            // Is it connected and is it a known field index.
            if (this._mainObject._connected && typeof this._mainObject._indexes[name] !== 'undefined') {
                //
                // Dropping index as a way to drop any error.
                this.dropFieldIndex(name)
                    .then(() => {
                        //
                        // Readding index so it starts from scratch.
                        this.addFieldIndex(name)
                            .then(resolve)
                            .catch(reject);
                    })
                    .catch(reject);
            } else if (!this._mainObject._connected) {
                this._mainObject._subLogicErrors.setLastRejection(new Rejection(RejectionCodes.CollectionNotConnected));
                reject(this._mainObject._subLogicErrors.lastRejection());
            } else {
                this._mainObject._subLogicErrors.setLastRejection(new Rejection(RejectionCodes.UnknownIndex, { index: name }));
                reject(this._mainObject._subLogicErrors.lastRejection());
            }
        });
    }
    /**
     * This method a document from all field indexes.
     *
     * @method removeDocFromIndexes
     * @param {DBDocumentID} id ID of the document to be removed.
     * @returns {Promise<void>} Return a promise that gets resolved when the
     * operation finishes.
     */
    public removeDocFromIndexes(id: DBDocumentID): Promise<void> {
        //
        // Building promise to return.
        return new Promise<void>((resolve: () => void, reject: (err: Rejection) => void) => {
            //
            // List of operations.
            let steps: any[] = [];
            //
            // Generating a step for each field index.
            Object.keys(this._mainObject._indexes).forEach(name => {
                steps.push({
                    params: { id, name },
                    stepFunction: (params: any) => this.removeDocFromIndex(params)
                });
            })
            //
            // Removing document.
            Tools.ProcessPromiseSteps(steps)
                .then(resolve)
                .catch(reject);
        });
    }
    /**
     * This method truncates all field indexes.
     *
     * @method truncateIndexes
     * @param {any} params This parameter is provided for compatibility, but it's
     * not used.
     * @returns {Promise<void>} Return a promise that gets resolved when the
     * operation finishes.
     */
    public truncateIndexes(params: any): Promise<void> {
        //
        // Building promise to return.
        return new Promise<void>((resolve: () => void, reject: (err: Rejection) => void) => {
            //
            // List of operations.
            let steps: any[] = [];
            //
            // Generating a step for each field index.
            Object.keys(this._mainObject._indexes).forEach(name => {
                steps.push({
                    params: { name },
                    stepFunction: (params: any) => this.truncateIndex(params)
                });
            })
            //
            // Truncating.
            Tools.ProcessPromiseSteps(steps)
                .then(resolve)
                .catch(reject);
        });
    }
    //
    // Protected methods.
    /**
     * This method adds a document to a specific index.
     *
     * @protected
     * @method addDocToIndex
     * @param {BasicDictionary} params List of required parameters to perform this
     * operation ('name', 'doc').
     * @returns {Promise<void>} Return a promise that gets resolved when the
     * operation finishes.
     */
    protected addDocToIndex(params: BasicDictionary): Promise<void> {
        //
        // Building promise to return.
        return new Promise<void>((resolve: () => void, reject: (err: Rejection) => void) => {
            //
            // Skipping physical save, that will be dealt with later.
            this._mainObject._indexes[params.name].skipSave();
            //
            // Adding document.
            this._mainObject._indexes[params.name].addDocument(params.doc)
                .then(resolve)
                .catch(reject);
        });
    }
    /**
     * This closes a specific index.
     *
     * @protected
     * @method closeIndex
     * @param {BasicDictionary} params List of required parameters to perform this
     * operation ('name').
     * @returns {Promise<void>} Return a promise that gets resolved when the
     * operation finishes.
     */
    protected closeIndex(params: BasicDictionary): Promise<void> {
        //
        // Building promise to return.
        return new Promise<void>((resolve: () => void, reject: (err: Rejection) => void) => {
            //
            // Skipping physical save, that will be dealt with later.
            this._mainObject._indexes[params.name].skipSave();
            //
            // Closing index.
            this._mainObject._indexes[params.name].close()
                .then(() => {
                    //
                    // Forgetting index object so, in any case, it's reloaded.
                    delete this._mainObject._indexes[params.name];
                    resolve();
                })
                .catch(reject);
        });
    }
    /**
     * This method drops a specific index.
     *
     * @protected
     * @method dropIndex
     * @param {BasicDictionary} params List of required parameters to perform this
     * operation ('name').
     * @returns {Promise<void>} Return a promise that gets resolved when the
     * operation finishes.
     */
    protected dropIndex(params: BasicDictionary): Promise<void> {
        //
        // Building promise to return.
        return new Promise<void>((resolve: () => void, reject: (err: Rejection) => void) => {
            //
            // Forgetting index.
            delete this._mainObject._manifest.indexes[params.name];
            //
            // Asking index to get dropped.
            this._mainObject._indexes[params.name].drop()
                .then(() => {
                    //
                    // Forgetting index object.
                    delete this._mainObject._indexes[params.name];
                    resolve();
                })
                .catch(reject);
        });
    }
    /**
     * This closes a specific index.
     *
     * @protected
     * @method loadIndex
     * @param {BasicDictionary} params List of required parameters to perform this
     * operation.
     * @returns {Promise<void>} Return a promise that gets resolved when the
     * operation finishes.
     */
    protected loadIndex(params: BasicDictionary): Promise<void> {
        //
        // Building promise to return.
        return new Promise<void>((resolve: () => void, reject: (err: Rejection) => void) => {
            if (typeof this._mainObject._indexes[params.name] === 'undefined') {
                this._mainObject._indexes[params.name] = new Index((<any>this._mainObject), params.field, this._mainObject._connection);
                this._mainObject._indexes[params.name].connect()
                    .then(resolve)
                    .catch(reject);
            } else {
                resolve();
            }
        });
    }
    /**
     * This method removes a document from a specific index.
     *
     * @protected
     * @method removeDocFromIndex
     * @param {BasicDictionary} params List of required parameters to perform this
     * operation ('id', 'name').
     * @returns {Promise<void>} Return a promise that gets resolved when the
     * operation finishes.
     */
    protected removeDocFromIndex(params: BasicDictionary): Promise<void> {
        //
        // Building promise to return.
        return new Promise<void>((resolve: () => void, reject: (err: Rejection) => void) => {
            //
            // Skipping physical save, that will be dealt with later.
            this._mainObject._indexes[params.name].skipSave();
            //
            // Removing document based on its ID.
            this._mainObject._indexes[params.name].removeDocument(params.id)
                .then(resolve)
                .catch(reject);
        });
    }
    /**
     * This method truncates a specific index.
     *
     * @protected
     * @method truncateIndex
     * @param {BasicDictionary} params List of required parameters to perform this
     * operation ('name').
     * @returns {Promise<void>} Return a promise that gets resolved when the
     * operation finishes.
     */
    protected truncateIndex(params: BasicDictionary): Promise<void> {
        //
        // Building promise to return.
        return new Promise<void>((resolve: () => void, reject: (err: Rejection) => void) => {
            //
            // Skipping physical save, that will be dealt with later.
            this._mainObject._indexes[params.name].skipSave();
            this._mainObject._indexes[params.name].truncate()
                .then(resolve)
                .catch(reject);
        });
    }
}