UNPKG

async-states

Version:

Core of async-states

262 lines (259 loc) 8.55 kB
import { __DEV__, isFunction } from './utils.js'; import devtools from './devtools/Devtools.js'; import { success } from './enums.js'; import { shallowClone, nextUniqueId } from './helpers/core.js'; import { runcInstance, runInstanceImmediately } from './modules/StateRun.js'; import { hasCacheEnabled, getTopLevelParent, persistAndSpreadCache, spreadCacheChangeOnLanes } from './modules/StateCache.js'; import { replaceInstanceState, setInstanceState, setInstanceData, disposeInstance } from './modules/StateUpdate.js'; import { subscribeToInstanceEvent, subscribeToInstance } from './modules/StateSubscription.js'; import { requestContext } from './modules/StateContext.js'; import { initializeInstance } from './modules/StateInitialization.js'; // this is the main instance that will hold and manipulate the state // it is referenced by its 'key' or name. // when a state with the same name exists, it is returned instead of creating // a new one. class AsyncState { ctx; // used only in __DEV__ mode journal = null; // this contains all methods, such as getState, setState and so on actions; id; key; version = 0; config; payload = null; cache = null; parent = null; lanes = null; state; queue = null; latestRun = null; lastSuccess; promise = null; res; currentAbort = null; fn = null; pendingUpdate = null; pendingTimeout = null; subsIndex = null; subscriptions = null; eventsIndex = null; events = null; global = null; constructor(key, producer, config) { let instanceConfig = shallowClone(config); // this means that the instance won't be stored in the LibraryContext // object, will be mostly used with anonymous instances if (instanceConfig.storeInContext !== false) { // fallback to globalContext (null) let context = config?.context || null; let libraryContext = requestContext(context); let existingInstance = libraryContext.get(key); // when an instance with the same key exists, reuse it if (existingInstance) { existingInstance.actions.patchConfig(config); existingInstance.actions.replaceProducer(producer || null); return existingInstance; } // start recording journal events early before end of creation if (__DEV__) { this.journal = []; } this.ctx = libraryContext; libraryContext.set(key, this); } else { this.ctx = null; } this.key = key; this.id = nextUniqueId(); this.fn = producer ?? null; this.config = instanceConfig; this.actions = new StateSource(this); // this function will loadCache if applied and also attempt // hydrated data if existing and also set the initial state // it was moved from here for simplicity and keep the constructor readable initializeInstance(this); if (__DEV__) devtools.emitCreation(this); } } class StateSource { key; uniqueId; inst; constructor(instance) { this.inst = instance; this.key = instance.key; this.uniqueId = instance.id; } getState = () => { return this.inst.state; }; replaceState = (newState, notify = true, callbacks) => { replaceInstanceState(this.inst, newState, notify, callbacks); }; setState = (newValue, status = success, callbacks) => { setInstanceState(this.inst, newValue, status, callbacks); }; setData = (newData) => { setInstanceData(this.inst, newData); }; on = (eventType, eventHandler) => { return subscribeToInstanceEvent(this.inst, eventType, eventHandler); }; run = (...args) => { return this.inst.actions.runc({ args }); }; runp = (...args) => { let instance = this.inst; return new Promise(function runpInstance(resolve) { let runcProps = { args, onError: resolve, onSuccess: resolve, }; runcInstance(instance, runcProps); }); }; runc = (props) => { let instance = this.inst; return runcInstance(instance, props); }; abort = (reason = undefined) => { let abortFn = this.inst.currentAbort; if (isFunction(abortFn)) { abortFn(reason); } }; replay = () => { let instance = this.inst; let latestRunTask = instance.latestRun; if (!latestRunTask) { return undefined; } let { args, payload } = latestRunTask; return runInstanceImmediately(instance, payload, { args }); }; invalidateCache = (cacheKey) => { let instance = this.inst; if (hasCacheEnabled(instance)) { const topLevelParent = getTopLevelParent(instance); if (!cacheKey) { topLevelParent.cache = {}; } else if (topLevelParent.cache) { delete topLevelParent.cache[cacheKey]; } persistAndSpreadCache(instance); } }; replaceCache = (cacheKey, cache) => { let instance = this.inst; if (!hasCacheEnabled(instance)) { return; } const topLevelParent = getTopLevelParent(instance); if (!topLevelParent.cache) { topLevelParent.cache = {}; } topLevelParent.cache[cacheKey] = cache; spreadCacheChangeOnLanes(topLevelParent); }; hasLane = (laneKey) => { let instance = this.inst; if (!instance.lanes) { return false; } return !!instance.lanes[laneKey]; }; removeLane = (laneKey) => { let instance = this.inst; if (!instance.lanes || !laneKey) { return false; } return delete instance.lanes[laneKey]; }; getLane = (laneKey) => { if (!laneKey) { return this; } let instance = this.inst; if (!instance.lanes) { instance.lanes = {}; } let existingLane = instance.lanes[laneKey]; if (existingLane) { return existingLane.actions; } let producer = instance.fn; let config = Object.assign({}, instance.config); let newLane = new AsyncState(laneKey, producer, config); newLane.parent = instance; instance.lanes[laneKey] = newLane; return newLane.actions; }; getVersion = () => { return this.inst.version; }; getConfig = () => { return this.inst.config; }; patchConfig = (updater) => { let currentConfig = this.inst.config; if (typeof updater === "function") { this.inst.config = { ...currentConfig, ...updater(currentConfig) }; } else { this.inst.config = { ...currentConfig, ...updater }; } }; getPayload = () => { let instance = this.inst; if (!instance.payload) { instance.payload = {}; } return instance.payload; }; mergePayload = (partialPayload) => { let instance = this.inst; if (!instance.payload) { instance.payload = {}; } instance.payload = Object.assign(instance.payload, partialPayload); }; dispose = () => { return disposeInstance(this.inst); }; replaceProducer = (newProducer) => { this.inst.fn = newProducer; }; subscribe = (argv) => { return subscribeToInstance(this.inst, argv); }; getAllLanes = () => { let instance = this.inst; if (!instance.lanes) { return []; } return Object.values(instance.lanes).map((lane) => lane.actions); }; } function getSource(key, context) { let executionContext = requestContext(context || null); return executionContext.get(key)?.actions; } function createSource(props, maybeProducer, maybeConfig) { let instance; if (typeof props === "object") { let { key, producer, config } = props; instance = new AsyncState(key, producer, config); } else { instance = new AsyncState(props, maybeProducer, maybeConfig); } return instance.actions; } export { AsyncState, StateSource, createSource, getSource }; //# sourceMappingURL=AsyncState.js.map