2023-05-21 21:32:29 +03:00
|
|
|
(()=>{
|
2024-10-08 23:41:50 +03:00
|
|
|
class É {
|
|
|
|
map = new Map();
|
|
|
|
assertThrow(statement, message){
|
|
|
|
if(statement === false)
|
2023-06-18 16:10:05 +03:00
|
|
|
{
|
2024-10-08 23:41:50 +03:00
|
|
|
throw new Error(message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
checkTyping(locks, value)
|
|
|
|
{
|
|
|
|
if(locks)
|
|
|
|
{
|
|
|
|
if(locks.nullable === false)
|
2023-06-18 16:10:05 +03:00
|
|
|
{
|
2024-10-08 23:41:50 +03:00
|
|
|
this.assertThrow(typeof value != "undefined","value is not nullable")
|
|
|
|
}
|
|
|
|
|
|
|
|
if(locks.instance)
|
2023-06-18 16:10:05 +03:00
|
|
|
{
|
2024-10-08 23:41:50 +03:00
|
|
|
this.assertThrow(value instanceof (locks.instance),"value is not instanceof " + locks.instance.prototype.constructor.name)
|
|
|
|
}
|
|
|
|
|
|
|
|
if(locks.type)
|
|
|
|
{
|
|
|
|
this.assertThrow(locks.type == typeof value,"value is not typeof " + locks.type)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
setValue(path, value){
|
|
|
|
let isReadonly = this.getValue(path,'readonly') || false;
|
|
|
|
|
|
|
|
if(isReadonly) return;
|
|
|
|
|
|
|
|
let writers = this.getValue(path,'writer') || [];
|
|
|
|
|
|
|
|
if(writers.length){
|
|
|
|
let oldvalue = this.getValue(path);
|
|
|
|
for (const writer of writers)
|
|
|
|
{
|
|
|
|
value = writer(value,oldvalue);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let locks = this.getValue(path,'locks') || false;
|
|
|
|
|
|
|
|
locks && this.checkTyping(locks, value)
|
|
|
|
|
|
|
|
let {npath,process} = this.setAttribute(path, "value", value);
|
|
|
|
|
|
|
|
// optimization
|
|
|
|
if(!process){
|
|
|
|
return []
|
|
|
|
}
|
|
|
|
|
|
|
|
let callers = [];
|
|
|
|
|
|
|
|
this.mapHierachy(npath, node => {
|
|
|
|
let localCallers = node.has("changelistener") ? node.get("changelistener") : [];
|
|
|
|
if(localCallers.length){
|
|
|
|
callers = callers.concat(localCallers);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
if(callers.length) {
|
|
|
|
return callers.filter(e=>typeof e == "function").map(callbacks => {
|
|
|
|
let result;
|
|
|
|
try{
|
|
|
|
result = callbacks(npath)
|
|
|
|
}catch(error){
|
|
|
|
result = error
|
|
|
|
}finally{
|
|
|
|
return result;
|
2023-06-18 16:10:05 +03:00
|
|
|
}
|
2024-10-08 23:41:50 +03:00
|
|
|
}).filter(e => e !== void 0);
|
|
|
|
}else{
|
|
|
|
return callers.filter(e=>typeof e == "function").map(callbacks => {
|
|
|
|
let result;
|
|
|
|
try{
|
|
|
|
result = callbacks(npath)
|
|
|
|
}catch(error){
|
|
|
|
result = error
|
|
|
|
}finally{
|
|
|
|
return result;
|
2023-06-18 16:10:05 +03:00
|
|
|
}
|
2024-10-08 23:41:50 +03:00
|
|
|
}).filter(e => e !== void 0);
|
|
|
|
}
|
2023-05-17 23:39:01 +03:00
|
|
|
}
|
2024-10-08 23:41:50 +03:00
|
|
|
deleteAttribute(
|
|
|
|
path,
|
|
|
|
attributeName = "value"
|
|
|
|
){
|
|
|
|
let npath = this.normalizePath(path);
|
|
|
|
this.map.get(npath).delete(attributeName)
|
2023-06-18 15:09:09 +03:00
|
|
|
}
|
2024-10-08 23:41:50 +03:00
|
|
|
removeNode(path){
|
|
|
|
let npath = this.normalizePath(path);
|
|
|
|
this.map.delete(npath);
|
2023-06-18 15:09:09 +03:00
|
|
|
}
|
2024-10-08 23:41:50 +03:00
|
|
|
removeAttribute(
|
|
|
|
path,
|
|
|
|
attributeName
|
|
|
|
){
|
|
|
|
if(this.map.has(path))
|
|
|
|
{
|
|
|
|
this.map.delete(attributeName)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
setAttribute(
|
|
|
|
path,
|
|
|
|
attributeName = "value",
|
|
|
|
value,
|
|
|
|
callableValue = true
|
|
|
|
){
|
|
|
|
let process;
|
|
|
|
let npath = this.normalizePath(path);
|
|
|
|
if(this.map.has(path))
|
|
|
|
{
|
|
|
|
let node = this.map.get(npath);
|
|
|
|
if(typeof value == "function" && callableValue)
|
2023-06-18 15:25:43 +03:00
|
|
|
{
|
2024-10-08 23:41:50 +03:00
|
|
|
let oldv = node.get(attributeName);
|
|
|
|
let newv = value(oldv);
|
|
|
|
let diffAlgorithm = node.has('diff') ? node.get('diff') : ediffEpsilon;
|
|
|
|
if(diffAlgorithm(oldv, newv))
|
|
|
|
{
|
|
|
|
node.set(attributeName, newv)
|
|
|
|
process = 'update';
|
|
|
|
}
|
2023-06-18 15:25:43 +03:00
|
|
|
}else{
|
2024-10-08 23:41:50 +03:00
|
|
|
let oldv = node.get(attributeName);
|
|
|
|
let diffAlgorithm = node.has('diff') ? node.get('diff') : ediffEpsilon;
|
|
|
|
if(diffAlgorithm(oldv, value))
|
|
|
|
{
|
|
|
|
this.map.get(npath).set(attributeName, value)
|
|
|
|
process = 'update';
|
|
|
|
}
|
|
|
|
this.map.get(npath).set(attributeName, value)
|
2023-06-18 15:25:43 +03:00
|
|
|
}
|
2024-10-08 23:41:50 +03:00
|
|
|
}else{
|
|
|
|
let valueMap = new Map();
|
|
|
|
valueMap.set(attributeName, typeof value == "function" ? value() : value);
|
|
|
|
this.map.set(npath, valueMap);
|
|
|
|
process = 'create';
|
2023-06-18 15:09:09 +03:00
|
|
|
}
|
2024-10-08 23:41:50 +03:00
|
|
|
return {npath,process};
|
|
|
|
}
|
|
|
|
mapHierachy(npath, mapCallback){
|
|
|
|
let npaths = npath.split('/');
|
|
|
|
let t = [], current = "";
|
|
|
|
for (const path of npaths)
|
2023-05-17 23:39:01 +03:00
|
|
|
{
|
2024-10-08 23:41:50 +03:00
|
|
|
t.push(path);
|
|
|
|
current = t.join('/');
|
|
|
|
if(this.map.has(current))
|
|
|
|
{
|
|
|
|
mapCallback(this.map.get(current))
|
|
|
|
};
|
2023-05-17 23:39:01 +03:00
|
|
|
}
|
|
|
|
}
|
2024-10-08 23:41:50 +03:00
|
|
|
createListenerForPath(path, callback){
|
|
|
|
let npath = this.normalizePath(path);
|
|
|
|
this.setAttribute(
|
|
|
|
npath,
|
|
|
|
'changelistener',
|
|
|
|
callbacksOrNull => (callbacksOrNull ? callbacksOrNull.concat([callback]) : [callback])
|
|
|
|
);
|
2023-05-17 23:39:01 +03:00
|
|
|
}
|
2024-10-08 23:41:50 +03:00
|
|
|
getValue(path, attribute = "value"){
|
|
|
|
let npath = this.normalizePath(path);
|
|
|
|
let staticValue = this.map.get(npath)?.get(attribute);
|
|
|
|
if(attribute == 'value')
|
2023-05-21 21:32:29 +03:00
|
|
|
{
|
2024-10-08 23:41:50 +03:00
|
|
|
let readers = this.getValue(path,'reader') || [];
|
|
|
|
if(readers.length){
|
|
|
|
let oldvalue = staticValue, value;
|
|
|
|
for (const reader of readers)
|
|
|
|
{
|
|
|
|
value = reader(oldvalue, value);
|
|
|
|
}
|
|
|
|
return value;
|
|
|
|
}else{
|
|
|
|
return staticValue;
|
|
|
|
}
|
|
|
|
}else{
|
|
|
|
return staticValue;
|
2023-05-21 21:32:29 +03:00
|
|
|
}
|
2024-10-08 23:41:50 +03:00
|
|
|
}
|
|
|
|
normalizePath(str){
|
|
|
|
if(!str)
|
2023-05-17 23:39:01 +03:00
|
|
|
{
|
2024-10-08 23:41:50 +03:00
|
|
|
return ':moment:'
|
|
|
|
}else if(!(typeof str == "string")){
|
|
|
|
throw new Error("É is not valid path ")
|
|
|
|
}else{
|
|
|
|
if(str.indexOf('/') == -1)
|
2023-05-17 23:39:01 +03:00
|
|
|
{
|
2024-10-08 23:41:50 +03:00
|
|
|
return str;
|
2023-05-17 23:39:01 +03:00
|
|
|
}
|
2024-10-08 23:41:50 +03:00
|
|
|
let path = str.split('/').map(e => e.trim());
|
|
|
|
let first = path[0]
|
|
|
|
let last = path[path.length - 1];
|
|
|
|
if(first == '') path.shift();
|
|
|
|
if(last == '') path.pop();
|
|
|
|
let result = path.join('/');
|
|
|
|
return result;
|
2023-05-17 23:39:01 +03:00
|
|
|
}
|
2023-05-21 21:32:29 +03:00
|
|
|
}
|
2024-10-08 23:41:50 +03:00
|
|
|
createListenerForWrite(path, callback){
|
|
|
|
let npath = this.normalizePath(path);
|
|
|
|
this.setAttribute(
|
|
|
|
npath,
|
|
|
|
'writer',
|
|
|
|
value => (value || []).concat([callback]),
|
|
|
|
true
|
|
|
|
)
|
|
|
|
}
|
|
|
|
createListenerForRead(path, callback){
|
|
|
|
let npath = this.normalizePath(path);
|
|
|
|
this.setAttribute(
|
|
|
|
npath,
|
|
|
|
'reader',
|
|
|
|
value => (value || []).concat([callback]),
|
|
|
|
true
|
|
|
|
)
|
|
|
|
}
|
|
|
|
changeDifferenceAlgorithm(path, callback){
|
|
|
|
this.setAttribute(
|
|
|
|
path,
|
|
|
|
'diff',
|
|
|
|
callback,
|
|
|
|
false
|
|
|
|
)
|
|
|
|
}
|
|
|
|
lockValue(path){
|
|
|
|
é.current.setAttribute(path, 'readonly', true);
|
|
|
|
}
|
|
|
|
unlockValue(path){
|
|
|
|
é.current.removeAttribute(path, 'readonly');
|
|
|
|
}
|
|
|
|
setTypedLocks(path, options){
|
|
|
|
let npath = this.normalizePath(path);
|
|
|
|
if(options.instance !== void 0)
|
2023-05-21 21:32:29 +03:00
|
|
|
{
|
2024-10-08 23:41:50 +03:00
|
|
|
this.setAttribute(
|
|
|
|
npath,
|
|
|
|
'locks',
|
|
|
|
obj => ({
|
|
|
|
...(obj || {}),
|
|
|
|
instance: options.instance
|
|
|
|
}),
|
|
|
|
true
|
|
|
|
);
|
2023-05-21 21:32:29 +03:00
|
|
|
}
|
2024-10-08 23:41:50 +03:00
|
|
|
if(options.type !== void 0)
|
2023-05-21 21:32:29 +03:00
|
|
|
{
|
2024-10-08 23:41:50 +03:00
|
|
|
this.setAttribute(
|
|
|
|
npath,
|
|
|
|
'locks',
|
|
|
|
obj => ({
|
|
|
|
...(obj || {}),
|
|
|
|
type: options.type
|
|
|
|
}),
|
|
|
|
true
|
|
|
|
);
|
2023-05-21 21:32:29 +03:00
|
|
|
}
|
2024-10-08 23:41:50 +03:00
|
|
|
if(options.nullable !== void 0)
|
|
|
|
{
|
|
|
|
this.setAttribute(
|
|
|
|
npath,
|
|
|
|
'locks',
|
|
|
|
obj => ({
|
|
|
|
...(obj || {}),
|
|
|
|
nullable: options.nullable
|
|
|
|
}),
|
|
|
|
true
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
setTypedUnlocks(path, options){
|
|
|
|
this.setAttribute(
|
|
|
|
path,
|
|
|
|
'locks',
|
|
|
|
void 0,
|
|
|
|
false
|
|
|
|
);
|
2023-05-17 23:39:01 +03:00
|
|
|
}
|
|
|
|
}
|
2024-10-08 23:41:50 +03:00
|
|
|
function é(arg1, arg2)
|
|
|
|
{
|
|
|
|
if("string" == typeof arg1)
|
2023-05-21 21:32:29 +03:00
|
|
|
{
|
2024-10-08 23:41:50 +03:00
|
|
|
if(arg2 === void 0)
|
|
|
|
{
|
|
|
|
return é.current.getValue(arg1)
|
2023-05-21 21:32:29 +03:00
|
|
|
}
|
2024-10-08 23:41:50 +03:00
|
|
|
|
|
|
|
if(["bigint","boolean"/*,"function"*/,"number","object","symbol","string"].includes(typeof arg2) || arg2 === null)
|
|
|
|
{
|
|
|
|
return é.current.setValue(arg1, arg2)
|
2023-05-21 21:32:29 +03:00
|
|
|
}
|
2024-10-08 23:41:50 +03:00
|
|
|
/*
|
|
|
|
if(["function"].includes(typeof arg2) || arg2 === null)
|
|
|
|
{
|
|
|
|
return é.current.setFunctionValue(arg1, arg2)
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
if(
|
|
|
|
"function" == typeof arg1 &&
|
|
|
|
arg2 instanceof Array &&
|
|
|
|
arg2.every(e => typeof e == "string")
|
|
|
|
){
|
|
|
|
for (const path of arg2)
|
|
|
|
{
|
|
|
|
é.current.createListenerForPath(path, arg1);
|
2023-05-21 21:32:29 +03:00
|
|
|
}
|
2024-10-08 23:41:50 +03:00
|
|
|
}
|
2023-05-21 21:32:29 +03:00
|
|
|
}
|
2024-10-08 23:41:50 +03:00
|
|
|
é.current = new É();
|
|
|
|
é.write = (...args) => é.current.createListenerForWrite(...args);
|
|
|
|
é.read = (...args) => é.current.createListenerForRead(...args);
|
|
|
|
é.diff = (...args) => é.current.changeDifferenceAlgorithm(...args);
|
2023-05-21 21:32:29 +03:00
|
|
|
|
2024-10-08 23:41:50 +03:00
|
|
|
é.var = (path, value, write, read) => {
|
|
|
|
é.current.setValue(path, value);
|
|
|
|
é.current.createListenerForWrite(path, write);
|
|
|
|
é.current.createListenerForWrite(path, read);
|
|
|
|
return {
|
|
|
|
get: () => é.current.getValue(path),
|
|
|
|
set: (value) => é.current.setValue(path, value)
|
2023-05-17 23:39:01 +03:00
|
|
|
}
|
2023-05-21 21:32:29 +03:00
|
|
|
};
|
2024-10-08 23:41:50 +03:00
|
|
|
|
|
|
|
é.typedLock = (path, options) => {
|
|
|
|
é.current.setTypedLocks(path,options)
|
|
|
|
};
|
|
|
|
|
|
|
|
é.typedUnLock = path => {
|
|
|
|
é.current.setTypedUnlocks(path)
|
|
|
|
};
|
|
|
|
|
|
|
|
é.delete = path => {
|
|
|
|
é.current.removeNode(path);
|
|
|
|
};
|
|
|
|
|
|
|
|
é.lockValue = path => {
|
|
|
|
é.current.setAttribute(path, 'readonly', true);
|
|
|
|
};
|
|
|
|
é.unlockValue = path => {
|
|
|
|
é.current.removeNode(path, 'readonly', true);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Differance algorithm
|
|
|
|
function ediffEpsilon(a, b, c){
|
|
|
|
let cursoryDiffResult = typedDiff(a, b);
|
|
|
|
if (cursoryDiffResult == typedDiff.arrayDiff) {
|
|
|
|
return objectDiff(a, b, c || []);
|
|
|
|
} else {
|
|
|
|
return cursoryDiffResult == typedDiff.some ? false : true;
|
2023-05-17 23:39:01 +03:00
|
|
|
}
|
2024-10-08 23:41:50 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
function objectDiff(a, b, c){
|
|
|
|
if (c.includes(a) && c.includes(b)) {
|
|
|
|
console.error("Circular object detected !");
|
|
|
|
return objectKeyDiff(a, b);
|
|
|
|
}
|
|
|
|
let typea = a instanceof Array ? 0 : a.byteLength ? 1 : 2;
|
|
|
|
let typeb = b instanceof Array ? 0 : b.byteLength ? 1 : 2;
|
|
|
|
if (typea != typeb) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (typea == 0) {
|
|
|
|
if (a.length != b.length) {
|
|
|
|
return true;
|
2023-05-21 21:32:29 +03:00
|
|
|
}
|
2024-10-08 23:41:50 +03:00
|
|
|
for (let k = 0; k < a.length; k++) {
|
|
|
|
if (ediffEpsilon(a[k], b[k])) {
|
|
|
|
return true;
|
|
|
|
}
|
2023-05-21 21:32:29 +03:00
|
|
|
}
|
2024-10-08 23:41:50 +03:00
|
|
|
return false;
|
2023-05-17 23:39:01 +03:00
|
|
|
}
|
2024-10-08 23:41:50 +03:00
|
|
|
if (a instanceof Object) {
|
|
|
|
if (Object.keys(a).length != Object.keys(b).length) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
c.push(a);
|
|
|
|
c.push(b);
|
|
|
|
for (const key in a) {
|
|
|
|
if (Object.hasOwnProperty.call(a, key)) {
|
|
|
|
if (ediffEpsilon(a[key], b[key], c)) {
|
|
|
|
return true;
|
2023-05-21 21:32:29 +03:00
|
|
|
}
|
2024-10-08 23:41:50 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
2023-05-21 21:32:29 +03:00
|
|
|
}
|
2023-05-17 23:39:01 +03:00
|
|
|
};
|
2024-10-08 23:41:50 +03:00
|
|
|
|
|
|
|
function arrayLengthPointerDiff(a, b){
|
|
|
|
return a.length != b.length || !a.every((v, i) => a[i] === v);
|
2023-05-17 23:39:01 +03:00
|
|
|
};
|
2024-10-08 23:41:50 +03:00
|
|
|
|
|
|
|
function objectKeyDiff(a, b){
|
|
|
|
return arrayLengthPointerDiff(
|
|
|
|
Object.keys(a),
|
|
|
|
Object.keys(b)
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
function typedDiff(a, b){
|
|
|
|
if(typeof a == typeof b)
|
2023-06-18 16:10:05 +03:00
|
|
|
{
|
2024-10-08 23:41:50 +03:00
|
|
|
switch (typeof a) {
|
|
|
|
case "undefined":
|
|
|
|
case "function": {
|
|
|
|
return typeof a == typeof b ? typedDiff.some : typedDiff.different;
|
|
|
|
}
|
|
|
|
case "symbol":
|
|
|
|
case "bigint":
|
|
|
|
case "boolean":
|
|
|
|
case "number":
|
|
|
|
case "string": {
|
|
|
|
return a === b ? typedDiff.some : typedDiff.different;
|
|
|
|
}
|
|
|
|
case "object": {
|
|
|
|
return typedDiff.arrayDiff;
|
|
|
|
}
|
|
|
|
}
|
2023-06-18 16:10:05 +03:00
|
|
|
}else{
|
2024-10-08 23:41:50 +03:00
|
|
|
return typedDiff.different;
|
2023-06-18 16:10:05 +03:00
|
|
|
}
|
2024-10-08 23:41:50 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
typedDiff.arrayDiff = -1;
|
|
|
|
typedDiff.some = 0;
|
|
|
|
typedDiff.different = 1;
|
2023-06-18 16:10:05 +03:00
|
|
|
|
2023-05-21 21:32:29 +03:00
|
|
|
try{
|
|
|
|
module.exports = é;
|
|
|
|
}catch{
|
|
|
|
window.é = é;
|
|
|
|
}
|
2023-06-18 16:10:05 +03:00
|
|
|
})();
|