core/Datastore.js

/**
 *  DataStore - Superclass for all backing stores
 * 
 *  This code is licensed under the MIT License (MIT).
 *  
 *  Copyright 2020, 2021, 2022 Rolf Bagge, Janus B. Kristensen, CAVI,
 *  Center for Advanced Visualization and Interaction, Aarhus University
 *  
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the “Software”), to deal
 *  in the Software without restriction, including without limitation the rights 
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
 *  copies of the Software, and to permit persons to whom the Software is 
 *  furnished to do so, subject to the following conditions:
 *  
 *  The above copyright notice and this permission notice shall be included in 
 *  all copies or substantial portions of the Software.
 *  
 *  THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
 *  THE SOFTWARE.
 *  
 */

/**
 * Datastores
 * @namespace Datastores
 */

// superclass for all datastores (mostly empty for potential later introspection code)
class Datastore {
    constructor(name, options){
        this.name = name;
        this.options = options;
        this.mappedConcepts = new Map();
    }

    isConceptMapped(concept){
        return this.isConceptTypeMapped(concept.name);
    }
    isConceptTypeMapped(conceptTypeName){
        return this.mappedConcepts.has(conceptTypeName);
    }
    isPropertyMapped(concept, property){
        if (!this.isConceptMapped(concept)) return false;
        return this.mappedConcepts.get(concept.name).has(property.name);
    }
    isPropertyNameMapped(conceptName, propertyName){
        if (!this.isConceptTypeMapped(conceptName)) return false;
        return this.mappedConcepts.get(conceptName).has(propertyName);
    }    
    internalAddConceptMapping(concept){
        if (!this.isConceptMapped(concept))
            this.mappedConcepts.set(concept.name, new Map());
    }    
    internalAddPropertyMapping(concept, property, trackingData={}){
        if (this.isPropertyMapped(concept, property)){
            throw new Error('Already has internal mapping for '+concept+"."+property);
        }
        this.internalAddConceptMapping(concept);
        let propertyMap = this.mappedConcepts.get(concept.name);
        propertyMap.set(property.name, trackingData);
    }
    internalRemovePropertyMapping(concept, property){
        if (!this.isConceptMapped(concept)) throw new Error("Concept not mapped when trying to remove "+concept+"."+property);
        let propertyMap = this.mappedConcepts.get(concept.name);
        propertyMap.delete(property.name);
    }    
    
    internalPropertyTrackingData(concept, property){
        return this.mappedConcepts.get(concept.name).get(property.name);
    }

    createBackingStore(concept, property) {
        throw new Error("createBackingStore, should always be overridden in Datastore subclass - "+this.constructor.name);
    }

    removeBackingStore(concept, property) {
        throw new Error("removeBackingStore, should always be overridden in Datastore subclass - "+this.constructor.name);
    }

    isShared() {
        return true;
    }

    async init() {
        throw new Error("init, should always be overridden in Datastore subclass - "+this.constructor.name);
    }

    destroy() {
        throw new Error("destroy, should always be overridden in Datastore subclass - "+this.constructor.name);
    }

    /**
     *
     * @param {String[]} typeNames
     * @param {Filter} query
     * @param {VarvContext} context
     * @param {number} limit
     * @param {Concept} localConcept
     * @returns {Promise<String[]>}
     */
    async lookupInstances(typeNames, query, context, limit = 0, localConcept = null) {
        throw new Error("Implement [lookupInstances] me in sub datastores! - "+this.constructor.name);
    }

    /**
     * @param {String[]} typeNames
     * @param {Filter} query
     * @param {VarvContext} context
     * @param {Concept} localConcept
     * @returns {Promise<number>}
     */
    async countInstances(typeNames, query, context, localConcept) {
        throw new Error("Implement [countInstances] me in sub datastores! - "+this.constructor.name);
    }

    /**
     * @param {String[]} typeNames
     * @param {Filter} query
     * @param {VarvContext} context
     * @param {Concept} localConcept
     * @returns {Promise<boolean>}
     */
    async existsInstance(typeNames, query, context, localConcept) {
        throw new Error("Implement [existsInstance] me in sub datastores! - "+this.constructor.name);
    }

    /**
     *
     * @param {String} uuid
     * @returns {Promise<Concept>}
     */
    async lookupConcept(uuid) {
        throw new Error("Implement [lookupConcept] me in sub datastores! - "+this.constructor.name);
    }

    static getDatastoreFromName(name) {
        return Datastore.datastores.get(name);
    }

    static registerDatastoreType(name, datastoreType) {
        Datastore.datastoreTypes.set(name, datastoreType);
    }

    static getDatastoreType(name) {
        return Datastore.datastoreTypes.get(name);
    }

    static getAllDatastores() {
        return Array.from(Datastore.datastores.values());
    }
}
Datastore.DEBUG = false;
Datastore.datastores = new Map();
Datastore.optionalDatastores = ["cauldron"]; // A list of datastores for which there is no warning emitted if not found
Datastore.datastoreTypes = new Map();

window.Datastore = Datastore;