Select Git revision
config-manager.mjs

Volker Schukai authored
config-manager.mjs 6.90 KiB
/**
* Copyright © schukai GmbH and all contributing authors, {{copyRightYear}}. All rights reserved.
* Node module: @schukai/monster
*
* This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3).
* The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html
*
* For those who do not wish to adhere to the AGPLv3, a commercial license is available.
* Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms.
* For more information about purchasing a commercial license, please contact schukai GmbH.
*
* SPDX-License-Identifier: AGPL-3.0
*/
import {
assembleMethodSymbol,
CustomElement,
registerCustomElement,
} from "../../dom/customelement.mjs";
import { ConfigManagerStyleSheet } from "./stylesheet/config-manager.mjs";
import { getWindow } from "../../dom/util.mjs";
import { instanceSymbol } from "../../constants.mjs";
export { ConfigManager };
/**
* @private
* @type {symbol}
*/
const indexDBInstanceSymbol = Symbol("indexDBInstance");
/**
* @private
* @type {symbol}
*/
const initializedPromiseSymbol = Symbol("initializedPromiseSymbol");
/**
* @private
* @type {string}
*/
const MODE_READONLY = "readonly";
/**
* @private
* @type {string}
*/
const MODE_READ_WRITE = "readwrite";
/**
* The Config Manager component is used to encapsulate the configuration of the application.
*
* @copyright schukai GmbH
* @summary A config manager component
*/
class ConfigManager extends CustomElement {
/**
* This method is called by the `instanceof` operator.
* @return {symbol}
*/
static get [instanceSymbol]() {
return Symbol.for("@schukai/component-host/config-manager@@instance");
}
constructor() {
super();
/**
* @private
* @type {symbol}
*/
this[initializedPromiseSymbol] = [];
this[indexDBInstanceSymbol] = null;
this[initializedPromiseSymbol].push(openDatabase.call(this));
}
/**
* @return {Promise}
*/
ready() {
return Promise.all(this[initializedPromiseSymbol]);
}
/**
* To set the options via the HTML tag, the attribute `data-monster-options` must be used.
* @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
*
* The individual configuration values can be found in the table.
*
* @property {Object} templates Template definitions
* @property {string} templates.main Main template
*/
get defaults() {
return Object.assign({}, super.defaults, {
templates: {
main: getTemplate(),
},
indexDB: {
name: "monster",
version: 2,
objectStore: {
name: "config",
keyPath: "key",
},
},
});
}
/**
* @param {string} key
* @return {Promise<unknown>}
*/
getConfig(key) {
return this.ready().then(() => {
return getBlob.call(this, key);
});
}
/**
* @param {string} key
* @return {Promise<boolean>}
*/
hasConfig(key) {
return this.ready()
.then(() => {
return getBlob.call(this, key);
})
.then(() => {
return true;
})
.catch(() => {
return false;
});
}
/**
* @param {string} key
* @param {*} value
* @return {Promise<unknown>}
*/
setConfig(key, value) {
return this.ready().then(() => {
return setBlob.call(this, key, value);
});
}
deleteConfig(key) {
return this.ready().then(() => {
return deleteBlob.call(this, key);
});
}
/**
*
* @return {string}
*/
static getTag() {
return "monster-config-manager";
}
/**
* @return {CSSStyleSheet[]}
*/
static getCSSStyleSheet() {
return [ConfigManagerStyleSheet];
}
/**
*/
[assembleMethodSymbol]() {
super[assembleMethodSymbol]();
}
}
function openDatabase() {
const window = getWindow();
const name = this.getOption("indexDB.name");
const version = this.getOption("indexDB.version");
const storageName = this.getOption("indexDB.objectStore.name");
const KeyPath = this.getOption("indexDB.objectStore.keyPath");
if (!name || !version) {
throw new Error("The database name and version must be set.");
}
const request = window.indexedDB.open(name, version);
return new Promise((resolve, reject) => {
request.onerror = (event) => {
console.error("Error opening database", event);
reject(request.error);
};
request.onsuccess = (event) => {
this[indexDBInstanceSymbol] = event?.target?.result;
resolve(request.result);
};
request.onupgradeneeded = (event) => {
const db = event.target.result;
let objectStore;
if (!db.objectStoreNames.contains(storageName)) {
objectStore = db.createObjectStore(storageName, { keyPath: KeyPath });
}
objectStore.transaction.oncomplete = (event) => {
console.log("Database upgrade complete");
resolve();
};
};
});
}
/**
* @param {string} mode either "readonly" or "readwrite"
*/
function getObjectStore(mode) {
const storageName = this.getOption("indexDB.objectStore.name");
if (!this[indexDBInstanceSymbol]) {
throw new Error("The database is not open.");
}
// @see https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase/transaction
// transaction(storeNames, mode, options)
const tx = this[indexDBInstanceSymbol].transaction(storageName, mode);
return tx.objectStore(storageName);
}
/**
* @return {Promise<unknown>}
*/
function clearObjectStore() {
const store = getObjectStore.call(this, "readwrite");
return new Promise((resolve, reject) => {
const req = store.clear();
req.onsuccess = function (evt) {
console.log("clearObjectStore completed");
resolve();
};
req.onerror = function (evt) {
console.error("clearObjectStore:", evt.target.errorCode);
reject(evt.target.errorCode);
};
});
}
function getBlob(key) {
const store = getObjectStore.call(this, MODE_READONLY);
const req = store.get(key);
return new Promise((resolve, reject) => {
req.onsuccess = function (evt) {
const value = evt.target.result;
if (value) {
resolve(value.blob);
return;
}
reject(new Error("The value of the key '" + key + "' is not defined."));
};
});
}
function deleteBlob(key) {
const store = getObjectStore.call(this, MODE_READ_WRITE);
const req = store.delete(key);
return new Promise((resolve, reject) => {
req.onsuccess = function (evt) {
resolve();
};
req.onerror = function (evt) {
console.error("deleteBlob:", evt.target.errorCode);
reject(evt.target.errorCode);
};
});
}
function setBlob(key, blob) {
const store = getObjectStore.call(this, MODE_READ_WRITE);
const KeyPath = this.getOption("indexDB.objectStore.keyPath");
const obj = {};
obj[KeyPath] = key;
obj.blob = blob;
const req = store.put(obj);
return new Promise((resolve, reject) => {
req.onsuccess = function (evt) {
resolve();
};
req.onerror = function (evt) {
console.error("setBlob:", evt.target.errorCode);
reject(evt.target.errorCode);
};
});
}
function getTemplate() {
// language=HTML
return `
<div data-monster-role="control" part="control">
<slot></slot>
</div>`;
}
registerCustomElement(ConfigManager);