proxy-state-tree
Version:
An implementation of the Mobx/Vue state tracking approach, for library authors
194 lines • 6.64 kB
JavaScript
import { MutationTree } from './MutationTree';
import { IS_PROXY, PATH, PROXY_TREE, Proxifier, VALUE } from './Proxyfier';
import { TrackStateTree } from './TrackStateTree';
export { IS_PROXY, PROXY_TREE, VALUE, PATH, TrackStateTree, MutationTree, };
export * from './types';
export class ProxyStateTree {
flushCallbacks = [];
mutationCallbacks = [];
currentFlushId = 0;
currentTree;
previousTree;
mutationTree;
proxifier;
root;
pathDependencies = {};
state;
sourceState;
options;
constructor(state, options = {}) {
if (typeof options.devmode === 'undefined') {
options.devmode = true;
}
if (!options.delimiter) {
options.delimiter = '.';
}
this.root = this;
this.sourceState = state;
this.options = options;
this.createTrackStateProxifier();
}
/*
We create a base proxifier for tracking state. That means there is one
proxifier for all track state trees. This works because the actual tracking
refers to the current tree on "root"
*/
createTrackStateProxifier() {
const trackStateTree = new TrackStateTree(this);
this.proxifier = trackStateTree.proxifier = new Proxifier(trackStateTree);
this.state = trackStateTree.state = this.proxifier.proxify(this.sourceState, '');
}
getMutationTree() {
// We never want to do tracking when we want to do mutations
this.clearTrackStateTree();
if (!this.options.devmode) {
return (this.mutationTree =
this.mutationTree || new MutationTree(this, this.proxifier));
}
return new MutationTree(this);
}
getTrackStateTree() {
return new TrackStateTree(this);
}
getTrackStateTreeWithProxifier() {
const tree = this.getTrackStateTree();
if (this.options.ssr) {
tree.state = this.sourceState;
}
else {
tree.proxifier = new Proxifier(tree);
tree.state = tree.proxifier.proxify(this.sourceState, '');
}
return tree;
}
setTrackStateTree(tree) {
this.previousTree = this.currentTree;
this.currentTree = tree;
}
unsetTrackStateTree(tree) {
// We only allow unsetting it when it is currently the active tree
if (this.currentTree === tree) {
this.currentTree = null;
}
}
clearTrackStateTree() {
this.previousTree = null;
this.currentTree = null;
}
disposeTree(tree) {
if (tree instanceof MutationTree) {
;
tree.dispose();
}
}
onMutation(callback) {
this.mutationCallbacks.push(callback);
return () => {
this.mutationCallbacks.splice(this.mutationCallbacks.indexOf(callback), 1);
};
}
forceFlush() {
const emptyMutations = [];
const emptyPaths = [];
for (const key in this.pathDependencies) {
const callbacks = this.pathDependencies[key];
callbacks.forEach((callback) => {
callback(emptyMutations, emptyPaths, this.currentFlushId++, false);
});
}
}
flush(trees, isAsync = false) {
let changes;
if (Array.isArray(trees)) {
changes = trees.reduce((aggr, tree) => ({
mutations: aggr.mutations.concat(tree.getMutations()),
objectChanges: new Set([
...aggr.objectChanges,
...tree.getObjectChanges(),
]),
}), {
mutations: [],
objectChanges: new Set(),
});
}
else {
changes = {
mutations: trees.getMutations(),
objectChanges: trees.getObjectChanges(),
};
}
if (!changes.mutations.length && !changes.objectChanges.size) {
return {
mutations: [],
flushId: null,
};
}
const paths = new Set();
const pathCallbacksToCall = new Set();
const flushId = this.currentFlushId++;
for (const objectChange of changes.objectChanges) {
if (this.pathDependencies[objectChange]) {
paths.add(objectChange);
}
}
for (const mutation of changes.mutations) {
if (mutation.hasChangedValue) {
paths.add(mutation.path);
}
}
// Sort so that parent paths are called first
const sortedPaths = Array.from(paths).sort();
for (const path of sortedPaths) {
if (this.pathDependencies[path]) {
for (const callback of this.pathDependencies[path]) {
pathCallbacksToCall.add(callback);
}
}
}
for (const callback of pathCallbacksToCall) {
callback(changes.mutations, sortedPaths, flushId, isAsync);
}
// We have to ensure that we iterate all callbacks. One flush might
// lead to a change of the array (disposing), which means something
// might be skipped. But we still want to allow removal of callbacks,
// we just do not want to skip any, which is why we still check if it
// exists in the original array
const flushCallbacks = this.flushCallbacks.slice();
for (const callback of flushCallbacks) {
if (this.flushCallbacks.includes(callback)) {
callback(changes.mutations, sortedPaths, flushId, isAsync);
}
}
paths.clear();
pathCallbacksToCall.clear();
return {
mutations: changes.mutations,
flushId,
};
}
onFlush(callback) {
this.flushCallbacks.push(callback);
return () => this.flushCallbacks.splice(this.flushCallbacks.indexOf(callback), 1);
}
rescope(value, tree) {
return value && value[IS_PROXY]
? tree.proxifier.proxify(value[VALUE], value[PATH])
: value;
}
addPathDependency(path, callback) {
if (!this.pathDependencies[path]) {
this.pathDependencies[path] = new Set();
}
this.pathDependencies[path].add(callback);
}
removePathDependency(path, callback) {
this.pathDependencies[path].delete(callback);
if (!this.pathDependencies[path].size) {
delete this.pathDependencies[path];
}
}
toJSON() {
return this.sourceState;
}
}
//# sourceMappingURL=index.js.map