v2 alpha released

This commit is contained in:
Abdussamed 2024-10-08 23:41:50 +03:00
parent 5ad3c17235
commit 7381d3ff2d
2 changed files with 765 additions and 475 deletions

549
readme.md
View File

@ -1,241 +1,420 @@
# WiréJS | Değişken değil, kablo kullanın !
## Açıklama
# Açıklama
WiréS, ReactJS kütüphanesinin useState ve watch kancalarından ilham alınarak browser ortamı için tekrar yazılmış bir kütüphanedir.
Verileri getter/setter fonksiyonlarıyla sararak gerçek değişikleri algılayan özelliği sayesinde gereksiz güncellemeleri göz ardı eder.
WiréJS, dizinlenmiş akıllı değişkenler oluşturup kendi kendini yönetebilen veri değişimini kolayca yönetebilen sistemler oluşturabileceğiniz
yapılar kurmanıza olanak sağlayan sistemdir.
Veri ilişkilerini önceden belirleyerek değişiklik olduğunda okunacak ve yazılacak verileri koşullara bağlayabilir, okuma ve yazma öncesi ara katmanlar ekleyebilirsiniz
Verileri wriré içerisinden oluşturarak değişimleri ile etkileyecek diğer değişkenleri bağımsız yapılar içinde yazabilirsiniz.
Ayrıca verilerinizi sistematik olarak depolar bu sayede veri türünü okuma ve yazma olaylarını denetleyebilirsiniz (getter/setter)
## Kullanım örneği
# Kullanım örneği
### Kablo okuma ve değiştirme
## Veri yazma
Bir değişkeni `get` ile okuyabilir, `set` ile değiştirebilirsiniz. `get` fonksiyonu veriyi olduğu gibi döner. `set` fonksiyonuna bir fonksiyon verirseniz hemen çalıştıracak ve return edilen veriyi kabul edecektir
Tanım
```typescript
declare function é(name: string, value: any): void;
```
### Basit
`name` ve `value` değerlerini `é` fonksiyonuna aktardığınızda bu ismi ve değeri global olarak depolar. Bu değeri istediğiniz herhangi bir noktada kullanabilirsiniz
```javascript
// Yeni kablo oluşturun
let currency = é(+0.00);
// İlk argümanı isim ve ikinci argümanı değer olarak alır ve depolar
é('name', 'John');
é('surname', 'Carter');
é('age', '34');
```
// Değerini okuyun
currency.get(); // --> 0
### Gelişmiş
// Değerini değiştirin
currency.set(5);
`name` alanına verdiğiniz değerler dosya sistemi benzeri dizinlenmiş olarak saklanır.
Teknik olarak `/` ile ayırdığınız her alan için sistem o alan klasör gibi davranır ve son eğik çizgiden sonraki isme o değeri kaydeder. Teknik olarak `name`, `surname` ve `age` değerleri `userform` adı altında tutulur ve izlenir
currency.get(); // --> 5
```javascript
é("userform/name/value", "John");
é("userform/name/required", true);
é("userform/surname/value", "carter");
é("userform/surname/required", true);
é("userform/age/value", "17");
é("userform/age/required", false);
```
// Değerini fonksiyon ile değiştirin
currency.set(oldValue => {
// Eski verinin üzerine 20 ekle
return oldValue + 20;
## Veri okuma
Tanım
```typescript
declare function é(name: string): any;
```
### Basit
Bir değeri okumak için `é` fonksiyonune değerin ismini yazmanız yeterli
```javascript
let name = é("name");
console.log(name) // --> john
```
### Gelişmiş
Sistem yanlızca değerleri döner, dizinleri vermez. Bu yüzden değişkenin tam ismini kullanmanız gerekir
```javascript
é("userform/name/value", "John");
é("userform") // ----> undefined
é("userform/name") // ----> undefined
é("userform/name/value") // ----> "John" !!
é("userform/name/value/other") // ----> undefined
```
## Veriyi izleme
Tanım
```typescript
declare function é(callback: function, variables:string[]): any;
```
`variables` ismindeki veriler değiştiğinde `callback` fonksiyonunu tetikler.
### Basit
```javascript
é("number1", 20);
é("number2", 40);
é("number3", 60);
é("average", 0);
é(() => {
let total = é("number1") + é("number2") + é("number3");
é("average", total / 3);
},["number1","number2","number3"]);
```
Yukarıdaki kodda `number` isimli değişkenlerin herhangi birinin değişmesi durumunda en alttaki fonksiyonumuz çalışarak `average` değerini güncelleyecektir.
Değişiklik izleme tiplemeleride dikkate alan karmaşık bir karşılaştırma algoritması üzerinden yapılır böylecek number, string, boolean değişimleri dışında array ve object tipli değişkenlerin değerleri izlene rekürsif bir şekilde izlenir
```javascript
é("number1", 20);
é("number1", 20); // does nothing, no write, no access
é("number1", 21); // change detected, writed
é("number1", 21); // does nothing, no write, no access
é("object_variable", {var1: true,arr2: [1,2,3,[2]],callback: () => {}});
é("object_variable", {var1: true,arr2: [1,2,3,[2]],callback: () => {}}); // does nothing, no write, no access
é("object_variable", {var1: true,arr2: [1,2,3,[3]],callback: () => {}}); // does nothing, no write, no access
```
### Gelişmiş
Ayrıca dizinlenmiş veriler için iç değerlerin değişimi, üst dizinlerden de izlenebilir
```javascript
é("userform/register/name/value", "John");
é("userform/register/name/required", true);
é("userform/register/surname/value", "carter");
é("userform/register/surname/required", true);
é("userform/register/age/value", "17");
é("userform/register/age/required", false);
é(()=>{
console.log("changed userform -> register")
},["userform/register"]);
```
`userform/register` dizinin altındaki herhangi bir değerin değişmesi durumunda callback çağırılacaktır
## Okuma arabirimi (Middleware)
```typescript
declare namespace é {
function read(name: string, callback: (value:any) => any): void;
}
```
Okuma arabirimi bir değerin saklanmasından sonra `é` fonksiyonu üzerinden geri çağırılması durumunda durumunda devreye girer. Bazı verileri saklandığı gibi değilde, farklı bir formatta almak istediğinizde kullanışlıdır. Nesneye yönelik dillerde getter gibidir **ancak burda değişkenin bulunması zorunlu değildir**, önce getter daha sonra değer belirlenebilir
```javascript
é.read("code",usernameValue => {
return `CODE#` + usernameValue;
});
currency.get(); // --> 25
é("code", 117);
console.log(é("code")) // ---> CODE#117
```
## Değişimler
Bir kablonun değerinin değiştiğini `watch` özelliğiyle dinleyebilirsiniz veya birden fazla kablonun değişimini aynı anda dinlemek için `é.watch` fonksiyonunu kullanabilirsiniz
```javascript
let a = é(0);
let b = é(0);
a.watch(()=>{
// a kablosu değiştiğinde burası çalışacak
})
b.watch(()=>{
// b kablosu değiştiğinde burası çalışacak
})
é.watch(()=>{
// a veya b değiştiğinde burası çalışacak
},[a, b])
```
Örnek
```javascript
let toplayan = é(0);
let toplanan = é(0);
let sonuc = é(0);
é.watch(()=>{
sonuc.set( toplayan.get() + toplanan.get() )
},[toplayan, toplanan])
é("person", null);
é("person/name", "John");
é("person/surname", "Carter");
toplayan.watch(()=>{
console.log("Toplayan: ",toplayan.get())
})
toplanan.watch(()=>{
console.log("Toplanan: ",toplanan.get())
})
sonuc.watch(()=>{
console.log("Değişen Sonuç: ",sonuc.get())
é.read('person',function(){
return é("person/name") + " " + é("person/surname");
})
toplayan.set(7);
toplanan.set(2);
console.log(é("person")) // ---> "John Carter"
console.log("Sonuç",sonuc.get())
é("person/name", "Bell");
console.log(é("person")) // ---> "Bell Carter"
```
Konsol çıktısı aşağıdaki gibi olacaktır
```
Toplayan: 0
Toplanan: 0
Değişen Sonuç: 0
Toplayan: 7
Değişen Sonuç: 7
Toplanan: 2
Değişen Sonuç: 9
Sonuç: 9
## Yazma arabirimi (Middleware)
```typescript
declare namespace é {
function write(name: string, callback: (value:any) => any): void;
}
```
## Ara katmanlar
Yazma arabirimi bir verinin kaydedilmesi sırasında devreye girer ve verilen verinin depolanmadan önce ek kontroller yapılmasına değiştirilmesini sağlamak için kullanılabilir. Nesneye yönelik dillerde setter gibidir **ancak burda değişkenin bulunması zorunlu değildir**, önce setter daha sonra değer belirlenebilir
### Okuma ara katmanı
Bazen orjinal değişkeni değiştirmeden her seferinde daha gelişmiş bir veri tipiyle okumak istersiniz
Bir değerin tipinin değiştirilmesi
```javascript
// Yeni kablo oluşturun
let user = é({
name: "John",
surname: "Wattson"
é("integer_value", 217.7525);
é.write("integer_value",usernameValue => {
return parseInt(usernameValue);
});
// Normalde okuma katmanı eklemezseniz veri düz bir şekilde verecektir
user.get(); // --> {"name":"John",surname:"Wattson"}
console.log(é("integer_value")) // ---> 217
// Verinin nasıl okunacağını belirleyin
user.readLayer((value, update)=>{
// value: orjinal veridir
// değiştirmek istediğinizde update fonksiyonunda verirsiniz
update(
`İsim: ${value.name}, soyisim: ${value.surname}`
)
})
// `get` fonksiyonu veriyi nasıl okuyacağını readLayer fonksiyonunda öğrenecektir
user.get(); // --> "İsim: John, soyisim: Wattson"
```
### Yazma ara katmanı
Bazen orjinal değişkeni değiştirmeden her seferinde daha gelişmiş bir veri tipiyle okumak istersiniz
Bir listenin içerisindeki null verilerinin çıkarılarak saklanması
```javascript
// Yeni kablo oluşturun
let user = é([2,4,6,8,10]);
user.writeLayer((value, update)=>{
// Sadece çift sayıları alıyoruz
update(
value.filter(e => e % 2 == 0)
)
})
user.writeLayer((value, update)=>{
// Verinin sadece ilk 5 öğesini alıyoruz
update(
value.slice(0, 5)
)
})
// Orjinal veri
user.get(); // --> [2,4,6,8,10]
// set fonksiyonumuz gerçek veriyi değiştirmeden önce yukarıdaki iki yazma katmanını çalıştırıyorlar
// set fonksiyonumuz artık sadece ilk 5 çift sayıyı kaydediyor
user.set([10,11,12,13,14,15,16,17,18,19,20,21,22,23])
user.get(); // --> [10,12,14,16,18]
```
## Fark bulma algoritması
WiréS eşitlenmek istenen değeri zaten var olan değer ile karşılaştırarak farklı olup olmadığını algılayan özel bir algoritmaya sahiptir. Bu şekilde tekrarlı eşitlemelerde veya bellekte aynı şekilde tutulan veriler için yazma işlemi gerçekleştirmez
### Basit değişkenler
```javascript
let variable = é(0);
variable.set(false); // --> Değişecek
variable.set(7); // --> Değişecek
variable.set(7); // --> Değişmeyecek
variable.set(7 + 0); // --> Değişmeyecek
variable.set(-7); // --> Değişecek
```
### Objeler ve listeler
```javascript
let variable = é([1,2,3]);
variable.set([1,2,3,4]) // --> Değişecek
variable.set([1,2,3,4]) // --> Değişmeyecek
variable.set([1,0,3,4]) // --> Değişecek
variable.set() // --> Değişecek (undefined olarak)
variable.set(null) // --> Değişmeyecek
let variable = é({
name: null,
surname: "Wattson",
numbers: [1,2,3,4]
é.write("numberList",usernameValue => {
return usernameValue.filter(e => e != null)
});
// İsmini null iken John olarak değiştiği için değişecek
variable.set(oldValue => {
return {
name: "John",
surname: "Wattson",
numbers: [1,2,3,4]
é("numberList", [1, null, 7, 63, 74, null, 7, 15]);
console.log(é("numberList")) // ---> [1, 7, 63, 74, 7, 15]
```
> [!WARNING] Dikkat
> getter ve setter metotları belirlendiği andan itibaren devreye girer, kendinden önce ve sonraki depolama veya okuma operasyonlarına karışmaz
## Veriyi kısıtlama
```typescript
declare namespace é {
function typedLock(name: string, {instance?:Function, type?:string, nullable?:boolean}): void;
}
```
### Veri tipini kısıtlama
`typedLock` fonksiyonu belirlenen `name` değeri için tipleme ve değersizlik (nullable) kısıtlamalarını uygular. Bu tipleme isteğe bağlı olarak değiştirilebilir veya tekrar belirlenebilir
```javascript
é("person/name", "John");
é.typedLock("person/name",{
type: "string",
nullable: false
});
é("person/name", null); // ----> Uncaught Error: value is not typeof number
é("person/name", 27); /// -----> Uncaught Error: value is not typeof string
```
### Veri tipi kısıtını kaldırma
`typedLock` fonksiyonu belirlenen `name` değeri için tipleme ve değersizlik (nullable) kısıtlama kurallarını siler
```javascript
é("person/name", "John");
é.typedLock("person/name",{
type: "string",
nullable: false
});
é("person/name", null); // ----> Uncaught Error: value is not typeof number
é("person/name", 27); /// -----> Uncaught Error: value is not typeof string
é.typedUnLock("person/name");
é("person/name", null); // ----> success, allowed!
é("person/name", 27); /// -----> success, allowed !
```
## Seti temizleme
WiréJS'de veriler ile arabirim ve veri tipi kısıtlayıcıları vs. ayrı ayrı tutulur. Veri bellek içerisinden silinse veya null değeri verilse dahil dinleyiciler aktif olarak çalışır.
```typescript
declare namespace é {
function delete(name: string): void;
}
```
Verilen veri isminden tüm verileri, kısıtlayıcıları, okuma ve yazma arabirimlerini temizler.
```javascript
é("name", "john");
// use updated callback
é(()=> console.log("name changed"),['name']);
// use setter callback
é.read('name',value => `--${value}--`);
é('name'); // ---> "--john---"
é('name','Bell'); // ---> name changed
// Delete name value, remove updated callbacks and setter callbacks
é.delete("name");
é('name'); // ---> undefined
é('name','Kyra'); // ---> do nothing....
é('name','Oliver'); // ---> do nothing....
é('name'); // ---> "Oliver"
```
## Detaylar
### DOM ile birlikte kullanma
```javascript
let nameInput = document.createElement("input");
// input değiştiğinde bildir
nameInput.oninput = () => é('name', nameInput.value);
é.read('name/valid',value => value.trim() != "");
let surnameInput = document.createElement("input");
// input değiştiğinde bildir
surnameInput.oninput = () => é('surname', surnameInput.value);
é.read('surname/valid',value => value.trim() != "");
let errorSpan = document.createElement("span");
é(() => surnameInput.value = é('message'),['message']);
let submitButton = document.createElement("button");
submitButton.disabled = true;
// submit_isActive değeri değiştiğinde disabled özelliğini değiştir
é(() => {
submitButton.disabled = !é("submit_isActive")
},["submit_isActive"]);
é(()=>{
if(é("name/valid") && é("surname/valid")){
é('message', "Form's input is valid")
é("submit_isActive", true)
}else{
é('message', "Please fill all inputs")
é("submit_isActive", false)
}
})
// isim ve soyisim bir öncekiyle aynı olduğu için hiç bir şey yapılmaz
variable.set({
name: "John",
surname: "Wattson",
numbers: [1,2,3,4]
})
// Ayrıca fark bulmak algoritması sonsuz derinlikte tarama yapıp farkı anlayabilir
variable.set({
name: "John",
surname: "Wattson",
numbers: [1,2,3,4,"Hello !"]
})
},["name","surname"]);
```
### Sonsuz derinlikteki objeler
### Fark algoritmasını değiştirme
Javascriptte bir objenin içindeki bir değer yine kendisini işaret ediyor olabilir, böyle bir durumda objenin içini tarayan algoritma aynı objeyi defalarca taramaması için (Maximum call stack hatası) önlem koyulmuştur
Bazen verilerin değişimini algılamak için tipleri veya verinin içeriği kontrol etmek gereksiz olabilir. Senaryonuzu karmaşık veriler dışında basit primitive veriler üzerine kurguluyorsanız fark algoritmasını basitleştirerek performansı yarı yarıya artırabilirsiniz
```typescript
declare namespace é {
function diff(callback: (oldValue?:any, newValue?:any) => boolean): void;
}
```
Belirli bir verinin farklı bir algoritma kullanılarak karşılaştırılmasını sağlar
Örneğin sizin için basit bir (tipleme korumasız) bir karşılaştırma yetiyor olabilir
```javascript
let infinite = {
one: 1,
two: 2,
three: 3,
onetwothree: undefined
};
// Objenin içine yine kendisini yerleştiriyoruz
infinite.onetwothree = infinite;
let variable = é(infinite);
é("number", 4);
/**
* Aşağıdaki kodda WiréJS neyin değiştiğini anlamak için infinite
* objesinin içine girdiğinde, içine girdiği objeleri bellekte
* tutarak aynı objeyi tekrar taramasını engelleyen yapıya
* sahip olur
**/
é("number", 5); // ----> changed
é.diff("number", (oldValue, newValue) => oldValue != newValue);
é("number", 6); // ----> changed !
é("number", 0); // ----> changed !
é("number", false); // ----> no change
é("number", null); // ----> no change
é("number", undefined); // ----> no change
é("number", []); // ----> no change
é("number", 3); // ----> changed !
é("number", 1); // ----> changed !
é("number", true); // ----> no change
// Sonsuz döngüde dönerek [Maximum call stack] hatası vereceğine three değerini başarılı bir şekilde değiştirir
variable.set(oldValue => {
// sadece three değerini 3 ile çarpıyoruz
return {
...oldValue,
three: oldValue.three * 3
}
});
```
Veya ortalaması aynı olan bir listenin eşitlenmesi gereksiz bir durum olabilir
```javascript
é("numberList", [ 0, 1, 2, 3]);
é("numberList", [-1, 1, 2, 3, 4]); // ----> changed !
é.diff("numberList", (oldValue, newValue) => {
let oldValueSum = oldValue.reduce((sum, num) => sum + num, 0) / oldValue.length;
let newValue = newValue.reduce((sum, num) => sum + num, 0) / newValue.length;
return oldValueSum != newValue
});
é("numberList", [2]);
é("numberList", [ 1, 2, 3]); // ----> no change, no write
é("numberList", [ 0, 1, 2, 3, 4]); // ----> no change, no write
é("numberList", [-2, 0, 2, 4, 6]); // ----> no change, no write
é("numberList", [+1, 2, 3, 4, 5]) // ----> changed !
```
Bu karşılaştırmanın sonucunu depolamadığı gibi aynı zamanda dinleyicileride çalıştırmayacaktır

691
wire.js
View File

@ -1,331 +1,442 @@
/*
Flags
0x01 01 watch enabled
0x02 02 read layer enabled
0x04 04 write layer enable
0x08 08 initial state
0x10 16 changing state
0x20 32 immediate change
*/
(()=>{
let pipeLine = function(wire){
let _caches = {};
let _cache_version = null;
this.reads = [];
this.writes = [];
this.filters = new Map();
this.read = (fn,pr) => this.reads.push({fn,pr});
this.write = (fn,pr) => this.writes.push({fn,pr});
this.filter = (name, fn) => this.filters.set(name,fn);
this.get = (val,filters = []) => {
let real = val;
if(filters.length)
class É {
map = new Map();
assertThrow(statement, message){
if(statement === false)
{
if(_cache_version !== wire.version())
throw new Error(message);
}
}
checkTyping(locks, value)
{
if(locks)
{
if(locks.nullable === false)
{
_caches = {};
};
for (const name of filters)
{
if(_caches[name])
{
real = _caches[name];
continue;
}
let func = this.filters.get(name);
if(func)
{
func(real, e => {
real = e;
_caches[name] = e;
});
}
};
_cache_version = wire.version();
};
this.assertThrow(typeof value != "undefined","value is not nullable")
}
let fns = this.reads.sort((a,b) => (a?.pr|0) - (b?.pr|0));
for (const { fn } of fns) {
fn( real, e => real = e );
};
return real;
};
this.set = () => {
let fns = this.writes.sort((a,b) => (a?.pr|0) - (b?.pr|0)),
real = val;
for (const { fn } of fns) {
fn( real, e => real = e );
};
return real;
};
}
pipeLine.prototype.read = function(fn,pr){
this.reads.push({fn,pr})
}
pipeLine.prototype.write = function(fn,pr){
this.writes.writes({fn,pr})
}
pipeLine.prototype.get = function(val){
let fns = this.reads.sort((a,b) => (a?.pr|0) - (b?.pr|0)),
real = val;
for (const { fn } of fns) {
fn( real, e => real = e );
};
return real;
}
pipeLine.prototype.set = function(val){
let fns = this.writes.sort((a,b) => (a?.pr|0) - (b?.pr|0)),
real = val;
for (const { fn } of fns) {
fn( real, e => real = e );
};
return real;
}
function é(defaultValue)
{
if(this instanceof é)
{
this.value = é.extract(defaultValue);
this._version = 0;
this.effects = [];
this.piping = new pipeLine(this);
this.flag = 8;
}else{
return new é(defaultValue)
}
}
é.fingerPrint = Symbol("wire");
é.isWire = n => n?.fp == é.fingerPrint;
é.extract = (e,v) => {
if(typeof e=='function')
{
return é.freeze(e(v))
}else{
return é.freeze(e);
}
};
é.freeze = a => {
if(Object.isFrozen(a))
{
return a;
}
switch(typeof a)
{
case "object":{
if(Array.isArray(a))
if(locks.instance)
{
let k = [...a];
for (let name = 0; name < k.length; name++) {
let value = k[name];
if(typeof value == "object")
{
k[name]=é.freeze(value);
}
};
Object.freeze(k);
return k;
}else{
let k = Object.assign({}, a);
for (const [name, value] of Object.entries(k)) {
if(typeof value == "object")
{
k[name]=é.freeze(value);
}
};
Object.freeze(k);
return k;
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)
}
}
default: return a;
};
};
é.isSame = (a,b) => !é.diff(a,b);
é.watch = (fn, assoc) => {
for (const wireVar of assoc)
{
if(é.isWire(wireVar))
{
wireVar.watch(fn);
}
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;
}
}).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;
}
}).filter(e => e !== void 0);
}
}
};
é.diff = (a,b,c) => {
let cursoryDiffResult = cDiff(a,b);
if(cursoryDiffResult == cDiff.adiff)
{
return adiff(a,b,c||[])
}else{
return cursoryDiffResult == cDiff.some ? false : true;
deleteAttribute(
path,
attributeName = "value"
){
let npath = this.normalizePath(path);
this.map.get(npath).delete(attributeName)
}
removeNode(path){
let npath = this.normalizePath(path);
this.map.delete(npath);
}
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)
{
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';
}
}else{
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)
}
}else{
let valueMap = new Map();
valueMap.set(attributeName, typeof value == "function" ? value() : value);
this.map.set(npath, valueMap);
process = 'create';
}
return {npath,process};
}
mapHierachy(npath, mapCallback){
let npaths = npath.split('/');
let t = [], current = "";
for (const path of npaths)
{
t.push(path);
current = t.join('/');
if(this.map.has(current))
{
mapCallback(this.map.get(current))
};
}
}
createListenerForPath(path, callback){
let npath = this.normalizePath(path);
this.setAttribute(
npath,
'changelistener',
callbacksOrNull => (callbacksOrNull ? callbacksOrNull.concat([callback]) : [callback])
);
}
getValue(path, attribute = "value"){
let npath = this.normalizePath(path);
let staticValue = this.map.get(npath)?.get(attribute);
if(attribute == 'value')
{
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;
}
}
normalizePath(str){
if(!str)
{
return ':moment:'
}else if(!(typeof str == "string")){
throw new Error("É is not valid path ")
}else{
if(str.indexOf('/') == -1)
{
return str;
}
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;
}
}
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)
{
this.setAttribute(
npath,
'locks',
obj => ({
...(obj || {}),
instance: options.instance
}),
true
);
}
if(options.type !== void 0)
{
this.setAttribute(
npath,
'locks',
obj => ({
...(obj || {}),
type: options.type
}),
true
);
}
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
);
}
}
const adiff = (a,b,c) => {
if(c.includes(a) && c.includes(b))
function é(arg1, arg2)
{
if("string" == typeof arg1)
{
console.error("Circular object detected !")
return cObjectDiff(a,b);
if(arg2 === void 0)
{
return é.current.getValue(arg1)
}
if(["bigint","boolean"/*,"function"*/,"number","object","symbol","string"].includes(typeof arg2) || arg2 === null)
{
return é.current.setValue(arg1, arg2)
}
/*
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);
}
}
}
é.current = new É();
é.write = (...args) => é.current.createListenerForWrite(...args);
é.read = (...args) => é.current.createListenerForRead(...args);
é.diff = (...args) => é.current.changeDifferenceAlgorithm(...args);
é.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)
}
};
é.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;
}
};
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)
{
if (typea != typeb) {
return true;
}
if(typea==0)
{
if(a.length != b.length)
{
return true
if (typea == 0) {
if (a.length != b.length) {
return true;
}
for(let k = 0; k < a.length; k++)
{
if(é.diff(a[k], b[k]))
{
return true
for (let k = 0; k < a.length; k++) {
if (ediffEpsilon(a[k], b[k])) {
return true;
}
}
return false
return false;
}
if(a instanceof Object)
{
if(Object.keys(a).length != Object.keys(b).length)
{
return true
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(é.diff(a[key], b[key], c))
{
return true
for (const key in a) {
if (Object.hasOwnProperty.call(a, key)) {
if (ediffEpsilon(a[key], b[key], c)) {
return true;
}
}
}
return false
return false;
}
}
const cArrDiff = (a,b) => {
return a.length != b.length || !a.every((v,i) => a[i] === v)
};
const cObjectDiff = (a,b) => {
return cArrDiff(
function arrayLengthPointerDiff(a, b){
return a.length != b.length || !a.every((v, i) => a[i] === v);
};
function objectKeyDiff(a, b){
return arrayLengthPointerDiff(
Object.keys(a),
Object.keys(b)
);
};
const cDiff = (a,b) => {
switch(typeof a)
{
case "undefined":
case "function":{
return typeof a == typeof b ? cDiff.some : cDiff.different
}
case "symbol":
case "bigint":
case "boolean":
case "number":
case "string":{
return a === b ? cDiff.some : cDiff.different;
}
case "object":{
return cDiff.adiff;
}
};
}
cDiff.adiff = -1;
cDiff.some = 0;
cDiff.different = 1;
é.prototype.fp = é.fingerPrint;
é.prototype.diff = é.diff;
é.prototype.get = function(...filters){
if(this.flag & 2)
function typedDiff(a, b){
if(typeof a == typeof b)
{
return this.piping.get(this.value,filters)
}else{
return this.value
}
};
é.prototype.set = function(newValue){
if(this.flag & 16)
{
return;
}
newValue = é.extract(newValue, this.value);
if(this.flag & 4)
{
newValue = this.piping.set(newValue)
};
if(this.diff(newValue,this.value))
{
this.flag = this.flag | 16;
if(this.flag & 1)
{
schedule((()=>{
this.effects.filter(e => e.o).forEach(k => k())
}));
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;
}
}
this.value = newValue;
this._version++;
if(this.flag & 1)
{
schedule((()=>{
this.effects.forEach(e => e.f());
}));
}
this.flag = this.flag ^ 16;
}
};
const schedule = function(fn){
schedule.jobs.push(fn)
if(!schedule.executing)
{
requestAnimationFrame(()=>{
for (const fn of schedule.jobs) {
try{
fn()
}catch(e){
console.error(e)
}
};
schedule.jobs=[];
})
}
};
schedule.executing = false;
schedule.jobs = [];
é.prototype.watch = function(fn){
this.flag = this.flag | 1;
let k = {
f:fn,
o:undefined
};
requestAnimationFrame(()=> {
k.o = fn();
})
this.effects.push(k);
};
é.prototype.version = function(){
return this._version;
}
é.prototype.equalTo = function(value){
return é.isSame(value, this.value)
}
é.prototype.read = function(argument1, argument2, argument3){
this.flag = this.flag | 2;
if(typeof argument1=="string")
{
this.piping.filter(argument1, argument2, argument3);
}else{
this.piping.read(argument1, argument2)
return typedDiff.different;
}
}
é.prototype.write = function(value, pr){
this.flag = this.flag | 4;
this.piping.write(value, pr)
}
};
typedDiff.arrayDiff = -1;
typedDiff.some = 0;
typedDiff.different = 1;
try{
module.exports = é;