Source: pouchdbdatacollection.js

/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

const DataCollection = require('composer-runtime').DataCollection;
const Logger = require('composer-common').Logger;
const pouchCollate = require('pouchdb-collate');
const PouchDBUtils = require('./pouchdbutils');

const LOG = Logger.getLog('PouchDBDataCollection');

/**
 * Base class representing a data collection provided by a {@link DataService}.
 * @protected
 * @abstract
 */
class PouchDBDataCollection extends DataCollection {

    /**
     * Constructor.
     * @param {DataService} dataService The owning data service.
     * @param {Dexie} db The database to use.
     * @param {string} collectionId The collection ID to use.
     * @param {string} uuid The uuid to use
     */
    constructor(dataService, db, collectionId, uuid) {
        super(dataService);
        const method = 'constructor';
        LOG.entry(method, dataService, db, collectionId);
        this.db = db;
        this.uuid = uuid;
        this.collectionId = collectionId;
        LOG.exit(method);
    }

    /**
     * Get all of the objects in this collection.
     * @abstract
     * @return {Promise} A promise that will be resolved with an array of objects,
     * or rejected with an error.
     */
    getAll() {
        const method = 'getAll';
        LOG.entry(method);
        let compositeKey = [this.collectionId];
        if(this.uuid) {
            compositeKey.unshift(this.uuid);
        }

        const startKey = pouchCollate.toIndexableString(compositeKey);
        const endCompositeKey = compositeKey;
        endCompositeKey.push('\uffff');
        const endKey = pouchCollate.toIndexableString(endCompositeKey);
        return this.db.allDocs({ include_docs: true, startkey: startKey, endkey: endKey, inclusive_end: false })
            .then((response) => {
                const result = response.rows.map((row) => {
                    delete row.doc._id;
                    delete row.doc._rev;
                    return row.doc;
                });
                LOG.exit(result);
                return result;
            });
    }

    /**
     * Get the specified object in this collection.
     * @abstract
     * @param {string} id The ID of the object.
     * @return {Promise} A promise that will be resolved with an object, or rejected
     * with an error.
     */
    get(id) {
        const method = 'get';
        LOG.entry(method, id);
        let compositeKey = [this.collectionId, id];
        if(this.uuid) {
            compositeKey.unshift(this.uuid);
        }

        const key = pouchCollate.toIndexableString(compositeKey);
        return PouchDBUtils.getDocument(this.db, key)
            .then((doc) => {
                if (!doc) {
                    throw new Error(`Object with ID '${id}' in collection with ID '${this.collectionId}' does not exist`);
                }
                LOG.exit(method, doc);
                return doc;
            });
    }

    /**
     * Determine whether the specified object exists in this collection.
     * @abstract
     * @param {string} id The ID of the object.
     * @return {Promise} A promise that will be resolved with a boolean indicating
     * whether the object exists.
     */
    exists(id) {
        const method = 'exists';
        LOG.entry(method, id);
        let compositeKey = [this.collectionId, id];
        if(this.uuid) {
            compositeKey.unshift(this.uuid);
        }

        const key = pouchCollate.toIndexableString(compositeKey);
        return PouchDBUtils.getDocument(this.db, key)
            .then((doc) => {
                LOG.exit(method, !!doc);
                return !!doc;
            });
    }

    /**
     * Add an object to the collection.
     * @abstract
     * @param {string} id The ID of the object.
     * @param {Object} object The object.
     * @param {boolean} force Whether to force creation without checking it already exists.
     * @return {Promise} A promise that will be resolved when complete, or rejected
     * with an error.
     */
    add(id, object, force) {
        const method = 'add';
        LOG.entry(method, id, object, force);
        force = !!force;

        let compositeKey = [this.collectionId, id];
        if(this.uuid) {
            compositeKey.unshift(this.uuid);
            object.$networkId = this.uuid;
        }

        const key = pouchCollate.toIndexableString(compositeKey);
        return PouchDBUtils.getDocument(this.db, key)
            .then((doc) => {
                if (doc && !force) {
                    throw new Error(`Failed to add object with ID '${id}' as the object already exists`);
                }
                return this.dataService.handleAction(() => {
                    return PouchDBUtils.putDocument(this.db, key, object);
                });
            });
    }

    /**
     * Add an object to the collection.
     * @abstract
     * @param {string} id The ID of the object.
     * @param {Object} object The object.
     * @return {Promise} A promise that will be resolved when complete, or rejected
     * with an error.
     */
    update(id, object) {
        const method = 'update';
        LOG.entry(method, id, object);
        let compositeKey = [this.collectionId, id];
        if(this.uuid) {
            compositeKey.unshift(this.uuid);
            object.$networkId = this.uuid;
        }

        const key = pouchCollate.toIndexableString(compositeKey);
        return PouchDBUtils.getDocument(this.db, key)
            .then((doc) => {
                if (!doc) {
                    throw new Error(`Object with ID '${id}' in collection with ID '${this.collectionId}' does not exist`);
                }
                return this.dataService.handleAction(() => {
                    return PouchDBUtils.putDocument(this.db, key, object);
                });
            });
    }

    /**
     * Remove an object from the collection.
     * @abstract
     * @param {string} id The ID of the object.
     * @return {Promise} A promise that will be resolved when complete, or rejected
     * with an error.
     */
    remove(id) {
        const method = 'remove';
        LOG.entry(method, id);
        let compositeKey = [this.collectionId, id];
        if(this.uuid) {
            compositeKey.unshift(this.uuid);
        }

        const key = pouchCollate.toIndexableString(compositeKey);
        return PouchDBUtils.getDocument(this.db, key)
            .then((doc) => {
                if (!doc) {
                    throw new Error(`Object with ID '${id}' in collection with ID '${this.collectionId}' does not exist`);
                }
                return this.dataService.handleAction(() => {
                    return PouchDBUtils.removeDocument(this.db, key);
                });
            });
    }

}

module.exports = PouchDBDataCollection;