async-states
Version:
Core of async-states
262 lines (259 loc) • 8.55 kB
JavaScript
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