@reatom/core
Version:
The ultimate state manager
3 lines (2 loc) • 9.93 kB
JavaScript
const impossibleValue=Symbol(),callSafely=function(fn){try{return fn(...[].slice.call(arguments,1))}catch(err){return setTimeout(()=>{throw err}),err instanceof Error?err:err=new Error(err)}};function throwReatomError(condition,message){if(condition)throw new Error(`Reatom error: ${message}`)}const isAtom=thing=>void 0!==thing?.__reatom,isAction=thing=>!0===thing?.__reatom?.isAction,isConnected=cache=>cache.subs.size+cache.listeners.size>0;function assertFunction(thing){throwReatomError("function"!=typeof thing,`invalid "${typeof thing}", function expected`)}const getRootCause=cause=>null===cause.cause?cause:getRootCause(cause.cause),isBrowser=()=>"object"==typeof window&&"object"==typeof document;let CTX,initiations=0;const createCtx=({callLateEffect:callLateEffect=callSafely,callNearEffect:callNearEffect=callSafely,restrictMultipleContexts:restrictMultipleContexts=isBrowser()}={})=>{restrictMultipleContexts&&1==initiations++&&console.warn("Reatom: multiple contexts detected, which is irrelevant in browser, you should use only one context");let caches=new WeakMap,read=proto=>caches.get(proto),logsListeners=new Set,nearEffects=[],lateEffects=[],inTr=!1,trError=null,trUpdates=[],trRollbacks=[],trLogs=[],trNearEffectsStart=0,trLateEffectsStart=0,effectsProcessing=!1,walkNearEffects=()=>{for(let effect of nearEffects)callNearEffect(effect,ctx);nearEffects=[]},walkLateEffects=()=>{if(!effectsProcessing){effectsProcessing=!0,walkNearEffects();for(let effect of lateEffects)callLateEffect(effect,ctx),nearEffects.length>0&&walkNearEffects();lateEffects=[],effectsProcessing=!1}},addPatch=({state:state,proto:proto,pubs:pubs,subs:subs,listeners:listeners},cause)=>(proto.actual=!1,trLogs.push(proto.patch={state:state,proto:proto,cause:cause,pubs:pubs,subs:subs,listeners:listeners}),proto.patch),enqueueComputers=cache=>{for(let subProto of cache.subs){let subCache=subProto.patch??read(subProto);subProto.patch&&!subProto.actual||0===addPatch(subCache,cache).listeners.size&&enqueueComputers(subCache)}},disconnect=(proto,pubPatch)=>{if(pubPatch.subs.delete(proto)&&(trRollbacks.push(()=>pubPatch.subs.add(proto)),!isConnected(pubPatch))){null!==pubPatch.proto.disconnectHooks&&nearEffects.push(...pubPatch.proto.disconnectHooks);for(let parentParent of pubPatch.pubs)disconnect(pubPatch.proto,parentParent)}},connect=(proto,pubPatch)=>{if(!pubPatch.subs.has(proto)){let wasConnected=isConnected(pubPatch);if(pubPatch.subs.add(proto),trRollbacks.push(()=>pubPatch.subs.delete(proto)),!wasConnected){null!==pubPatch.proto.connectHooks&&nearEffects.push(...pubPatch.proto.connectHooks);for(let parentParentPatch of(pubPatch.proto.patch??read(pubPatch.proto)).pubs)connect(pubPatch.proto,parentParentPatch)}}},actualize=(ctx,proto,updater)=>{let{patch:patch,actual:actual}=proto,updating=void 0!==updater;if(!updating&&actual&&(0===patch.pubs.length||isConnected(patch)))return patch;let cache=patch??read(proto),isInt=!cache,cause=updating?ctx.cause:read(__root);if(isInt)cache={state:proto.initState(ctx),proto:proto,cause:cause,pubs:[],subs:new Set,listeners:new Set},updating&&trLogs.push(cache);else if(null===proto.computer&&!updating)return cache;patch&&!actual||(patch=addPatch(cache,cause));let{state:state}=patch,patchCtx={get:ctx.get,spy:void 0,schedule:ctx.schedule,subscribe:ctx.subscribe,cause:patch};try{proto.computer&&((patchCtx,patch)=>{let{proto:proto,pubs:pubs}=patch,isDepsChanged=!1;if(0===pubs.length||pubs.some(({proto:proto,state:state})=>!Object.is(state,(patch.cause=actualize(patchCtx,proto)).state))){let newPubs=[];if(patchCtx.spy=({__reatom:depProto},cb)=>{let depPatch=actualize(patchCtx,depProto),prevDepPatch=newPubs.push(depPatch)<=pubs.length?pubs[newPubs.length-1]:void 0,isDepChanged=prevDepPatch?.proto!==depPatch.proto;isDepsChanged||=isDepChanged;let state=depProto.isAction&&!isDepChanged?depPatch.state.slice(prevDepPatch.state.length):depPatch.state;if(!cb||!isDepChanged&&Object.is(state,prevDepPatch.state))return state;if(depProto.isAction)for(const call of state)cb(call);else cb(state,isDepChanged?void 0:prevDepPatch?.state)},patch.state=patch.proto.computer(patchCtx,patch.state),patch.pubs=newPubs,(isDepsChanged||pubs.length>newPubs.length)&&isConnected(patch)){for(let{proto:depProto}of pubs)newPubs.every(dep=>dep.proto!==depProto)&&disconnect(proto,depProto.patch??read(depProto));for(let{proto:depProto}of newPubs)pubs.every(dep=>dep.proto!==depProto)&&connect(proto,depProto.patch??read(depProto))}patchCtx.spy=()=>throwReatomError(!0,"async spy"),patch=proto=pubs=newPubs=null}})(patchCtx,patch),updating&&(patch.cause=ctx.cause,updater(patchCtx,patch)),proto.actual=!0}catch(error){throw patch.error=error}if(!Object.is(state,patch.state)&&(patch.subs.size>0&&(updating||patch.listeners.size>0)&&enqueueComputers(patch),proto.updateHooks)){let ctx={get:patchCtx.get,spy:void 0,schedule:patchCtx.schedule,subscribe:patchCtx.subscribe,cause:patchCtx.cause};proto.updateHooks.forEach(hook=>trUpdates.push(()=>hook(ctx,patch)))}return patch},ctx={get(atomOrCb){if(throwReatomError(CTX&&getRootCause(CTX.cause)!==read(__root),"cause collision"),isAtom(atomOrCb)){let proto=atomOrCb.__reatom;if(inTr)return actualize(this,proto).state;let cache=read(proto);return void 0===cache||null!==proto.computer&&!isConnected(cache)?this.get(()=>actualize(this,proto).state):cache.state}if(throwReatomError(null!==trError,"tr failed"),inTr)return atomOrCb(read,actualize);inTr=!0,trNearEffectsStart=nearEffects.length,trLateEffectsStart=lateEffects.length;let start=void 0===CTX;start&&(CTX=this);try{var result=atomOrCb(read,actualize);for(let i=0;i<trLogs.length;i++){let{listeners:listeners,proto:proto}=trLogs[i];if(listeners.size>0&&actualize(this,proto),trUpdates.length>0)for(let commit of trUpdates.splice(0))commit(this)}if(trLogs.length)for(let log of logsListeners)log(trLogs);for(let patch of trLogs){let{proto:proto,state:state}=patch;if(proto.isAction&&(patch.state=[]),patch===proto.patch)if(proto.patch=null,proto.actual=!1,caches.set(proto,patch),proto.isAction){if(0===state.length)continue;for(let cb of patch.listeners)nearEffects.push(()=>cb(state))}else for(let cb of patch.listeners)lateEffects.push(()=>cb(read(proto).state))}}catch(e){trError=e=e instanceof Error?e:new Error(String(e));for(let log of logsListeners)log(trLogs,e);for(let cb of trRollbacks)callSafely(cb,e);for(let{proto:proto}of trLogs)proto.patch=null,proto.actual=!1;throw nearEffects.length=trNearEffectsStart,lateEffects.length=trLateEffectsStart,e}finally{inTr=!1,trError=null,trUpdates=[],trRollbacks=[],trLogs=[],trNearEffectsStart=0,trLateEffectsStart=0,start&&(CTX=void 0)}return walkLateEffects(),result},spy:void 0,schedule(cb,step=1){return assertFunction(cb),throwReatomError(!this,"missed context"),new Promise((res,rej)=>{-1===step?inTr&&trRollbacks.push(cb):0===step?inTr&&trUpdates.push(()=>cb(this)):((1===step?nearEffects:lateEffects).push(()=>{try{let result=cb(this);return result instanceof Promise?result.then(res,rej):res(result),result}catch(error){throw rej(error),error}}),inTr||walkLateEffects())})},subscribe(atom,cb=atom){if(assertFunction(cb),atom===cb)return logsListeners.add(cb),()=>logsListeners.delete(cb);throwReatomError(!isAtom(atom),"target subscriber isn't an atom");let{__reatom:proto}=atom,lastState=impossibleValue,listener=state=>Object.is(lastState,state)||cb(lastState=state),cache=read(proto);return void 0!==cache&&isConnected(cache)?cache.listeners.add(listener):this.get(()=>{cache=actualize(this,proto,(patchCtx,patch)=>{}),cache.listeners.add(listener),trRollbacks.push(()=>proto.patch.listeners.delete(listener));for(let pubPatch of cache.pubs)connect(proto,pubPatch);null!==proto.connectHooks&&nearEffects.push(...proto.connectHooks)}),lastState===impossibleValue&&listener((proto.patch??read(proto)).state),()=>{if(cache.listeners.delete(listener)&&!isConnected(cache)){proto.disconnectHooks&&nearEffects.push(...proto.disconnectHooks);for(let pubCache of read(proto).pubs)disconnect(proto,pubCache);inTr||(trRollbacks.length=0,walkLateEffects())}}},cause:void 0};return(ctx.cause=ctx.get(()=>actualize(ctx,__root))).cause=null,ctx};let i=0,__count=name=>`${name}#${++i}`;function pipe(){return[].slice.call(arguments).reduce((acc,fn)=>fn(acc),this)}function onChange(cb){const hook=(ctx,patch)=>cb(ctx,patch.state);return(this.__reatom.updateHooks??=new Set).add(hook),()=>this.__reatom.updateHooks.delete(hook)}function onCall(cb){return this.onChange((ctx,state)=>{const{params:params,payload:payload}=state[state.length-1];cb(ctx,payload,params)})}function atom(initState,name=__count("_atom")){let theAtom=(ctx,update)=>ctx.get((read,actualize)=>actualize(ctx,theAtom.__reatom,(patchCtx,patch)=>{patch.state="function"==typeof update?update(patch.state,patchCtx):update}).state),computer=null;return"function"==typeof initState&&(theAtom={},computer=initState,initState=void 0),theAtom.__reatom={name:name,isAction:!1,patch:null,initState:()=>initState,computer:computer,connectHooks:null,disconnectHooks:null,updateHooks:null,actual:!1},theAtom.pipe=pipe,theAtom.onChange=onChange,0===experimental_PLUGINS.length?theAtom:theAtom.pipe(...experimental_PLUGINS)}const action=(fn,name)=>{void 0!==fn&&"string"!=typeof fn||(name=fn,fn=(ctx,v)=>v),assertFunction(fn);let actionAtom=atom([],name??__count("_action"));return actionAtom.__reatom.isAction=!0,actionAtom.__reatom.unstable_fn=fn,Object.assign(function(){var params=[].slice.call(arguments);let state=actionAtom(params[0],(state,patchCtx)=>(params[0]=patchCtx,[...state,{params:params.slice(1),payload:patchCtx.cause.proto.unstable_fn(...params)}]));return state[state.length-1].payload},actionAtom,{onCall:onCall})},experimental_PLUGINS=[],__root=atom(void 0,"root").__reatom,batch=(ctx,cb)=>ctx.get(cb);export{__count,__root,action,atom,batch,callSafely,createCtx,experimental_PLUGINS,isAction,isAtom,throwReatomError};
//# sourceMappingURL=index.mjs.map