olova 2.0.4 → 2.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/olova.js +1 -291
  2. package/package.json +1 -1
package/dist/olova.js CHANGED
@@ -1,291 +1 @@
1
- const memoize = (e) => {
2
- const t = new Map();
3
- return (...n) => {
4
- const r = JSON.stringify(n);
5
- return t.has(r) || t.set(r, e(...n)), t.get(r);
6
- };
7
- };
8
- let currentObserver = null;
9
- function diffProps(e = {}, t = {}, n) {
10
- void 0 !== t.className && ((t.class = t.className), delete t.className),
11
- void 0 !== e.className && ((e.class = e.className), delete e.className);
12
- for (const [r, o] of Object.entries(e))
13
- r in t ||
14
- (r.startsWith("on")
15
- ? n.removeEventListener(r.slice(2).toLowerCase(), o)
16
- : "style" === r
17
- ? (n.style.cssText = "")
18
- : n.removeAttribute(r));
19
- for (const [r, o] of Object.entries(t))
20
- if (null != o && e[r] !== o)
21
- if (r.startsWith("on")) {
22
- const t = r.slice(2).toLowerCase();
23
- e[r] && n.removeEventListener(t, e[r]), n.addEventListener(t, o);
24
- } else if ("ref" === r)
25
- o && "object" == typeof o && "current" in o
26
- ? (o.current = n)
27
- : "function" == typeof o && o(n);
28
- else if ("style" === r) {
29
- const e = n.style;
30
- "object" == typeof o ? Object.assign(e, o) : (e.cssText = o);
31
- } else n.setAttribute(r, o);
32
- }
33
- const diffChildren = memoize((e, t, n) => {
34
- const r = Array.isArray(e) ? e.flat() : [e],
35
- o = Array.isArray(t) ? t.flat() : [t],
36
- s = Math.max(r.length, o.length);
37
- for (let e = 0; e < s; e++) {
38
- const t = r[e],
39
- s = o[e];
40
- if (!t || s)
41
- if (t || !s) {
42
- if (t instanceof Node && s instanceof Node)
43
- t.nodeType !== s.nodeType || t.nodeName !== s.nodeName
44
- ? n.replaceChild(s, t)
45
- : (diffProps(t.attributes, s.attributes, t),
46
- diffChildren(
47
- Array.from(t.childNodes),
48
- Array.from(s.childNodes),
49
- t
50
- ));
51
- else if (t !== s) {
52
- const t = n.childNodes[e];
53
- t && (t.textContent = String(s));
54
- }
55
- } else {
56
- const e = s instanceof Node ? s : document.createTextNode(String(s));
57
- n.appendChild(e);
58
- }
59
- else t instanceof Node && n.removeChild(t);
60
- }
61
- });
62
- class Signal {
63
- constructor(e) {
64
- (this._value = e),
65
- (this.observers = new Map()),
66
- (this.pending = new Set()),
67
- (this.isBatching = !1);
68
- }
69
- get value() {
70
- return (
71
- currentObserver &&
72
- (this.observers.has("_root") || this.observers.set("_root", new Set()),
73
- this.observers.get("_root").add(currentObserver)),
74
- this._value
75
- );
76
- }
77
- set value(e) {
78
- if (this._value === e) return;
79
- const t = this._value;
80
- if (((this._value = e), this.observers.has("_root")))
81
- for (const e of this.observers.get("_root")) this.pending.add(e);
82
- if ("object" == typeof t && "object" == typeof e) {
83
- const n = new Set([...Object.keys(t), ...Object.keys(e)]);
84
- for (const r of n)
85
- if (t[r] !== e[r] && this.observers.has(r))
86
- for (const e of this.observers.get(r)) this.pending.add(e);
87
- }
88
- this.batchUpdate();
89
- }
90
- batchUpdate() {
91
- this.isBatching ||
92
- ((this.isBatching = !0),
93
- Promise.resolve().then(() => {
94
- this.pending.forEach((e) => e()),
95
- this.pending.clear(),
96
- (this.isBatching = !1);
97
- }));
98
- }
99
- observe(e, t) {
100
- this.observers.has(e) || this.observers.set(e, new Set()),
101
- this.observers.get(e).add(t);
102
- }
103
- unobserve(e, t) {
104
- this.observers.has(e) && this.observers.get(e).delete(t);
105
- }
106
- }
107
- function $signal(e) {
108
- const t = new Signal(e),
109
- n = () => t.value;
110
- return (
111
- (n.toString = () => t.value),
112
- (n.observe = (e, n) => t.observe(e, n)),
113
- (n.unobserve = (e, n) => t.unobserve(e, n)),
114
- [
115
- n,
116
- (e) => {
117
- t.value = "function" == typeof e ? e(t.value) : e;
118
- },
119
- ]
120
- );
121
- }
122
- function $effect(e, t) {
123
- let n,
124
- r = !0,
125
- o = null;
126
- const s = () => {
127
- "function" == typeof n && (n(), (n = void 0)), (currentObserver = o);
128
- const t = e();
129
- (currentObserver = null), "function" == typeof t && (n = t);
130
- };
131
- return (
132
- (o = () => {
133
- if (r || !t) return s(), void (r = !1);
134
- Array.isArray(t) && 0 === t.length ? r && (s(), (r = !1)) : s();
135
- }),
136
- o(),
137
- () => {
138
- n && n();
139
- }
140
- );
141
- }
142
- function $memo(e) {
143
- const [t, n] = $signal(e());
144
- return $effect(() => n(e())), t;
145
- }
146
- function $ref(e) {
147
- const [t, n] = $signal({
148
- current: e,
149
- toString() {
150
- return this.current;
151
- },
152
- valueOf() {
153
- return this.current;
154
- },
155
- });
156
- return {
157
- get current() {
158
- return t().current;
159
- },
160
- set current(e) {
161
- n((t) => (t.current === e ? t : { ...t, current: e }));
162
- },
163
- toString() {
164
- return this.current.toString();
165
- },
166
- valueOf() {
167
- return this.current;
168
- },
169
- };
170
- }
171
- function h(e, t, ...n) {
172
- if (e === Fragment || Array.isArray(e)) {
173
- const r = document.createDocumentFragment(),
174
- o = e === Fragment ? t?.children || n : e;
175
- return (
176
- Array.isArray(o) &&
177
- o.flat().forEach((e) => {
178
- null != e &&
179
- r.appendChild(
180
- e instanceof Node ? e : document.createTextNode(String(e))
181
- );
182
- }),
183
- r
184
- );
185
- }
186
- if (!e) return null;
187
- const r = "function" == typeof e ? e(t) : document.createElement(e);
188
- if (("string" == typeof e && r && t && diffProps({}, t, r), n.length)) {
189
- const e = document.createDocumentFragment(),
190
- t = (n) => {
191
- if (null != n)
192
- if ("function" == typeof n) {
193
- const t = document.createTextNode("");
194
- $effect(() => {
195
- const e = n();
196
- if (Array.isArray(e)) {
197
- const n = document.createDocumentFragment();
198
- e.forEach((e) => {
199
- n.appendChild(
200
- e instanceof Node ? e : document.createTextNode(String(e))
201
- );
202
- }),
203
- t.parentNode && t.parentNode.replaceChild(n, t);
204
- } else t.textContent = String(e);
205
- }),
206
- e.appendChild(t);
207
- } else
208
- n instanceof Node
209
- ? e.appendChild(n)
210
- : Array.isArray(n)
211
- ? n.flat().forEach(t)
212
- : e.appendChild(document.createTextNode(String(n)));
213
- };
214
- n.forEach(t), r.appendChild(e);
215
- }
216
- return r;
217
- }
218
- const Component = (e, t) => {
219
- if ("function" != typeof e)
220
- throw new Error("Invalid Component: must be a function");
221
- return e(t);
222
- },
223
- Fragment = (e) => e.children;
224
- window.Fragment = Fragment;
225
- const Olova = {
226
- render(e, t) {
227
- const n = "function" == typeof e ? e() : e;
228
- return (
229
- t.firstChild ? diffChildren([t.firstChild], [n], t) : t.appendChild(n),
230
- n
231
- );
232
- },
233
- mount(e, t) {
234
- return this.render(e, t);
235
- },
236
- unmount(e) {
237
- e.innerHTML = "";
238
- },
239
- Fragment: Fragment,
240
- },
241
- contextRegistry = new Map();
242
- function $context(e) {
243
- const t = Symbol("context");
244
- return (
245
- contextRegistry.set(t, e),
246
- {
247
- Provider({ value: e, children: n }) {
248
- const r = contextRegistry.get(t);
249
- contextRegistry.set(t, e);
250
- const o = n;
251
- return contextRegistry.set(t, r), o;
252
- },
253
- use() {
254
- const n = contextRegistry.get(t);
255
- if (void 0 === n && void 0 === e)
256
- throw new Error("Context used outside of Provider");
257
- return n ?? e;
258
- },
259
- }
260
- );
261
- }
262
- function $callback(e, t) {
263
- const [n, r] = $signal(() => ({
264
- fn: e,
265
- deps: t,
266
- memoized: (...t) => e(...t),
267
- }));
268
- return (
269
- $effect(() => {
270
- const o = n();
271
- t &&
272
- ((o.deps &&
273
- t.length === o.deps.length &&
274
- !t.some((e, t) => e !== o.deps[t])) ||
275
- r({ fn: e, deps: t, memoized: (...t) => e(...t) }));
276
- }),
277
- () => n().memoized
278
- );
279
- }
280
- export {
281
- $signal,
282
- $effect,
283
- $memo,
284
- $ref,
285
- $context,
286
- $callback,
287
- Component,
288
- h,
289
- Fragment,
290
- };
291
- export default Olova;
1
+ const memoize=(e,{resolver:t=((...e)=>JSON.stringify(e))}={})=>{const r=new Map;return(...n)=>{const o=t(...n);return r.has(o)||r.set(o,e(...n)),r.get(o)}};let currentObserver=null,batchQueue=new Set,isBatching=!1;function batch(e){isBatching=!0,e(),isBatching=!1,batchQueue.forEach((e=>e())),batchQueue.clear()}const perf={enabled:!0,entries:{},wrap:(e,t)=>function(...r){if(!this.enabled)return e(...r);const n=performance.now(),o=e(...r);return this.entries[t]=(this.entries[t]||0)+(performance.now()-n),o}};function ErrorBoundary({fallback:e,children:t}){const[r,n]=$signal(null),o=t=>(console.error("Error caught by boundary:",t),n(t),e);try{if(r())return e;try{return t}catch(e){return o(e)}}catch(e){return o(e)}}function diffProps(e={},t={},r){if(!(r&&r instanceof Element))throw new TypeError("diffProps requires a valid DOM element");try{void 0!==t.className&&(t.class=t.className,delete t.className),void 0!==e.className&&(e.class=e.className,delete e.className);const n={remove:[],set:[],events:[],styles:[]};for(const[r,o]of Object.entries(e))r in t||n.remove.push([r,o]);for(const[r,o]of Object.entries(t))null!=o&&e[r]!==o&&(r.startsWith("on")?n.events.push([r,o,e[r]]):"style"===r?n.styles.push(o):n.set.push([r,o]));n.remove.forEach((([e,t])=>{e.startsWith("on")?r.removeEventListener(e.slice(2).toLowerCase(),t):"style"===e?r.style.cssText="":r.removeAttribute(e)})),n.events.forEach((([e,t,n])=>{if("function"!=typeof t)throw new TypeError(`Event handler for ${e} must be a function`);const o=e.slice(2).toLowerCase();n&&r.removeEventListener(o,n),r.addEventListener(o,t)})),n.styles.forEach((e=>{if("object"==typeof e)Object.assign(r.style,e);else{if("string"!=typeof e)throw new TypeError("style must be an object or string");r.style.cssText=e}})),n.set.forEach((([e,t])=>{if("ref"===e)if(t&&"object"==typeof t&&"current"in t)t.current=r;else{if("function"!=typeof t)throw new TypeError("ref must be a ref object or function");t(r)}else"boolean"==typeof t&&e in r?r[e]=t:r.setAttribute(e,t)}))}catch(e){throw new Error(`Failed to diff props: ${e.message}`)}}const diffChildren=perf.wrap(memoize(((e,t,r)=>{if(!(r&&r instanceof Node))throw new TypeError("diffChildren requires a valid parent node");try{const n=Array.isArray(e)?e.flat():[e],o=Array.isArray(t)?t.flat():[t],s=new Map,i=new Map,a=new Set,c=[];n.forEach(((e,t)=>{e?.key&&s.set(e.key,{node:e,index:t,originalIndex:t})})),o.forEach(((e,t)=>{e?.key&&(a.add(e.key),i.set(e.key,{node:e,index:t,originalIndex:s.get(e.key)?.originalIndex??-1}))})),n.forEach(((e,t)=>{e?.key&&!a.has(e.key)&&r.removeChild(e)}));let l=0;o.forEach(((e,t)=>{const r=e?.key;if(!r)return;const n=s.get(r);i.get(r);n?(n.index<l&&c.push({node:n.node,index:t,type:"move"}),l=Math.max(l,n.index)):c.push({node:e,index:t,type:"insert"})})),c.sort(((e,t)=>e.index-t.index)),c.forEach((({node:e,index:t,type:n})=>{if("move"===n)r.insertBefore(e,r.childNodes[t]||null);else{const n=e instanceof Node?e:document.createTextNode(String(e));r.insertBefore(n,r.childNodes[t]||null)}}));for(let e=0;e<o.length;e++){const t=o[e],n=r.childNodes[e];if(n)if(t){if(n instanceof Element&&t instanceof Element)n.nodeType!==t.nodeType||n.nodeName!==t.nodeName?r.replaceChild(t,n):(diffProps(n.attributes,t.attributes,n),diffChildren(Array.from(n.childNodes),Array.from(t.childNodes),n));else if(n.nodeType===Node.TEXT_NODE&&"object"!=typeof t)n.textContent=String(t);else if(n!==t){const e=t instanceof Node?t:document.createTextNode(String(t));r.replaceChild(e,n)}}else r.removeChild(n);else{const e=t instanceof Node?t:document.createTextNode(String(t));r.appendChild(e)}}for(;r.childNodes.length>o.length;)r.removeChild(r.lastChild)}catch(e){throw new Error(`Failed to diff children: ${e.message}`)}})),"diffChildren");class Signal{constructor(e){this._value=e,this.observers=new Map,this.pending=new Set,this.isBatching=!1,this.isLoading=!1,this.error=null}get value(){return currentObserver&&(this.observers.has("_root")||this.observers.set("_root",new Set),this.observers.get("_root").add(currentObserver)),this._value}set value(e){try{if(this._value===e)return;const t=this._value;if(e instanceof Promise)return this.isLoading=!0,this.error=null,this._value=void 0,this.notifyObservers(),void e.then((e=>(this.isLoading=!1,this._value=e,this.notifyObservers(),e))).catch((e=>{throw this.isLoading=!1,this.error=e,this._value=void 0,this.notifyObservers(),e}));if(this._value=e,this.observers.has("_root")){const e=this.observers.get("_root");for(const t of e)t&&this.pending.add(t)}if("object"==typeof t&&"object"==typeof e){const r=new Set([...Object.keys(t),...Object.keys(e)]);for(const n of r)if(t[n]!==e[n]&&this.observers.has(n))for(const e of this.observers.get(n))this.pending.add(e)}this.batchUpdate()}catch(e){throw new Error(`Failed to update signal value: ${e.message}`)}}notifyObservers(){if(this.observers.has("_root")){const e=this.observers.get("_root");for(const t of e)t&&this.pending.add(t)}this.batchUpdate()}batchUpdate(){isBatching?batchQueue.add((()=>this.runObservers())):this.isBatching||(this.isBatching=!0,Promise.resolve().then((()=>{try{this.pending.forEach((e=>{try{e()}catch(e){console.error(`Observer execution failed: ${e.message}`)}})),this.pending.clear()}catch(e){console.error(`Batch update failed: ${e.message}`)}finally{this.isBatching=!1}})))}runObservers(){try{this.pending.forEach((e=>{try{e()}catch(e){console.error(`Observer execution failed: ${e.message}`)}})),this.pending.clear()}catch(e){console.error(`Run observers failed: ${e.message}`)}}observe(e,t){if("function"!=typeof t)throw new TypeError("Observer must be a function");this.observers.has(e)||this.observers.set(e,new Set),this.observers.get(e).add(t)}unobserve(e,t){this.observers.has(e)&&this.observers.get(e).delete(t)}cleanup(){try{this.observers.clear(),this.pending.clear(),this._value=void 0,this.isLoading=!1,this.error=null}catch(e){throw new Error(`Failed to cleanup signal: ${e.message}`)}}}function $signal(e,t={}){if(t&&"object"!=typeof t)throw new TypeError("Options must be an object");const{equals:r=((e,t)=>e===t),persistence:n,lazy:o=!1}=t;try{const t=new Signal(o?()=>e:"function"==typeof e?e():e),s=()=>(o&&!t.initialized&&(t.value="function"==typeof e?e():e,t.initialized=!0),t.value);s.loading=()=>t.isLoading,s.error=()=>t.error,s.peek=()=>t._value,s.toString=()=>t.value,s.toJSON=()=>t.value,s.derive=(e,r={})=>{if("function"!=typeof e)throw new TypeError("Derive requires a function");const n=$signal((()=>e(t.value)),r);return t.observe("_derived",(()=>n[1](e(t.value)))),n},s.subscribe=(e,r={})=>{if("function"!=typeof e)throw new TypeError("Subscribe requires a callback function");const{immediate:n=!0,once:o=!1,filter:s}=r,i=()=>{const r=t.value;s&&!s(r)||(e({value:r,loading:t.isLoading,error:t.error}),o&&a())},a=()=>t.unobserve("_root",i);return t.observe("_root",i),n&&i(),a},s.observe=(e,r)=>t.observe(e,r),s.unobserve=(e,r)=>t.unobserve(e,r),s.effect=(e,t={})=>{if("function"!=typeof e)throw new TypeError("Effect requires a function");let r;return s.subscribe((({value:t,loading:n,error:o})=>{r&&r(),r=e(t,{loading:n,error:o})}),t)};const i=e=>{try{return"function"==typeof e?i(e(t.value)):e instanceof Promise?(t.isLoading=!0,t.error=null,e.then((e=>(n&&n.save(e),t.isLoading=!1,t.value=e,e))).catch((e=>{throw t.isLoading=!1,t.error=e,e}))):(r(t.value,e)||(n&&n.save(e),t.value=e),t.value)}catch(e){throw new Error(`Failed to set signal value: ${e.message}`)}};if(i.reset=()=>{try{t.value="function"==typeof e?e():e,n&&n.remove()}catch(e){throw new Error(`Failed to reset signal: ${e.message}`)}},i.transaction=e=>{if("function"!=typeof e)throw new TypeError("Transaction requires a function");const r=t.value;try{t.isBatching=!0;const n=e(r);return t.value=n,n}catch(e){throw t.value=r,e}finally{t.isBatching=!1,t.batchUpdate()}},i.batch=e=>{if(!Array.isArray(e))throw new TypeError("Batch updates must be an array");return i.transaction((()=>e.reduce(((e,t)=>"function"==typeof t?t(e):t),t.value)))},n)try{const e=n.load();void 0!==e&&(t.value=e)}catch(e){console.error(`Failed to load persisted value: ${e.message}`)}return"development"===process.env.NODE_ENV&&(s.debug={listeners:()=>[...t.observers.entries()],dependencies:()=>t.dependencies,trace:()=>console.log("Signal trace:",{value:t.value,loading:t.isLoading,error:t.error,observers:t.observers})}),[s,i]}catch(e){throw new Error(`Failed to create signal: ${e.message}`)}}function createPersistence(e,t=localStorage){if(!e||"string"!=typeof e)throw new TypeError("Persistence requires a valid string key");return{save:r=>{try{t.setItem(e,JSON.stringify(r))}catch(e){throw new Error(`Failed to save to storage: ${e.message}`)}},load:()=>{try{return JSON.parse(t.getItem(e))}catch(e){throw new Error(`Failed to load from storage: ${e.message}`)}},remove:()=>{try{t.removeItem(e)}catch(e){throw new Error(`Failed to remove from storage: ${e.message}`)}},listen:t=>{const r=r=>{r.key===e&&t(r.newValue)};return window.addEventListener("storage",r),()=>window.removeEventListener("storage",r)}}}function $effect(e,t){if("function"!=typeof e)throw new TypeError("Effect requires a function");let r,n=!0,o=null;const s=()=>{try{"function"==typeof r&&(r(),r=void 0),currentObserver=o;const t=e();currentObserver=null,"function"==typeof t&&(r=t)}catch(e){throw new Error(`Effect execution failed: ${e.message}`)}};return o=()=>{if(n||!t)return s(),void(n=!1);Array.isArray(t)&&0===t.length?n&&(s(),n=!1):s()},o(),()=>{if(r)try{r()}catch(e){console.error(`Effect cleanup failed: ${e.message}`)}}}function $memo(e){if("function"!=typeof e)throw new TypeError("Memo requires a function");try{const[t,r]=$signal(e());return $effect((()=>r(e()))),t}catch(e){throw new Error(`Failed to create memo: ${e.message}`)}}function $ref(e){try{const[t,r]=$signal({current:e,toString(){return this.current},valueOf(){return this.current}});return{get current(){return t().current},set current(e){r((t=>t.current===e?t:{...t,current:e}))},toString(){return this.current.toString()},valueOf(){return this.current}}}catch(e){throw new Error(`Failed to create ref: ${e.message}`)}}function h(e,t,...r){try{if(e===Fragment||Array.isArray(e)){const n=document.createDocumentFragment(),o=e===Fragment?t?.children||r:e;return Array.isArray(o)&&o.flat().forEach((e=>{null!=e&&n.appendChild(e instanceof Node?e:document.createTextNode(String(e)))})),n}if(!e)return null;const n="function"==typeof e?e(t):document.createElement(e);if("string"==typeof e&&n&&t&&diffProps({},t,n),r.length){const e=document.createDocumentFragment(),t=r=>{if(null!=r)if("function"==typeof r){const t=document.createTextNode("");$effect((()=>{try{const e=r();if(Array.isArray(e)){const r=document.createDocumentFragment();e.forEach((e=>{r.appendChild(e instanceof Node?e:document.createTextNode(String(e)))})),t.parentNode&&t.parentNode.replaceChild(r,t)}else t.textContent=String(e)}catch(e){console.error(`Failed to render dynamic content: ${e.message}`),t.textContent=""}})),e.appendChild(t)}else r instanceof Node?e.appendChild(r):Array.isArray(r)?r.flat().forEach(t):e.appendChild(document.createTextNode(String(r)))};r.forEach(t),n.appendChild(e)}return n}catch(e){throw new Error(`Failed to create element: ${e.message}`)}}const Component=(e,t)=>{if("function"!=typeof e)throw new TypeError("Invalid Component: must be a function");try{return e(t)}catch(e){throw new Error(`Component rendering failed: ${e.message}`)}},Fragment=e=>e.children;window.Fragment=Fragment;const Olova={render(e,t){if(!(t&&t instanceof Element))throw new TypeError("Container must be a valid DOM element");try{const r="function"==typeof e?e():e;return t.firstChild?diffChildren([t.firstChild],[r],t):t.appendChild(r),r}catch(e){throw new Error(`Render failed: ${e.message}`)}},mount(e,t){return this.render(e,t)},unmount(e){if(!(e&&e instanceof Element))throw new TypeError("Container must be a valid DOM element");try{e.innerHTML=""}catch(e){throw new Error(`Unmount failed: ${e.message}`)}},Fragment:Fragment},contextStack=[];function $context(e){const t=Symbol("context");return{Provider:({value:e,children:r})=>{contextStack.push({id:t,value:e});const n=r;return contextStack.pop(),n},use:()=>{const r=contextStack.findLast((e=>e.id===t));return r?.value??e}}}function $callback(e,t){if("function"!=typeof e)throw new TypeError("Callback requires a function");try{const[r,n]=$signal((()=>({fn:e,deps:t,memoized:(...t)=>e(...t)})));return $effect((()=>{const o=r();t&&(o.deps&&t.length===o.deps.length&&!t.some(((e,t)=>e!==o.deps[t]))||n({fn:e,deps:t,memoized:(...t)=>e(...t)}))})),()=>r().memoized}catch(e){throw new Error(`Failed to create callback: ${e.message}`)}}export{$signal,$effect,$memo,$ref,$context,$callback,Component,h,Fragment,ErrorBoundary,batch};export default Olova;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "olova",
3
- "version": "2.0.4",
3
+ "version": "2.0.5",
4
4
  "description": "A lightweight JavaScript framework for building reactive applications.",
5
5
  "main": "dist/olova.js",
6
6
  "keywords": [