@yoltra/core 0.1.0 → 0.3.0
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.
- package/LICENSE +0 -0
- package/README.es.md +49 -34
- package/README.md +45 -32
- package/dist/types/index.d.ts +1 -1
- package/dist/types/store/Store.d.ts +133 -14
- package/dist/types/types.d.ts +134 -14
- package/dist/types/utils/detectChangedProps.d.ts +9 -6
- package/dist/yoltra.cjs.js +5 -2
- package/dist/yoltra.esm.js +547 -360
- package/dist/yoltra.umd.js +5 -2
- package/package.json +15 -16
package/dist/yoltra.umd.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* @yoltra/core v0.
|
|
2
|
+
* @yoltra/core v0.3.0
|
|
3
3
|
* (c) 2026 Manu Ramirez <@pixerael>
|
|
4
4
|
* License: MIT
|
|
5
5
|
* Homepage: https://yoltra.dev
|
|
6
|
+
*
|
|
7
|
+
* This source code is licensed under the MIT license found in the
|
|
8
|
+
* LICENSE file in the root directory of this source tree
|
|
6
9
|
*/
|
|
7
|
-
(function(y,g){typeof exports=="object"&&typeof module<"u"?g(exports):typeof define=="function"&&define.amd?define(["exports"],g):(y=typeof globalThis<"u"?globalThis:y||self,g(y.yoltra={}))})(this,(function(y){"use strict";var $=Object.defineProperty;var R=(y,g,b)=>g in y?$(y,g,{enumerable:!0,configurable:!0,writable:!0,value:b}):y[g]=b;var u=(y,g,b)=>R(y,typeof g!="symbol"?g+"":g,b);class g{constructor(){u(this,"handlers",new Map)}on(e,t,s){let n=this.handlers.get(e);n||(n=new Map,this.handlers.set(e,n));let r=n.get(t);return r||(r=new Set,n.set(t,r)),r.add(s),()=>this.off(e,t,s)}off(e,t,s){const n=this.handlers.get(e);if(!n)return;const r=n.get(t);r&&(r.delete(s),r.size===0&&n.delete(t),n.size===0&&this.handlers.delete(e))}emit(e,t,s){const n=this.handlers.get(e);if(!n)return;const r=n.get(t);if(!(!r||r.size===0))for(const o of[...r])try{o(s)}catch(i){console.error("EventBus handler error:",i)}}clear(){this.handlers.clear()}}class b{constructor(){u(this,"handlers",new Map);u(this,"patternHandlers",new Map)}on(e,t,s){const n=String(t);if(this.isPattern(n)){const r=n;this.patternHandlers.has(e)||this.patternHandlers.set(e,new Map);const o=this.patternHandlers.get(e);return o.has(r)||o.set(r,[]),o.get(r).push(s),()=>this.offPattern(e,r,s)}else{const r=this.normalizeTypeKey(n);this.handlers.has(e)||this.handlers.set(e,new Map);const o=this.handlers.get(e);return o.has(r)||o.set(r,[]),o.get(r).push(s),()=>this.offExactNormalized(e,r,s)}}off(e,t,s){const n=this.normalizeTypeKey(String(t));this.offExactNormalized(e,n,s)}offExactNormalized(e,t,s){const n=this.handlers.get(e);if(!n)return;const r=n.get(t);if(!r)return;const o=r.indexOf(s);o!==-1&&r.splice(o,1),r.length===0&&n.delete(t),n.size===0&&this.handlers.delete(e)}offPattern(e,t,s){const n=this.patternHandlers.get(e);if(!n)return;const r=n.get(t);if(!r)return;const o=r.indexOf(s);o!==-1&&r.splice(o,1),r.length===0&&n.delete(t),n.size===0&&this.patternHandlers.delete(e)}emit(e,t,s){const n=String(t),r=this.normalizeTypeKey(n),o=this.handlers.get(e)?.get(r)??[],i=this.patternHandlers.get(e),c=[];if(i&&i.size){const l=this.normalizeTypeKey(n);for(const[p,d]of i.entries())this.matchPattern(p,l)&&c.push(d)}const f=new Set,h=l=>{for(const p of[...l])if(!f.has(p)){f.add(p);try{p(s)}catch(d){console.error(d);continue}}};h(o);for(const l of c)h(l)}isPattern(e){return e.includes("*")}normalizeTypeKey(e){return e.replace(/^\./,"")}splitPath(e){return this.normalizeTypeKey(e).split(".").filter(Boolean)}matchPattern(e,t){const s=this.splitPath(e),n=this.splitPath(t);let r=0,o=0;for(;r<s.length&&o<n.length;){const i=s[r];if(i==="**"){if(r===s.length-1||s.slice(r).filter(f=>f!=="**").length===0)return!0;for(let f=o;f<=n.length;f++)if(this.matchPattern(s.slice(r+1).join("."),n.slice(f).join(".")))return!0;return!1}if(i==="*"||i===n[o]){r++,o++;continue}return!1}if(o===n.length){for(;r<s.length;r++)if(s[r]!=="**")return!1;return!0}return!1}clear(){this.handlers.clear(),this.patternHandlers.clear()}__introspect(){const e=[];for(const[t,s]of this.handlers)for(const[n,r]of s)r.length>0&&e.push({channel:t,type:n,count:r.length});for(const[t,s]of this.patternHandlers)for(const[n,r]of s)r.length>0&&e.push({channel:t,type:n,count:r.length});return e}}class z{constructor(e){u(this,"_reduce");this._reduce=e}reduce(e,t){return this._reduce(e,t)}}function S(a,e,t="",s=new WeakMap){if(a===e)return[];if(typeof a!="object"||typeof e!="object"||a===null||e===null)return[t];if(a instanceof Date&&e instanceof Date)return a.getTime()===e.getTime()?[]:[t];if(a instanceof RegExp&&e instanceof RegExp)return a.source===e.source&&e.flags===a.flags?[]:[t];const n=a,r=e;let o=s.get(n);if(o){if(o.has(r))return[];o.add(r)}else o=new WeakSet,o.add(r),s.set(n,o);const i=Array.isArray(a),c=Array.isArray(e);if(i!==c)return[t];const f=[];if(i){const d=a,E=e;if(d.length!==E.length)return[t];for(let m=0;m<d.length;m++){const w=t?`${t}.${m}`:`${m}`,P=S(d[m],E[m],w,s);f.push(...P)}return f.filter(Boolean)}const h=Object.keys(a),l=Object.keys(e),p=new Set([...h,...l]);for(const d of p){const E=t?`${t}.${d}`:d,m=Object.prototype.hasOwnProperty.call(a,d),w=Object.prototype.hasOwnProperty.call(e,d);if(!m){f.push(E);continue}if(!w){f.push(E);continue}const P=S(a[d],e[d],E,s);f.push(...P)}return f.filter(Boolean)}function v(a,e=new WeakSet){if(a===null||typeof a!="object"||e.has(a)||Object.isFrozen(a))return a;if(e.add(a),Array.isArray(a)){const t=a;for(let s=0;s<t.length;s++)t[s]=v(t[s],e);return Object.freeze(t)}for(const t of Object.getOwnPropertyNames(a)){const s=Object.getOwnPropertyDescriptor(a,t);!s||!("value"in s)||(a[t]=v(a[t],e))}for(const t of Object.getOwnPropertySymbols(a)){const s=Object.getOwnPropertyDescriptor(a,t);!s||!("value"in s)||(a[t]=v(a[t],e))}return Object.freeze(a)}class M{constructor(e){u(this,"name");u(this,"middleware");u(this,"reducers");u(this,"state");u(this,"reducerBus");u(this,"connectorBus");u(this,"listeners",new Set);u(this,"effects",new Map);u(this,"patternEffects",new Set);u(this,"committedEventSubscribers",new Map);u(this,"uncommittedEventSubscribers",new Map);u(this,"allEventSubscribers",new Map);u(this,"sliceUnsubs",new Map);u(this,"patternReducers",new Map);u(this,"replayEnabled");u(this,"eventQueue",[]);u(this,"isProcessingQueue",!1);u(this,"processedEvents",new Map);u(this,"dedupConfig");u(this,"eventCleanupTimer",null);this.name=e.name??"yoltra Store",this.reducerBus=new g,this.connectorBus=new b,this.middleware=[...e.middleware??[]],this.reducers={},this.state={},this.replayEnabled=e.devtools?.allowReplay??!1;const t=process.env.NODE_ENV==="production"?100:50;if(this.dedupConfig={windowMs:e.dedupWindowMs??t,maxCacheSize:1e3},Object.entries(e.reducer).forEach(([s,n])=>{this.mountSlice(s,n,{preserveState:!1})}),e.effects?.length)for(const s of e.effects)this.registerEffect(s);this.eventCleanupTimer=setInterval(()=>{this.pruneProcessedEvents(Date.now())},5e3),this.dispose=this.dispose.bind(this),this.notifyEffects=this.notifyEffects.bind(this),this.forwardEvent=this.forwardEvent.bind(this),this.__applyExternalState=this.__applyExternalState.bind(this),this.__replayEvents=this.__replayEvents.bind(this),this.__devtoolsIntrospect=this.__devtoolsIntrospect.bind(this),this.mountSlice=this.mountSlice.bind(this),this.unmountSlice=this.unmountSlice.bind(this),this.getAtPath=this.getAtPath.bind(this),this.emit=this.emit.bind(this),this.subscribe=this.subscribe.bind(this),this.connect=this.connect.bind(this),this.onEffect=this.onEffect.bind(this),this.onEvent=this.onEvent.bind(this),this.getState=this.getState.bind(this),this.registerEffect=this.registerEffect.bind(this),this.registerMiddleware=this.registerMiddleware.bind(this),this.registerReducer=this.registerReducer.bind(this),this.replaceMiddleware=this.replaceMiddleware.bind(this),this.replaceEffects=this.replaceEffects.bind(this),this.replaceReducers=this.replaceReducers.bind(this),this.hotReplace=this.hotReplace.bind(this)}dispose(){this.eventCleanupTimer&&(clearInterval(this.eventCleanupTimer),this.eventCleanupTimer=null),this.processedEvents.clear(),this.effects.clear(),this.patternEffects.clear()}fingerprint(e,t,s){const n=`${e}::${t}`;try{if(s==null)return`${n}::null`;if(typeof s!="object")return`${n}::${String(s)}`;const r=JSON.stringify(s);return`${n}::${r}`}catch{return`${n}::${Date.now()}::${Math.random()}`}}shouldDedupe(e){const t=Date.now(),s=this.processedEvents.get(e);return s!==void 0&&t-s<this.dedupConfig.windowMs?!0:(this.processedEvents.set(e,t),this.processedEvents.size>this.dedupConfig.maxCacheSize&&this.pruneProcessedEvents(t),!1)}pruneProcessedEvents(e){const t=e-this.dedupConfig.windowMs*2;for(const[s,n]of this.processedEvents)n<t&&this.processedEvents.delete(s)}matchesWhen(e,t){return!e||"any"in e&&e.any===!0?!0:"keys"in e?e.keys.some(([s,n])=>t.channel===s&&t.type===n):"channel"in e?t.channel===e.channel:"channels"in e?e.channels.includes(t.channel):!1}getMiddlewareFunction(e){return typeof e=="function"?e:e.middleware}getMiddlewareWhen(e){if(typeof e!="function")return e.when}async notifyEffects(e){const t=`${String(e.channel)}::${String(e.type)}`,s=this.effects.get(t);if(s&&s.size>0)for(const n of[...s])try{await n(e,this.getState,this.emit)}catch(r){console.error("Effect error:",r)}for(const{effect:n,when:r}of this.patternEffects)if(this.matchesWhen(r,e))try{await n(e,this.getState,this.emit)}catch(o){console.error("Effect error:",o)}}async notifyEventSubscribers(e,t){const s=`${String(e.channel)}::${String(e.type)}`,r=(t==="committed"?this.committedEventSubscribers:this.uncommittedEventSubscribers).get(s);if(r?.size)for(const i of[...r])try{await i(e,this.getState,this.emit,t)}catch(c){console.error("Event subscription error:",c)}const o=this.allEventSubscribers.get(s);if(o?.size)for(const i of[...o])try{await i(e,this.getState,this.emit,t)}catch(c){console.error("Event subscription error:",c)}}forwardEvent(e,t){const s=this.state[e],n=this.reducers[e].reduce(s,t);if(s===n)return!1;const r=S(s,n).filter(Boolean);if(r.length===0)return!1;const o=v(structuredClone(n));this.state={...this.state,[e]:o};const i=new Set;for(const c of r)for(const f of M.buildAncestorPaths(c))i.add(f);for(const c of i){const f=this.getAtPath(s,c),h=this.getAtPath(o,c);this.connectorBus.emit(e,c,{oldValue:f,newValue:h,path:c})}return!0}__devtoolsIntrospect(){const e=Object.keys(this.reducers).map(i=>{const c=this.patternReducers.get(i);return{name:i,when:c}}),t=[];for(const[i,c]of this.effects){if(c.size===0)continue;const[f,h]=i.split("::");for(const l of c){const p=l.__quoMeta;t.push({channel:f,type:h,name:p?.name,description:p?.description})}}for(const i of this.patternEffects){const c=i.effect.__quoMeta;t.push({channel:"*",type:"*",name:c?.name,description:c?.description})}const s=[];for(const i of this.middleware)typeof i=="function"?s.push({name:i.name||void 0}):s.push({name:i.meta?.name,description:i.meta?.description,when:i.when});const n=[];for(const i of this.connectorBus.__introspect())for(let c=0;c<i.count;c++)n.push({reducer:i.channel,property:i.type});const r=[];for(const[i,c]of this.committedEventSubscribers){if(c.size===0)continue;const[f,h]=i.split("::");for(let l=0;l<c.size;l++)r.push({channel:f,type:h,phase:"committed"})}for(const[i,c]of this.uncommittedEventSubscribers){if(c.size===0)continue;const[f,h]=i.split("::");for(let l=0;l<c.size;l++)r.push({channel:f,type:h,phase:"uncommitted"})}for(const[i,c]of this.allEventSubscribers){if(c.size===0)continue;const[f,h]=i.split("::");for(let l=0;l<c.size;l++)r.push({channel:f,type:h,phase:"all"})}const o=this.listeners.size;return{reducers:e,effects:t,middleware:s,atomic:n,event:r,coarse:o}}__applyExternalState(e){const t=this.state,s=e;let n={...this.state},r=!1;Object.keys(this.reducers).forEach(o=>{const i=t?.[o],c=s?.[o];if(i===c)return;const f=v(structuredClone(c));n[o]=f,r=!0;const h=S(i,c).filter(Boolean);if(h.length===0)return;const l=new Set;for(const p of h)for(const d of M.buildAncestorPaths(p))l.add(d);for(const p of l){const d=this.getAtPath(i,p),E=this.getAtPath(f,p);this.connectorBus.emit(o,p,{oldValue:d,newValue:E,path:p})}}),r&&(this.state=n),r&&this.listeners.forEach(o=>o())}__replayEvents(e,t){if(!this.replayEnabled)throw new Error("[yoltra] Event replay is disabled. Enable it with createStore({ devtools: { allowReplay: true } })");this.__applyExternalState(e);for(const s of t){const n=s,r=this.state;this.reducerBus.emit(n.channel,n.type,n.payload);for(const[c,f]of this.patternReducers)this.matchesWhen(f,n)&&this.forwardEvent(c,n);const o=this.state,i=r!==o;this.notifyEventSubscribers(n,"committed"),i&&this.listeners.forEach(c=>c())}}async emit(e,t,s){const n=this.fingerprint(e,t,s);if(this.shouldDedupe(n))return;const r=crypto.randomUUID();if(this.eventQueue.push({channel:e,type:t,payload:s,id:r}),!this.isProcessingQueue){this.isProcessingQueue=!0;try{for(;this.eventQueue.length;){const{channel:o,type:i,payload:c,id:f}=this.eventQueue.shift(),h={channel:o,type:i,payload:c,id:f};let l=!0;for(const m of this.middleware){const w=this.getMiddlewareWhen(m);if(!this.matchesWhen(w,h))continue;const P=this.getMiddlewareFunction(m);try{if(!await P(this.state,h,this.emit)){l=!1;break}}catch(k){console.error("Middleware error:",k),l=!1;break}}if(!l){await this.notifyEventSubscribers(h,"uncommitted");continue}const p=this.state;this.reducerBus.emit(o,i,c);for(const[m,w]of this.patternReducers)this.matchesWhen(w,h)&&this.forwardEvent(m,h);const d=this.state,E=p!==d;await this.notifyEventSubscribers(h,"committed"),await this.notifyEffects(h),E&&this.listeners.forEach(m=>m())}}catch(o){console.error("Emit queue error:",o)}finally{this.isProcessingQueue=!1}}}connect(e,t){return this.connectorBus.on(e.reducer,e.property,t)}onEvent(e,t,s,n="committed"){const r=`${e}::${String(t)}`,o=n==="committed"?this.committedEventSubscribers:n==="uncommitted"?this.uncommittedEventSubscribers:this.allEventSubscribers;return o.has(r)||o.set(r,new Set),o.get(r).add(s),()=>{const i=o.get(r);i&&(i.delete(s),i.size===0&&o.delete(r))}}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}getState(){return this.state}registerMiddleware(e){return this.middleware.push(e),()=>{const t=this.middleware.indexOf(e);t!==-1&&this.middleware.splice(t,1)}}registerReducer(e,t){if(e in this.reducers)throw new Error(`Reducer ${e} already exists`);return this.mountSlice(e,t,{preserveState:!1}),this.listeners.forEach(s=>s()),()=>{this.unmountSlice(e,{deleteState:!0}),this.listeners.forEach(s=>s())}}registerEffect(e){const{effect:t,meta:s,when:n}=e,r=[];if(s&&(t.__quoMeta=s),n&&("any"in n&&n.any===!0||"channel"in n||"channels"in n)){const c={effect:t,when:n};return this.patternEffects.add(c),()=>{this.patternEffects.delete(c)}}const i=this.normalizeEventKeys(e);if(i.length===0&&!n&&!e.events){const c={effect:t,when:{any:!0}};return this.patternEffects.add(c),()=>{this.patternEffects.delete(c)}}for(const[c,f]of i){const h=`${String(c)}::${String(f)}`;this.effects.has(h)||this.effects.set(h,new Set),this.effects.get(h).add(t),r.push(()=>{const l=this.effects.get(h);l&&(l.delete(t),l.size===0&&this.effects.delete(h))})}return()=>{for(const c of r)c()}}onEffect(e,t,s){const n=async(r,o,i)=>{if(r.channel!==e||r.type!==t)return;const c=r;return s(c.payload,o,i,c)};return this.registerEffect({events:[[e,t]],effect:n})}replaceMiddleware(e){this.middleware.length=0;for(const t of e)this.middleware.push(t)}replaceEffects(e){this.effects.clear(),this.patternEffects.clear();for(const t of e)this.registerEffect(t)}replaceReducers(e,t={}){const s=t.preserveState!==!1,n=new Set(Object.keys(this.reducers)),r=Object.entries(e),o=new Set(r.map(([i])=>i));for(const i of n)o.has(i)||this.unmountSlice(i,{deleteState:!0});for(const[i,c]of r)n.has(i)?(this.unmountSlice(i,{deleteState:!1}),this.mountSlice(i,c,{preserveState:s})):this.mountSlice(i,c,{preserveState:!1})}hotReplace(e){e.middleware&&this.replaceMiddleware(e.middleware),e.effects&&this.replaceEffects(e.effects),e.reducer&&this.replaceReducers(e.reducer,{preserveState:e.preserveState})}mountSlice(e,t,s){const n=e,{events:r,reducer:o,state:i,when:c}=t;if(this.reducers[e]=new z(o),(!s.preserveState||this.state[n]===void 0)&&(this.state[n]=v(structuredClone(i))),c&&("any"in c&&c.any===!0||"channel"in c||"channels"in c)){this.patternReducers.set(e,c),this.sliceUnsubs.set(n,[]);return}const h=this.normalizeEventKeys(t);if(h.length===0&&!c&&!r){this.patternReducers.set(e,{any:!0}),this.sliceUnsubs.set(n,[]);return}const l=[];for(const[p,d]of h){const E=this.reducerBus.on(p,d,m=>{const w={channel:p,type:d,payload:m,id:crypto.randomUUID()};this.forwardEvent(e,w)});l.push(E)}this.sliceUnsubs.set(n,l)}unmountSlice(e,t){const s=e;this.patternReducers.delete(e);const n=this.sliceUnsubs.get(s);if(n){for(const r of n)try{r()}catch(o){console.error(`[Store error]: ${o}`)}this.sliceUnsubs.delete(s)}delete this.reducers[e],t.deleteState&&delete this.state[s]}normalizeEventKeys(e){if(e.when){const t=e.when;if("any"in t&&t.any===!0)return[];if("keys"in t)return t.keys;if("channel"in t)return[];if("channels"in t)return[]}return e.events?e.events:[]}getAtPath(e,t){if(!t)return e;const n=(t[0]==="."?t.slice(1):t).split(".");let r=e;for(const o of n){if(r==null)return;r=r[o]}return r}static buildAncestorPaths(e){if(!e)return[];const s=(e[0]==="."?e.slice(1):e).split("."),n=[];for(let r=0;r<s.length;r++)n.push(s.slice(0,r+1).join("."));return n}}function _(a){return new M({name:a.name,reducer:a.reducer??{},middleware:a.middleware??[],effects:a.effects??[],dedupWindowMs:a.dedupWindowMs,devtools:a.devtools})}const O=a=>(e,t)=>t.map(s=>[e,s]),B=()=>a=>a;y.EventBus=g,y.LooseEventBus=b,y.Reducer=z,y.Store=M,y.createStore=_,y.detectChangedProps=S,y.eventKeys=B,y.freezeState=v,y.typedEvents=O,Object.defineProperty(y,Symbol.toStringTag,{value:"Module"})}));
|
|
10
|
+
(function(p,m){typeof exports=="object"&&typeof module<"u"?m(exports):typeof define=="function"&&define.amd?define(["exports"],m):(p=typeof globalThis<"u"?globalThis:p||self,m(p.yoltra={}))})(this,(function(p){"use strict";var B=Object.defineProperty;var T=(p,m,g)=>m in p?B(p,m,{enumerable:!0,configurable:!0,writable:!0,value:g}):p[m]=g;var d=(p,m,g)=>T(p,typeof m!="symbol"?m+"":m,g);class m{constructor(){d(this,"handlers",new Map)}on(e,t,r){let n=this.handlers.get(e);n||(n=new Map,this.handlers.set(e,n));let s=n.get(t);return s||(s=new Set,n.set(t,s)),s.add(r),()=>this.off(e,t,r)}off(e,t,r){const n=this.handlers.get(e);if(!n)return;const s=n.get(t);s&&(s.delete(r),s.size===0&&n.delete(t),n.size===0&&this.handlers.delete(e))}emit(e,t,r){const n=this.handlers.get(e);if(!n)return;const s=n.get(t);if(!(!s||s.size===0))for(const c of[...s])try{c(r)}catch(i){console.error("EventBus handler error:",i)}}clear(){this.handlers.clear()}}class g{constructor(){d(this,"handlers",new Map);d(this,"patternHandlers",new Map)}on(e,t,r){const n=String(t);if(this.isPattern(n)){const s=n;this.patternHandlers.has(e)||this.patternHandlers.set(e,new Map);const c=this.patternHandlers.get(e);return c.has(s)||c.set(s,[]),c.get(s).push(r),()=>this.offPattern(e,s,r)}else{const s=this.normalizeTypeKey(n);this.handlers.has(e)||this.handlers.set(e,new Map);const c=this.handlers.get(e);return c.has(s)||c.set(s,[]),c.get(s).push(r),()=>this.offExactNormalized(e,s,r)}}off(e,t,r){const n=this.normalizeTypeKey(String(t));this.offExactNormalized(e,n,r)}offExactNormalized(e,t,r){const n=this.handlers.get(e);if(!n)return;const s=n.get(t);if(!s)return;const c=s.indexOf(r);c!==-1&&s.splice(c,1),s.length===0&&n.delete(t),n.size===0&&this.handlers.delete(e)}offPattern(e,t,r){const n=this.patternHandlers.get(e);if(!n)return;const s=n.get(t);if(!s)return;const c=s.indexOf(r);c!==-1&&s.splice(c,1),s.length===0&&n.delete(t),n.size===0&&this.patternHandlers.delete(e)}emit(e,t,r){const n=String(t),s=this.normalizeTypeKey(n),c=this.handlers.get(e)?.get(s)??[],i=this.patternHandlers.get(e),o=[];if(i&&i.size){const l=this.normalizeTypeKey(n);for(const[u,y]of i.entries())this.matchPattern(u,l)&&o.push(y)}const h=new Set,f=l=>{for(const u of[...l])if(!h.has(u)){h.add(u);try{u(r)}catch(y){console.error(y);continue}}};f(c);for(const l of o)f(l)}isPattern(e){return e.includes("*")}normalizeTypeKey(e){return e.replace(/^\./,"")}splitPath(e){return this.normalizeTypeKey(e).split(".").filter(Boolean)}matchPattern(e,t){const r=this.splitPath(e),n=this.splitPath(t);let s=0,c=0,i=-1,o=0;for(;c<n.length;)if(s<r.length&&(r[s]==="*"||r[s]===n[c]))s++,c++;else if(s<r.length&&r[s]==="**")i=s,o=c,s++;else if(i!==-1)s=i+1,c=++o;else return!1;for(;s<r.length&&r[s]==="**";)s++;return s===r.length}clear(){this.handlers.clear(),this.patternHandlers.clear()}__introspect(){const e=[];for(const[t,r]of this.handlers)for(const[n,s]of r)s.length>0&&e.push({channel:t,type:n,count:s.length});for(const[t,r]of this.patternHandlers)for(const[n,s]of r)s.length>0&&e.push({channel:t,type:n,count:s.length});return e}}class P{constructor(e){d(this,"_reduce");this._reduce=e}reduce(e,t){return this._reduce(e,t)}}function v(a,e,t="",r=new Map){if(a===e)return[];if(typeof a!="object"||typeof e!="object"||a===null||e===null)return typeof a=="number"&&Number.isNaN(a)&&Number.isNaN(e)?[]:[t];if(a instanceof Date&&e instanceof Date)return a.getTime()===e.getTime()?[]:[t];if(a instanceof RegExp&&e instanceof RegExp)return a.source===e.source&&e.flags===a.flags?[]:[t];const n=a,s=e,c=r.get(n);if(c?.has(s))return[];const i=c??new Set;i.add(s),c||r.set(n,i);try{const o=Array.isArray(a),h=Array.isArray(e);if(o!==h)return[t];const f=[];if(o){const u=a,y=e;if(u.length!==y.length)return[t];for(let E=0;E<u.length;E++){const S=t?`${t}.${E}`:`${E}`;f.push(...v(u[E],y[E],S,r))}return f.filter(Boolean)}const l=new Set([...Object.keys(a),...Object.keys(e)]);for(const u of l){const y=t?`${t}.${u}`:u,E=Object.prototype.hasOwnProperty.call(a,u),S=Object.prototype.hasOwnProperty.call(e,u);if(!E||!S){f.push(y);continue}f.push(...v(a[u],e[u],y,r))}return f.filter(Boolean)}finally{i.delete(s),i.size===0&&r.delete(n)}}function b(a,e=new WeakSet){if(a===null||typeof a!="object"||e.has(a)||Object.isFrozen(a))return a;if(e.add(a),Array.isArray(a)){const t=a;for(let r=0;r<t.length;r++)t[r]=b(t[r],e);return Object.freeze(t)}for(const t of Object.getOwnPropertyNames(a)){const r=Object.getOwnPropertyDescriptor(a,t);!r||!("value"in r)||(a[t]=b(a[t],e))}for(const t of Object.getOwnPropertySymbols(a)){const r=Object.getOwnPropertyDescriptor(a,t);!r||!("value"in r)||(a[t]=b(a[t],e))}return Object.freeze(a)}function M(a){return process.env.NODE_ENV==="production"?a:b(a)}const z=100,O=()=>typeof performance<"u"&&typeof performance.now=="function"?performance.now():Date.now();class w{constructor(e){d(this,"name");d(this,"middleware");d(this,"reducers");d(this,"state");d(this,"reducerBus");d(this,"connectorBus");d(this,"listeners",new Set);d(this,"effects",new Map);d(this,"patternEffects",new Set);d(this,"committedEventSubscribers",new Map);d(this,"uncommittedEventSubscribers",new Map);d(this,"allEventSubscribers",new Map);d(this,"sliceUnsubs",new Map);d(this,"patternReducers",new Map);d(this,"replayEnabled");d(this,"onEffectError");d(this,"reduceQueue",[]);d(this,"isReducing",!1);d(this,"instrumentObservers",new Set);d(this,"changedPathSink",null);d(this,"inFlightEffects",0);d(this,"processedEvents",new Map);d(this,"dedupCount",0);d(this,"effectMeta",new WeakMap);d(this,"dedupConfig");d(this,"eventCleanupTimer",null);if(this.name=e.name??"yoltra Store",this.reducerBus=new m,this.connectorBus=new g,this.middleware=[...e.middleware??[]],this.reducers={},this.state={},this.replayEnabled=e.devtools?.allowReplay??!1,this.onEffectError=e.onEffectError,this.dedupConfig={windowMs:e.dedupWindowMs??0,maxCacheSize:1e3},Object.entries(e.reducer).forEach(([t,r])=>{this.mountSlice(t,r,{preserveState:!1})}),e.effects?.length)for(const t of e.effects)this.registerEffect(t);this.dispose=this.dispose.bind(this),this.notifyEffects=this.notifyEffects.bind(this),this.forwardEvent=this.forwardEvent.bind(this),this.__applyExternalState=this.__applyExternalState.bind(this),this.__replayEvents=this.__replayEvents.bind(this),this.__devtoolsIntrospect=this.__devtoolsIntrospect.bind(this),this.mountSlice=this.mountSlice.bind(this),this.unmountSlice=this.unmountSlice.bind(this),this.getAtPath=this.getAtPath.bind(this),this.emit=this.emit.bind(this),this.subscribe=this.subscribe.bind(this),this.connect=this.connect.bind(this),this.onEffect=this.onEffect.bind(this),this.onEvent=this.onEvent.bind(this),this.getState=this.getState.bind(this),this.registerEffect=this.registerEffect.bind(this),this.registerMiddleware=this.registerMiddleware.bind(this),this.registerReducer=this.registerReducer.bind(this),this.replaceMiddleware=this.replaceMiddleware.bind(this),this.replaceEffects=this.replaceEffects.bind(this),this.replaceReducers=this.replaceReducers.bind(this),this.hotReplace=this.hotReplace.bind(this)}dispose(){this.eventCleanupTimer&&(clearInterval(this.eventCleanupTimer),this.eventCleanupTimer=null),this.processedEvents.clear(),this.effects.clear(),this.patternEffects.clear(),this.effectMeta=new WeakMap,this.listeners.clear(),this.committedEventSubscribers.clear(),this.uncommittedEventSubscribers.clear(),this.allEventSubscribers.clear(),this.instrumentObservers.clear(),this.connectorBus.clear(),this.reducerBus.clear(),this.patternReducers.clear(),this.sliceUnsubs.clear(),this.changedPathSink=null}fingerprint(e,t,r){const n=`${e}::${t}`;try{if(r==null)return`${n}::null`;if(typeof r!="object")return`${n}::${String(r)}`;const s=JSON.stringify(r);return`${n}::${s}`}catch{return`${n}::${Date.now()}::${Math.random()}`}}shouldDedupe(e,t){const r=Date.now(),n=this.processedEvents.get(e);return n!==void 0&&r-n<t?(this.dedupCount++,!0):(this.processedEvents.set(e,r),this.ensureCleanupTimer(),this.processedEvents.size>this.dedupConfig.maxCacheSize&&this.pruneProcessedEvents(r),!1)}ensureCleanupTimer(){this.eventCleanupTimer===null&&(this.eventCleanupTimer=setInterval(()=>{this.pruneProcessedEvents(Date.now())},5e3),this.eventCleanupTimer.unref?.())}pruneProcessedEvents(e){const t=Math.max(this.dedupConfig.windowMs,z),r=e-t*2;for(const[n,s]of this.processedEvents)s<r&&this.processedEvents.delete(n);this.processedEvents.size===0&&this.eventCleanupTimer!==null&&(clearInterval(this.eventCleanupTimer),this.eventCleanupTimer=null)}matchesWhen(e,t){return!e||"any"in e&&e.any===!0?!0:"keys"in e?e.keys.some(([r,n])=>t.channel===r&&t.type===n):"channel"in e?t.channel===e.channel:"channels"in e?e.channels.includes(t.channel):!1}getMiddlewareFunction(e){return typeof e=="function"?e:e.middleware}getMiddlewareWhen(e){if(typeof e!="function")return e.when}async notifyEffects(e){const t=`${String(e.channel)}::${String(e.type)}`,r=this.effects.get(t);if(r&&r.size>0)for(const n of[...r])try{await n(e,this.getState,this.emit)}catch(s){console.error("Effect error:",s),this.onEffectError?.(s,e)}for(const{effect:n,when:s}of this.patternEffects)if(this.matchesWhen(s,e))try{await n(e,this.getState,this.emit)}catch(c){console.error("Effect error:",c),this.onEffectError?.(c,e)}}notifyEventSubscribers(e,t){const r=`${String(e.channel)}::${String(e.type)}`,s=(t==="committed"?this.committedEventSubscribers:this.uncommittedEventSubscribers).get(r);if(s?.size)for(const i of[...s])this.invokeEventSubscriber(i,e,t);const c=this.allEventSubscribers.get(r);if(c?.size)for(const i of[...c])this.invokeEventSubscriber(i,e,t)}invokeEventSubscriber(e,t,r){try{const n=e(t,this.getState,this.emit,r);n&&typeof n.then=="function"&&n.catch(s=>console.error("Event subscription error:",s))}catch(n){console.error("Event subscription error:",n)}}forwardEvent(e,t){const r=this.state[e],n=this.reducers[e].reduce(r,t);if(r===n)return!1;const s=v(r,n).filter(Boolean);if(s.length===0)return!1;const c=M(n);if(this.state={...this.state,[e]:c},this.changedPathSink)for(const o of s)this.changedPathSink.push(o?`${e}.${o}`:e);const i=new Set;for(const o of s)for(const h of w.buildAncestorPaths(o))i.add(h);for(const o of i){const h=this.getAtPath(r,o),f=this.getAtPath(c,o);this.connectorBus.emit(e,o,{oldValue:h,newValue:f,path:o})}return!0}__devtoolsIntrospect(){const e=Object.keys(this.reducers).map(i=>{const o=this.patternReducers.get(i);return{name:i,when:o}}),t=[];for(const[i,o]of this.effects){if(o.size===0)continue;const[h,f]=i.split("::");for(const l of o){const u=this.effectMeta.get(l);t.push({channel:h,type:f,name:u?.name,description:u?.description})}}for(const i of this.patternEffects){const o=this.effectMeta.get(i.effect);t.push({channel:"*",type:"*",name:o?.name,description:o?.description})}const r=[];for(const i of this.middleware)typeof i=="function"?r.push({name:i.name||void 0}):r.push({name:i.meta?.name,description:i.meta?.description,when:i.when});const n=[];for(const i of this.connectorBus.__introspect())for(let o=0;o<i.count;o++)n.push({reducer:i.channel,property:i.type});const s=[];for(const[i,o]of this.committedEventSubscribers){if(o.size===0)continue;const[h,f]=i.split("::");for(let l=0;l<o.size;l++)s.push({channel:h,type:f,phase:"committed"})}for(const[i,o]of this.uncommittedEventSubscribers){if(o.size===0)continue;const[h,f]=i.split("::");for(let l=0;l<o.size;l++)s.push({channel:h,type:f,phase:"uncommitted"})}for(const[i,o]of this.allEventSubscribers){if(o.size===0)continue;const[h,f]=i.split("::");for(let l=0;l<o.size;l++)s.push({channel:h,type:f,phase:"all"})}const c=this.listeners.size;return{reducers:e,effects:t,middleware:r,atomic:n,event:s,coarse:c,dedupHits:this.dedupCount,queueDepth:this.reduceQueue.length+this.inFlightEffects}}__applyExternalState(e){if(!this.replayEnabled){console.warn("[yoltra] External state apply (time-travel) is disabled. Enable it with createStore({ devtools: { allowReplay: true } })");return}const t=this.state,r=e,n={...this.state};let s=!1;Object.keys(this.reducers).forEach(c=>{const i=t?.[c],o=r?.[c];if(o===void 0){process.env.NODE_ENV!=="production"&&console.warn(`[yoltra] External state is missing slice "${String(c)}"; retaining its current value. Time-travel snapshots should contain all slices.`);return}if(i===o)return;const h=M(o);n[c]=h,s=!0;const f=v(i,o).filter(Boolean);if(f.length===0)return;const l=new Set;for(const u of f)for(const y of w.buildAncestorPaths(u))l.add(y);for(const u of l){const y=this.getAtPath(i,u),E=this.getAtPath(h,u);this.connectorBus.emit(c,u,{oldValue:y,newValue:E,path:u})}}),s&&(this.state=n),s&&this.listeners.forEach(c=>c())}__replayEvents(e,t){if(!this.replayEnabled)throw new Error("[yoltra] Event replay is disabled. Enable it with createStore({ devtools: { allowReplay: true } })");this.__applyExternalState(e);for(const r of t){const n=r,s=this.state;this.reducerBus.emit(n.channel,n.type,n.payload);for(const[o,h]of this.patternReducers)this.matchesWhen(h,n)&&this.forwardEvent(o,n);const c=this.state,i=s!==c;this.notifyEventSubscribers(n,"committed"),i&&this.listeners.forEach(o=>o())}}async emit(e,t,r,n){const s=n?.dedupKey,c=this.dedupConfig.windowMs;if(c>0||s!==void 0){const f=s!==void 0&&c<=0?z:c,l=s!==void 0?`${e}::${t}::#${s}`:this.fingerprint(e,t,r);if(this.shouldDedupe(l,f))return}const i=crypto.randomUUID();let o;const h=new Promise(f=>{o=f});return this.reduceQueue.push({channel:e,type:t,payload:r,id:i,resolve:o}),this.drainReduce(),h}drainReduce(){if(!this.isReducing){this.isReducing=!0;try{for(;this.reduceQueue.length>0;){const{channel:e,type:t,payload:r,id:n,resolve:s}=this.reduceQueue.shift(),c={channel:e,type:t,payload:r,id:n},i=this.instrumentObservers.size>0,o=i?this.state:void 0,h=[];i&&(this.changedPathSink=h);const f=i?O():0;let l=!1;try{l=this.applyEventSync(c)}catch(u){console.error("Emit reduce error:",u)}finally{i&&(this.changedPathSink=null)}i&&this.emitInstrumentation(c,l,h,o,O()-f),this.runEventEffects(c,l,s)}}finally{this.isReducing=!1}}}applyEventSync(e){for(const n of this.middleware){const s=this.getMiddlewareWhen(n);if(!this.matchesWhen(s,e))continue;const c=this.getMiddlewareFunction(n);let i;try{i=c(this.state,e,this.emit)}catch(o){console.error("Middleware error:",o),i=!1}if(!i)return this.notifyEventSubscribers(e,"uncommitted"),!1}const t=this.state;this.reducerBus.emit(e.channel,e.type,e.payload);for(const[n,s]of this.patternReducers)this.matchesWhen(s,e)&&this.forwardEvent(n,e);const r=t!==this.state;return this.notifyEventSubscribers(e,"committed"),r&&this.listeners.forEach(n=>n()),!0}async runEventEffects(e,t,r){this.inFlightEffects++;try{t&&await this.notifyEffects(e)}catch(n){console.error("Effect error:",n)}finally{this.inFlightEffects--,r()}}instrument(e){return this.instrumentObservers.add(e),()=>{this.instrumentObservers.delete(e)}}emitInstrumentation(e,t,r,n,s){const c={},i={};for(const h of r)c[h]=this.getAtPath(n,h),i[h]=this.getAtPath(this.state,h);const o={event:{id:e.id,channel:e.channel,type:e.type,payload:e.payload},committed:t,changedPaths:r,prevValues:c,nextValues:i,reduceTimeMs:s};for(const h of[...this.instrumentObservers])try{h(o)}catch(f){console.error("Instrumentation observer error:",f)}}connect(e,t){return this.connectorBus.on(e.reducer,e.property,t)}onEvent(e,t,r,n="committed"){const s=`${e}::${String(t)}`,c=n==="committed"?this.committedEventSubscribers:n==="uncommitted"?this.uncommittedEventSubscribers:this.allEventSubscribers;return c.has(s)||c.set(s,new Set),c.get(s).add(r),()=>{const i=c.get(s);i&&(i.delete(r),i.size===0&&c.delete(s))}}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}getState(){return this.state}registerMiddleware(e){return this.middleware.push(e),()=>{const t=this.middleware.indexOf(e);t!==-1&&this.middleware.splice(t,1)}}registerReducer(e,t){if(e in this.reducers)throw new Error(`Reducer ${e} already exists`);return this.mountSlice(e,t,{preserveState:!1}),this.listeners.forEach(r=>r()),()=>{this.unmountSlice(e,{deleteState:!0}),this.listeners.forEach(r=>r())}}registerEffect(e){const{effect:t,meta:r,when:n}=e,s=[];if(r&&this.effectMeta.set(t,r),n&&("any"in n&&n.any===!0||"channel"in n||"channels"in n)){const o={effect:t,when:n};return this.patternEffects.add(o),()=>{this.patternEffects.delete(o)}}const i=this.normalizeEventKeys(e);if(i.length===0&&!n&&!e.events){const o={effect:t,when:{any:!0}};return this.patternEffects.add(o),()=>{this.patternEffects.delete(o)}}for(const[o,h]of i){const f=`${String(o)}::${String(h)}`;this.effects.has(f)||this.effects.set(f,new Set),this.effects.get(f).add(t),s.push(()=>{const l=this.effects.get(f);l&&(l.delete(t),l.size===0&&this.effects.delete(f))})}return()=>{for(const o of s)o()}}onEffect(e,t,r){const n=async(s,c,i)=>{if(s.channel!==e||s.type!==t)return;const o=s;return r(o.payload,c,i,o)};return this.registerEffect({events:[[e,t]],effect:n})}replaceMiddleware(e){this.middleware.length=0;for(const t of e)this.middleware.push(t)}replaceEffects(e){this.effects.clear(),this.patternEffects.clear();for(const t of e)this.registerEffect(t)}replaceReducers(e,t={}){const r=t.preserveState!==!1,n=new Set(Object.keys(this.reducers)),s=Object.entries(e),c=new Set(s.map(([i])=>i));for(const i of n)c.has(i)||this.unmountSlice(i,{deleteState:!0});for(const[i,o]of s)n.has(i)?(this.unmountSlice(i,{deleteState:!1}),this.mountSlice(i,o,{preserveState:r})):this.mountSlice(i,o,{preserveState:!1})}hotReplace(e){e.middleware&&this.replaceMiddleware(e.middleware),e.effects&&this.replaceEffects(e.effects),e.reducer&&this.replaceReducers(e.reducer,{preserveState:e.preserveState})}mountSlice(e,t,r){const n=e,{events:s,reducer:c,state:i,when:o}=t;if(this.reducers[e]=new P(c),(!r.preserveState||this.state[n]===void 0)&&(this.state[n]=M(structuredClone(i))),o&&("any"in o&&o.any===!0||"channel"in o||"channels"in o)){this.patternReducers.set(e,o),this.sliceUnsubs.set(n,[]);return}const f=this.normalizeEventKeys(t);if(f.length===0&&!o&&!s){this.patternReducers.set(e,{any:!0}),this.sliceUnsubs.set(n,[]);return}const l=[];for(const[u,y]of f){const E=this.reducerBus.on(u,y,S=>{const $={channel:u,type:y,payload:S,id:crypto.randomUUID()};this.forwardEvent(e,$)});l.push(E)}this.sliceUnsubs.set(n,l)}unmountSlice(e,t){const r=e;this.patternReducers.delete(e);const n=this.sliceUnsubs.get(r);if(n){for(const s of n)try{s()}catch(c){console.error(`[Store error]: ${c}`)}this.sliceUnsubs.delete(r)}delete this.reducers[e],t.deleteState&&delete this.state[r]}normalizeEventKeys(e){if(e.when){const t=e.when;if("any"in t&&t.any===!0)return[];if("keys"in t)return t.keys;if("channel"in t)return[];if("channels"in t)return[]}return e.events?e.events:[]}getAtPath(e,t){if(!t)return e;const n=(t[0]==="."?t.slice(1):t).split(".");let s=e;for(const c of n){if(s==null)return;s=s[c]}return s}static buildAncestorPaths(e){if(!e)return[];const r=(e[0]==="."?e.slice(1):e).split("."),n=[];for(let s=0;s<r.length;s++)n.push(r.slice(0,s+1).join("."));return n}}function k(a){return new w({name:a.name,reducer:a.reducer??{},middleware:a.middleware??[],effects:a.effects??[],dedupWindowMs:a.dedupWindowMs,devtools:a.devtools,onEffectError:a.onEffectError})}const R=a=>(e,t)=>t.map(r=>[e,r]),_=()=>a=>a;p.EventBus=m,p.LooseEventBus=g,p.Reducer=P,p.Store=w,p.createStore=k,p.detectChangedProps=v,p.eventKeys=_,p.freezeState=b,p.typedEvents=R,Object.defineProperty(p,Symbol.toStringTag,{value:"Module"})}));
|
|
8
11
|
//# sourceMappingURL=yoltra.umd.js.map
|
package/package.json
CHANGED
|
@@ -1,21 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yoltra/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "A modern predictable state container built on the idea of channels, events, and payloads, giving you strongly typed, expressive actions",
|
|
5
|
-
"scripts": {
|
|
6
|
-
"dev": "vite",
|
|
7
|
-
"lint": "node ../../tools/repo-tools/bin/repo-eslint.cjs --report-unused-disable-directives --max-warnings 0",
|
|
8
|
-
"lint:fix": "node ../../tools/repo-tools/bin/repo-eslint.cjs --fix",
|
|
9
|
-
"test": "pnpm vitest --coverage --watch=false",
|
|
10
|
-
"build": "vite build",
|
|
11
|
-
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
12
|
-
"typecheck:tools": "tsc -p tsconfig.tools.json --noEmit",
|
|
13
|
-
"preview": "vite preview",
|
|
14
|
-
"prepublishOnly": "node ../../common/scripts/copy-license.cjs",
|
|
15
|
-
"docs": "rushx docs:js && rushx docs:md",
|
|
16
|
-
"docs:md": "pnpm typedoc --options ./typedoc.core.json",
|
|
17
|
-
"docs:js": "pnpm typedoc --options ./typedoc.core.json --json ./.typedoc/core-en.json"
|
|
18
|
-
},
|
|
19
5
|
"license": "MIT",
|
|
20
6
|
"author": {
|
|
21
7
|
"name": "Manu Ramirez <@pixerael>",
|
|
@@ -79,5 +65,18 @@
|
|
|
79
65
|
"vite-plugin-banner": "0.8.1",
|
|
80
66
|
"vite-plugin-dts": "^4.5.4",
|
|
81
67
|
"vitest": "3.2.4"
|
|
68
|
+
},
|
|
69
|
+
"scripts": {
|
|
70
|
+
"dev": "vite",
|
|
71
|
+
"lint": "node ../../tools/repo-tools/bin/repo-eslint.cjs --report-unused-disable-directives --max-warnings 0",
|
|
72
|
+
"lint:fix": "node ../../tools/repo-tools/bin/repo-eslint.cjs --fix",
|
|
73
|
+
"test": "vitest --coverage --watch=false",
|
|
74
|
+
"build": "vite build",
|
|
75
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
76
|
+
"typecheck:tools": "tsc -p tsconfig.tools.json --noEmit",
|
|
77
|
+
"preview": "vite preview",
|
|
78
|
+
"docs": "rushx docs:js && rushx docs:md",
|
|
79
|
+
"docs:md": "pnpm typedoc --options ./typedoc.core.json",
|
|
80
|
+
"docs:js": "pnpm typedoc --options ./typedoc.core.json --json ./.typedoc/core-en.json"
|
|
82
81
|
}
|
|
83
|
-
}
|
|
82
|
+
}
|