Select Git revision
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 {};
}