Skip to content
Snippets Groups Projects
Select Git revision
  • 374815f097c8b968a5e2a82ab1b94970c05cd7cb
  • master default protected
  • 1.31
  • 4.38.7
  • 4.38.6
  • 4.38.5
  • 4.38.4
  • 4.38.3
  • 4.38.2
  • 4.38.1
  • 4.38.0
  • 4.37.2
  • 4.37.1
  • 4.37.0
  • 4.36.0
  • 4.35.0
  • 4.34.1
  • 4.34.0
  • 4.33.1
  • 4.33.0
  • 4.32.2
  • 4.32.1
  • 4.32.0
23 results

datasource.mjs

  • datasource.mjs 4.82 KiB
    /**
     * Copyright schukai GmbH and contributors 2023. All Rights Reserved.
     * Node module: @schukai/monster
     * This file is licensed under the AGPLv3 License.
     * License text available at https://www.gnu.org/licenses/agpl-3.0.en.html
     */
    
    import { internalSymbol } from "../constants.mjs";
    import { instanceSymbol } from "../constants.mjs";
    import { Base } from "../types/base.mjs";
    import { parseDataURL } from "../types/dataurl.mjs";
    import { isString } from "../types/is.mjs";
    import { ProxyObserver } from "../types/proxyobserver.mjs";
    import { validateObject } from "../types/validate.mjs";
    import { extend } from "./extend.mjs";
    import { Pathfinder } from "./pathfinder.mjs";
    
    export { Datasource };
    
    /**
     * This callback can be passed to a datasource and is used to adapt data structures.
     *
     * @callback Monster.Data.Datasource~exampleCallback
     * @param {*} value Value
     * @param {string} key  Key
     * @memberOf Monster.Data
     * @see Monster.Data.Datasource
     */
    
    /**
     * @private
     * @type {symbol}
     * @memberOf Monster.Data
     * @license AGPLv3
     * @since 1.24.0
     */
    const internalDataSymbol = Symbol.for(
    	"@schukai/monster/data/datasource/@@data",
    );
    
    /**
     * The datasource class is the basis for dealing with different data sources.
     * It provides a unified interface for accessing data
     * @externalExample ../../example/data/datasource.mjs
     * @license AGPLv3
     * @since 1.22.0
     * @copyright schukai GmbH
     * @memberOf Monster.Data
     * @summary The datasource class encapsulates the access to data objects.
     */
    class Datasource extends Base {
    	/**
    	 * creates a new datasource
    	 *
    	 */
    	constructor() {
    		super();
    
    		this[internalSymbol] = new ProxyObserver({
    			options: extend({}, this.defaults),
    		});
    
    		this[internalDataSymbol] = new ProxyObserver({});
    	}
    
    	/**
    	 * attach a new observer
    	 *
    	 * @param {Observer} observer
    	 * @returns {Datasource}
    	 */
    	attachObserver(observer) {
    		this[internalDataSymbol].attachObserver(observer);
    		return this;
    	}
    
    	/**
    	 * detach a observer
    	 *
    	 * @param {Observer} observer
    	 * @returns {Datasource}
    	 */
    	detachObserver(observer) {
    		this[internalDataSymbol].detachObserver(observer);
    		return this;
    	}
    
    	/**
    	 * @param {Observer} observer
    	 * @returns {boolean}
    	 */
    	containsObserver(observer) {
    		return this[internalDataSymbol].containsObserver(observer);
    	}
    
    	/**
    	 * Derived classes can override and extend this method as follows.
    	 *
    	 * ```
    	 * get defaults() {
    	 *    return Object.assign({}, super.defaults, {
    	 *        myValue:true
    	 *    });
    	 * }
    	 * ```
    	 */
    	get defaults() {
    		return {};
    	}
    
    	/**
    	 * Set option
    	 *
    	 * @param {string} path
    	 * @param {*} value
    	 * @return {Datasource}
    	 */
    	setOption(path, value) {
    		new Pathfinder(this[internalSymbol].getSubject()["options"]).setVia(
    			path,
    			value,
    		);
    		return this;
    	}
    
    	/**
    	 * @param {string|object} options
    	 * @return {Datasource}
    	 * @throws {Error} the options does not contain a valid json definition
    	 */
    	setOptions(options) {
    		if (isString(options)) {
    			options = parseOptionsJSON(options);
    		}
    		extend(
    			this[internalSymbol].getSubject()["options"],
    			this.defaults,
    			options,
    		);
    
    		return this;
    	}
    
    	/**
    	 * nested options can be specified by path `a.b.c`
    	 *
    	 * @param {string} path
    	 * @param {*} defaultValue
    	 * @return {*}
    	 */
    	getOption(path, defaultValue) {
    		let value;
    
    		try {
    			value = new Pathfinder(
    				this[internalSymbol].getRealSubject()["options"],
    			).getVia(path);
    		} catch (e) {}
    
    		if (value === undefined) return defaultValue;
    		return value;
    	}
    
    	/**
    	 * @throws {Error} this method must be implemented by derived classes.
    	 * @return {Promise}
    	 */
    	read() {
    		throw new Error("this method must be implemented by derived classes");
    	}
    
    	/**
    	 * @throws {Error} this method must be implemented by derived classes.
    	 * @return {Promise}
    	 */
    	write() {
    		throw new Error("this method must be implemented by derived classes");
    	}
    
    	/**
    	 * Returns real object
    	 *
    	 * @return {Object|Array}
    	 */
    	get() {
    		return this[internalDataSymbol].getRealSubject();
    	}
    
    	/**
    	 * @param {Object|Array} data
    	 * @return {Datasource}
    	 */
    	set(data) {
    		this[internalDataSymbol].setSubject(data);
    		return this;
    	}
    
    	/**
    	 * This method is called by the `instanceof` operator.
    	 * @returns {symbol}
    	 * @since 2.1.0
    	 */
    	static get [instanceSymbol]() {
    		return Symbol.for("@schukai/monster/data/datasource");
    	}
    }
    
    /**
     * @private
     * @param data
     * @return {Object}
     * @throws {Error} the options does not contain a valid json definition
     */
    function parseOptionsJSON(data) {
    	if (isString(data)) {
    		// the configuration can be specified as a data url.
    		try {
    			const dataUrl = parseDataURL(data);
    			data = dataUrl.content;
    		} catch (e) {}
    
    		try {
    			const obj = JSON.parse(data);
    			validateObject(obj);
    			return obj;
    		} catch (e) {
    			throw new Error(
    				`the options does not contain a valid json definition (actual: ${data}).`,
    			);
    		}
    	}
    
    	return {};
    }