From 7381d3ff2dfe62601be2f404b47889d23c9a692d Mon Sep 17 00:00:00 2001 From: Abdussamed Date: Tue, 8 Oct 2024 23:41:50 +0300 Subject: [PATCH] v2 alpha released --- readme.md | 549 ++++++++++++++++++++++++++++--------------- wire.js | 691 +++++++++++++++++++++++++++++++----------------------- 2 files changed, 765 insertions(+), 475 deletions(-) diff --git a/readme.md b/readme.md index 38dfcb4..b0cfcfe 100755 --- a/readme.md +++ b/readme.md @@ -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] -}) +},["name","surname"]); +``` -// Ayrıca fark bulmak algoritması sonsuz derinlikte tarama yapıp farkı anlayabilir -variable.set({ - name: "John", - surname: "Wattson", - numbers: [1,2,3,4,"Hello !"] -}) +### Fark algoritmasını değiştirme + + +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 + +é("number", 4); + +é("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 derinlikteki objeler - -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 +Veya ortalaması aynı olan bir listenin eşitlenmesi gereksiz bir durum 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); +é("numberList", [ 0, 1, 2, 3]); +é("numberList", [-1, 1, 2, 3, 4]); // ----> changed ! -/** - * 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 - **/ - -// 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 - } +é.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 }); -``` \ No newline at end of file + +é("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 \ No newline at end of file diff --git a/wire.js b/wire.js index e6f5326..ae9d9c3 100755 --- a/wire.js +++ b/wire.js @@ -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) + + function typedDiff(a, b){ + if(typeof a == typeof b) { - case "undefined": - case "function":{ - return typeof a == typeof b ? cDiff.some : cDiff.different + 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; + } } - 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) - { - return this.piping.get(this.value,filters) }else{ - return this.value + return typedDiff.different; } }; - é.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()) - })); - } - 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) - } - } - é.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 = é;