proxy-state-tree
Advanced tools
Comparing version 1.0.0-alpha3 to 1.0.0-alpha4
'use strict'; | ||
const isPlainObject = require("is-plain-object"); | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
const IS_PROXY = "__is_proxy"; | ||
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } | ||
function proxifyObject(proxyStateTree, obj, path, paths, mutations) { | ||
return new Proxy(obj, { | ||
get(target, prop) { | ||
if (prop === IS_PROXY) { | ||
return true; | ||
} | ||
var isPlainObject = _interopDefault(require('is-plain-object')); | ||
const value = target[prop]; | ||
const nestedPath = path.concat(prop); | ||
const IS_PROXY = Symbol('IS_PROXY'); | ||
if (proxyStateTree.isTrackingPaths) { | ||
proxyStateTree.paths.push(nestedPath); | ||
} | ||
function concat(path, prop) { | ||
return path === undefined ? prop : path + '.' + prop; | ||
} | ||
if (typeof value === "function") { | ||
return value(proxyStateTree, nestedPath); | ||
} | ||
const arrayMutations = new Set([ 'push', 'shift', 'pop', 'unshift', 'splice' ]); | ||
target[prop] = proxify(proxyStateTree, value, nestedPath); | ||
function createArrayProxy(tree, value, path) { | ||
return new Proxy(value, { | ||
get(target, prop) { | ||
if (prop === IS_PROXY) return true; | ||
return target[prop]; | ||
}, | ||
set(target, prop, value) { | ||
if (!proxyStateTree.isTrackingMutations) { | ||
throw new Error( | ||
`proxy-state-tree - You are mutating the path "${path | ||
.concat(prop) | ||
.join(".")}", but it is not allowed` | ||
); | ||
} | ||
proxyStateTree.mutations.push({ | ||
method: "set", | ||
path: path.concat(prop), | ||
args: [value] | ||
}); | ||
if (prop === 'length' || (typeof target[prop] === 'function' && !arrayMutations.has(prop))) { | ||
return target[prop]; | ||
} | ||
return Reflect.set(target, prop, value); | ||
}, | ||
deleteProperty(target, prop) { | ||
proxyStateTree.mutations.push({ | ||
method: "unset", | ||
path: path.concat(prop), | ||
args: [] | ||
}); | ||
const nestedPath = concat(path, prop); | ||
delete target[prop]; | ||
if (tree.isTrackingPaths) { | ||
tree.paths.add(nestedPath); | ||
} | ||
return true; | ||
} | ||
}); | ||
if (arrayMutations.has(prop)) { | ||
if (!tree.isTrackingMutations) { | ||
throw new Error( | ||
`proxy-state-tree - You are mutating the path "${nestedPath}", but it is not allowed` | ||
); | ||
} | ||
return (...args) => { | ||
tree.mutations.push({ | ||
method: prop, | ||
path: path, | ||
args: args | ||
}); | ||
return target[prop](...args); | ||
}; | ||
} | ||
return (target[prop] = proxify(tree, target[prop], nestedPath)); | ||
} | ||
}); | ||
} | ||
const arrayMutations = ["push", "shift", "pop", "unshift", "splice"]; | ||
function createObjectProxy(tree, value, path) { | ||
return new Proxy(value, { | ||
get(target, prop) { | ||
if (prop === IS_PROXY) return true; | ||
function proxifyArray(proxyStateTree, array, path) { | ||
return new Proxy(array, { | ||
get(target, prop) { | ||
if (prop === IS_PROXY) { | ||
return true; | ||
} | ||
const value = target[prop]; | ||
const nestedPath = concat(path, prop); | ||
const value = target[prop]; | ||
const nestedPath = path.concat(prop); | ||
if (tree.isTrackingPaths) { | ||
tree.paths.add(nestedPath); | ||
} | ||
if (proxyStateTree.isTrackingPaths) { | ||
proxyStateTree.paths.push(nestedPath); | ||
} | ||
if (typeof value === 'function') { | ||
return value(tree, nestedPath); | ||
} | ||
if (arrayMutations.indexOf(prop) >= 0) { | ||
if (!proxyStateTree.isTrackingMutations) { | ||
throw new Error( | ||
`proxy-state-tree - You are mutating the path "${path | ||
.concat(prop) | ||
.join(".")}", but it is not allowed` | ||
); | ||
} | ||
return (...args) => { | ||
proxyStateTree.mutations.push({ | ||
method: prop, | ||
path: path, | ||
args: args | ||
}); | ||
return (target[prop] = proxify(tree, value, nestedPath)); | ||
}, | ||
set(target, prop, value) { | ||
const nestedPath = concat(path, prop); | ||
return target[prop](...args); | ||
}; | ||
} | ||
if (!tree.isTrackingMutations) { | ||
throw new Error(`proxy-state-tree - You are mutating the path "${nestedPath}", but it is not allowed`); | ||
} | ||
tree.mutations.push({ | ||
method: 'set', | ||
path: nestedPath, | ||
args: [ value ] | ||
}); | ||
target[prop] = proxify(proxyStateTree, value, nestedPath); | ||
return (target[prop] = value); | ||
}, | ||
deleteProperty(target, prop) { | ||
const nestedPath = concat(path, prop); | ||
return target[prop]; | ||
} | ||
}); | ||
} | ||
tree.mutations.push({ | ||
method: 'unset', | ||
path: nestedPath, | ||
args: [] | ||
}); | ||
function proxify(proxyStateTree, value, path) { | ||
if (Array.isArray(value)) { | ||
return proxifyArray(proxyStateTree, value, path); | ||
} else if (isPlainObject(value)) { | ||
return proxifyObject(proxyStateTree, value, path); | ||
} | ||
delete target[prop]; | ||
return value; | ||
return true; | ||
} | ||
}); | ||
} | ||
class ProxyStateTree { | ||
constructor(state) { | ||
this.state = state; | ||
this.pathDependencies = {}; | ||
this.mutations = []; | ||
this.paths = []; | ||
this.isTrackingPaths = false; | ||
this.isTrackingMutations = false; | ||
this.proxy = proxify(this, state, []); | ||
function proxify(tree, value, path) { | ||
if (value) { | ||
if (value[IS_PROXY]) { | ||
return value; | ||
} else if (Array.isArray(value)) { | ||
return createArrayProxy(tree, value, path); | ||
} else if (isPlainObject(value)) { | ||
return createObjectProxy(tree, value, path); | ||
} | ||
} | ||
get() { | ||
return this.proxy; | ||
} | ||
startMutationTracking() { | ||
const currentMutations = this.mutations.slice(); | ||
return value; | ||
} | ||
this.isTrackingMutations = true; | ||
this.mutations.length = 0; | ||
class ProxyStateTree { | ||
constructor(state) { | ||
this.state = state; | ||
this.pathDependencies = {}; | ||
this.mutations = []; | ||
this.paths = new Set(); | ||
this.isTrackingPaths = false; | ||
this.isTrackingMutations = false; | ||
this.proxy = proxify(this, state); | ||
} | ||
get() { | ||
return this.proxy; | ||
} | ||
startMutationTracking() { | ||
const currentMutations = this.mutations.slice(); | ||
return currentMutations; | ||
} | ||
stopMutationTracking() { | ||
for (let callback in this.mutationCallbacks) { | ||
this.mutationCallbacks[callback](this.mutations); | ||
} | ||
for (let mutation in this.mutations) { | ||
const path = this.mutations[mutation].path.join('.'); | ||
if (this.pathDependencies[path]) { | ||
for (let pathCallback in this.pathDependencies[path]) { | ||
this.pathDependencies[path][pathCallback](); | ||
} | ||
} | ||
} | ||
this.isTrackingMutations = false; | ||
this.isTrackingMutations = true; | ||
this.mutations.length = 0; | ||
return this.mutations; | ||
} | ||
startPathsTracking() { | ||
const currentPaths = this.paths.slice(); | ||
return currentMutations; | ||
} | ||
stopMutationTracking() { | ||
for (let callback in this.mutationCallbacks) { | ||
this.mutationCallbacks[callback](this.mutations); | ||
} | ||
for (let mutation in this.mutations) { | ||
const path = this.mutations[mutation].path; | ||
if (this.pathDependencies[path]) { | ||
for (let pathCallback in this.pathDependencies[path]) { | ||
this.pathDependencies[path][pathCallback](); | ||
} | ||
} | ||
} | ||
this.isTrackingMutations = false; | ||
this.isTrackingPaths = true; | ||
this.paths.length = 0; | ||
return this.mutations; | ||
} | ||
startPathsTracking() { | ||
this.isTrackingPaths = true; | ||
const currentPaths = Array.from(this.paths); | ||
this.paths.clear(); | ||
return currentPaths; | ||
} | ||
stopPathsTracking() { | ||
this.isTrackingPaths = false; | ||
return Array.from(this.paths); | ||
} | ||
addMutationListener(initialPaths, cb) { | ||
const pathDependencies = this.pathDependencies; | ||
let currentStringPaths = initialPaths; | ||
return currentPaths; | ||
} | ||
stopPathsTracking() { | ||
this.isTrackingPaths = false; | ||
for (let index in currentStringPaths) { | ||
const currentStringPath = currentStringPaths[index]; | ||
pathDependencies[currentStringPath] = pathDependencies[currentStringPath] | ||
? pathDependencies[currentStringPath].concat(cb) | ||
: [cb]; | ||
} | ||
return this.paths; | ||
} | ||
addMutationListener(initialPaths, cb) { | ||
const pathDependencies = this.pathDependencies; | ||
let currentStringPaths = initialPaths.map((path) => path.join('.')); | ||
return { | ||
update(newPaths) { | ||
const newStringPaths = newPaths; | ||
for (let index in currentStringPaths) { | ||
const currentStringPath = currentStringPaths[index]; | ||
pathDependencies[currentStringPath] = pathDependencies[currentStringPath] | ||
? pathDependencies[currentStringPath].concat(cb) | ||
: [ cb ]; | ||
} | ||
for (let index in currentStringPaths) { | ||
const currentStringPath = currentStringPaths[index]; | ||
return { | ||
update(newPaths) { | ||
const newStringPaths = newPaths.map((path) => path.join('.')); | ||
if (newStringPaths.indexOf(currentStringPath) === -1) { | ||
pathDependencies[currentStringPath].splice( | ||
pathDependencies[currentStringPath].indexOf(cb), | ||
1 | ||
); | ||
} | ||
} | ||
for (let index in currentStringPaths) { | ||
const currentStringPath = currentStringPaths[index]; | ||
for (let index in newStringPaths) { | ||
const newStringPath = newStringPaths[index]; | ||
if (newStringPaths.indexOf(currentStringPath) === -1) { | ||
pathDependencies[currentStringPath].splice(pathDependencies[currentStringPath].indexOf(cb), 1); | ||
} | ||
} | ||
if (currentStringPaths.indexOf(newStringPath) === -1) { | ||
pathDependencies[newStringPath] = pathDependencies[newStringPath] | ||
? pathDependencies[newStringPath].concat(cb) | ||
: [cb]; | ||
} | ||
} | ||
for (let index in newStringPaths) { | ||
const newStringPath = newStringPaths[index]; | ||
currentStringPaths = newStringPaths; | ||
}, | ||
dispose() { | ||
for (let index in currentStringPaths) { | ||
const currentStringPath = currentStringPaths[index]; | ||
if (currentStringPaths.indexOf(newStringPath) === -1) { | ||
pathDependencies[newStringPath] = pathDependencies[newStringPath] | ||
? pathDependencies[newStringPath].concat(cb) | ||
: [ cb ]; | ||
} | ||
} | ||
pathDependencies[currentStringPath].splice( | ||
pathDependencies[currentStringPath].indexOf(cb), | ||
1 | ||
); | ||
currentStringPaths = newStringPaths; | ||
}, | ||
dispose() { | ||
for (let index in currentStringPaths) { | ||
const currentStringPath = currentStringPaths[index]; | ||
pathDependencies[currentStringPath].splice(pathDependencies[currentStringPath].indexOf(cb), 1); | ||
if (!pathDependencies[currentStringPath].length) { | ||
delete pathDependencies[currentStringPath]; | ||
} | ||
} | ||
} | ||
}; | ||
} | ||
if (!pathDependencies[currentStringPath].length) { | ||
delete pathDependencies[currentStringPath]; | ||
} | ||
} | ||
} | ||
}; | ||
} | ||
} | ||
module.exports = ProxyStateTree; | ||
exports.IS_PROXY = IS_PROXY; | ||
exports.default = ProxyStateTree; |
@@ -1,209 +0,213 @@ | ||
const isPlainObject = require("is-plain-object"); | ||
import isPlainObject from 'is-plain-object'; | ||
const IS_PROXY = "__is_proxy"; | ||
const IS_PROXY = Symbol('IS_PROXY'); | ||
function proxifyObject(proxyStateTree, obj, path, paths, mutations) { | ||
return new Proxy(obj, { | ||
get(target, prop) { | ||
if (prop === IS_PROXY) { | ||
return true; | ||
} | ||
function concat(path, prop) { | ||
return path === undefined ? prop : path + '.' + prop; | ||
} | ||
const value = target[prop]; | ||
const nestedPath = path.concat(prop); | ||
const arrayMutations = new Set([ 'push', 'shift', 'pop', 'unshift', 'splice' ]); | ||
if (proxyStateTree.isTrackingPaths) { | ||
proxyStateTree.paths.push(nestedPath); | ||
} | ||
function createArrayProxy(tree, value, path) { | ||
return new Proxy(value, { | ||
get(target, prop) { | ||
if (prop === IS_PROXY) return true; | ||
if (typeof value === "function") { | ||
return value(proxyStateTree, nestedPath); | ||
} | ||
if (prop === 'length' || (typeof target[prop] === 'function' && !arrayMutations.has(prop))) { | ||
return target[prop]; | ||
} | ||
target[prop] = proxify(proxyStateTree, value, nestedPath); | ||
const nestedPath = concat(path, prop); | ||
return target[prop]; | ||
}, | ||
set(target, prop, value) { | ||
if (!proxyStateTree.isTrackingMutations) { | ||
throw new Error( | ||
`proxy-state-tree - You are mutating the path "${path | ||
.concat(prop) | ||
.join(".")}", but it is not allowed` | ||
); | ||
} | ||
proxyStateTree.mutations.push({ | ||
method: "set", | ||
path: path.concat(prop), | ||
args: [value] | ||
}); | ||
if (tree.isTrackingPaths) { | ||
tree.paths.add(nestedPath); | ||
} | ||
return Reflect.set(target, prop, value); | ||
}, | ||
deleteProperty(target, prop) { | ||
proxyStateTree.mutations.push({ | ||
method: "unset", | ||
path: path.concat(prop), | ||
args: [] | ||
}); | ||
if (arrayMutations.has(prop)) { | ||
if (!tree.isTrackingMutations) { | ||
throw new Error( | ||
`proxy-state-tree - You are mutating the path "${nestedPath}", but it is not allowed` | ||
); | ||
} | ||
return (...args) => { | ||
tree.mutations.push({ | ||
method: prop, | ||
path: path, | ||
args: args | ||
}); | ||
delete target[prop]; | ||
return target[prop](...args); | ||
}; | ||
} | ||
return true; | ||
} | ||
}); | ||
return (target[prop] = proxify(tree, target[prop], nestedPath)); | ||
} | ||
}); | ||
} | ||
const arrayMutations = ["push", "shift", "pop", "unshift", "splice"]; | ||
function createObjectProxy(tree, value, path) { | ||
return new Proxy(value, { | ||
get(target, prop) { | ||
if (prop === IS_PROXY) return true; | ||
function proxifyArray(proxyStateTree, array, path) { | ||
return new Proxy(array, { | ||
get(target, prop) { | ||
if (prop === IS_PROXY) { | ||
return true; | ||
} | ||
const value = target[prop]; | ||
const nestedPath = concat(path, prop); | ||
const value = target[prop]; | ||
const nestedPath = path.concat(prop); | ||
if (tree.isTrackingPaths) { | ||
tree.paths.add(nestedPath); | ||
} | ||
if (proxyStateTree.isTrackingPaths) { | ||
proxyStateTree.paths.push(nestedPath); | ||
} | ||
if (typeof value === 'function') { | ||
return value(tree, nestedPath); | ||
} | ||
if (arrayMutations.indexOf(prop) >= 0) { | ||
if (!proxyStateTree.isTrackingMutations) { | ||
throw new Error( | ||
`proxy-state-tree - You are mutating the path "${path | ||
.concat(prop) | ||
.join(".")}", but it is not allowed` | ||
); | ||
} | ||
return (...args) => { | ||
proxyStateTree.mutations.push({ | ||
method: prop, | ||
path: path, | ||
args: args | ||
}); | ||
return (target[prop] = proxify(tree, value, nestedPath)); | ||
}, | ||
set(target, prop, value) { | ||
const nestedPath = concat(path, prop); | ||
return target[prop](...args); | ||
}; | ||
} | ||
if (!tree.isTrackingMutations) { | ||
throw new Error(`proxy-state-tree - You are mutating the path "${nestedPath}", but it is not allowed`); | ||
} | ||
tree.mutations.push({ | ||
method: 'set', | ||
path: nestedPath, | ||
args: [ value ] | ||
}); | ||
target[prop] = proxify(proxyStateTree, value, nestedPath); | ||
return (target[prop] = value); | ||
}, | ||
deleteProperty(target, prop) { | ||
const nestedPath = concat(path, prop); | ||
return target[prop]; | ||
} | ||
}); | ||
} | ||
tree.mutations.push({ | ||
method: 'unset', | ||
path: nestedPath, | ||
args: [] | ||
}); | ||
function proxify(proxyStateTree, value, path) { | ||
if (Array.isArray(value)) { | ||
return proxifyArray(proxyStateTree, value, path); | ||
} else if (isPlainObject(value)) { | ||
return proxifyObject(proxyStateTree, value, path); | ||
} | ||
delete target[prop]; | ||
return value; | ||
return true; | ||
} | ||
}); | ||
} | ||
class ProxyStateTree { | ||
constructor(state) { | ||
this.state = state; | ||
this.pathDependencies = {}; | ||
this.mutations = []; | ||
this.paths = []; | ||
this.isTrackingPaths = false; | ||
this.isTrackingMutations = false; | ||
this.proxy = proxify(this, state, []); | ||
function proxify(tree, value, path) { | ||
if (value) { | ||
if (value[IS_PROXY]) { | ||
return value; | ||
} else if (Array.isArray(value)) { | ||
return createArrayProxy(tree, value, path); | ||
} else if (isPlainObject(value)) { | ||
return createObjectProxy(tree, value, path); | ||
} | ||
} | ||
get() { | ||
return this.proxy; | ||
} | ||
startMutationTracking() { | ||
const currentMutations = this.mutations.slice(); | ||
return value; | ||
} | ||
this.isTrackingMutations = true; | ||
this.mutations.length = 0; | ||
class ProxyStateTree { | ||
constructor(state) { | ||
this.state = state; | ||
this.pathDependencies = {}; | ||
this.mutations = []; | ||
this.paths = new Set(); | ||
this.isTrackingPaths = false; | ||
this.isTrackingMutations = false; | ||
this.proxy = proxify(this, state); | ||
} | ||
get() { | ||
return this.proxy; | ||
} | ||
startMutationTracking() { | ||
const currentMutations = this.mutations.slice(); | ||
return currentMutations; | ||
} | ||
stopMutationTracking() { | ||
for (let callback in this.mutationCallbacks) { | ||
this.mutationCallbacks[callback](this.mutations); | ||
} | ||
for (let mutation in this.mutations) { | ||
const path = this.mutations[mutation].path.join('.'); | ||
if (this.pathDependencies[path]) { | ||
for (let pathCallback in this.pathDependencies[path]) { | ||
this.pathDependencies[path][pathCallback](); | ||
} | ||
} | ||
} | ||
this.isTrackingMutations = false; | ||
this.isTrackingMutations = true; | ||
this.mutations.length = 0; | ||
return this.mutations; | ||
} | ||
startPathsTracking() { | ||
const currentPaths = this.paths.slice(); | ||
return currentMutations; | ||
} | ||
stopMutationTracking() { | ||
for (let callback in this.mutationCallbacks) { | ||
this.mutationCallbacks[callback](this.mutations); | ||
} | ||
for (let mutation in this.mutations) { | ||
const path = this.mutations[mutation].path; | ||
if (this.pathDependencies[path]) { | ||
for (let pathCallback in this.pathDependencies[path]) { | ||
this.pathDependencies[path][pathCallback](); | ||
} | ||
} | ||
} | ||
this.isTrackingMutations = false; | ||
this.isTrackingPaths = true; | ||
this.paths.length = 0; | ||
return this.mutations; | ||
} | ||
startPathsTracking() { | ||
this.isTrackingPaths = true; | ||
const currentPaths = Array.from(this.paths); | ||
this.paths.clear(); | ||
return currentPaths; | ||
} | ||
stopPathsTracking() { | ||
this.isTrackingPaths = false; | ||
return Array.from(this.paths); | ||
} | ||
addMutationListener(initialPaths, cb) { | ||
const pathDependencies = this.pathDependencies; | ||
let currentStringPaths = initialPaths; | ||
return currentPaths; | ||
} | ||
stopPathsTracking() { | ||
this.isTrackingPaths = false; | ||
for (let index in currentStringPaths) { | ||
const currentStringPath = currentStringPaths[index]; | ||
pathDependencies[currentStringPath] = pathDependencies[currentStringPath] | ||
? pathDependencies[currentStringPath].concat(cb) | ||
: [cb]; | ||
} | ||
return this.paths; | ||
} | ||
addMutationListener(initialPaths, cb) { | ||
const pathDependencies = this.pathDependencies; | ||
let currentStringPaths = initialPaths.map((path) => path.join('.')); | ||
return { | ||
update(newPaths) { | ||
const newStringPaths = newPaths; | ||
for (let index in currentStringPaths) { | ||
const currentStringPath = currentStringPaths[index]; | ||
pathDependencies[currentStringPath] = pathDependencies[currentStringPath] | ||
? pathDependencies[currentStringPath].concat(cb) | ||
: [ cb ]; | ||
} | ||
for (let index in currentStringPaths) { | ||
const currentStringPath = currentStringPaths[index]; | ||
return { | ||
update(newPaths) { | ||
const newStringPaths = newPaths.map((path) => path.join('.')); | ||
if (newStringPaths.indexOf(currentStringPath) === -1) { | ||
pathDependencies[currentStringPath].splice( | ||
pathDependencies[currentStringPath].indexOf(cb), | ||
1 | ||
); | ||
} | ||
} | ||
for (let index in currentStringPaths) { | ||
const currentStringPath = currentStringPaths[index]; | ||
for (let index in newStringPaths) { | ||
const newStringPath = newStringPaths[index]; | ||
if (newStringPaths.indexOf(currentStringPath) === -1) { | ||
pathDependencies[currentStringPath].splice(pathDependencies[currentStringPath].indexOf(cb), 1); | ||
} | ||
} | ||
if (currentStringPaths.indexOf(newStringPath) === -1) { | ||
pathDependencies[newStringPath] = pathDependencies[newStringPath] | ||
? pathDependencies[newStringPath].concat(cb) | ||
: [cb]; | ||
} | ||
} | ||
for (let index in newStringPaths) { | ||
const newStringPath = newStringPaths[index]; | ||
currentStringPaths = newStringPaths; | ||
}, | ||
dispose() { | ||
for (let index in currentStringPaths) { | ||
const currentStringPath = currentStringPaths[index]; | ||
if (currentStringPaths.indexOf(newStringPath) === -1) { | ||
pathDependencies[newStringPath] = pathDependencies[newStringPath] | ||
? pathDependencies[newStringPath].concat(cb) | ||
: [ cb ]; | ||
} | ||
} | ||
pathDependencies[currentStringPath].splice( | ||
pathDependencies[currentStringPath].indexOf(cb), | ||
1 | ||
); | ||
currentStringPaths = newStringPaths; | ||
}, | ||
dispose() { | ||
for (let index in currentStringPaths) { | ||
const currentStringPath = currentStringPaths[index]; | ||
pathDependencies[currentStringPath].splice(pathDependencies[currentStringPath].indexOf(cb), 1); | ||
if (!pathDependencies[currentStringPath].length) { | ||
delete pathDependencies[currentStringPath]; | ||
} | ||
} | ||
} | ||
}; | ||
} | ||
if (!pathDependencies[currentStringPath].length) { | ||
delete pathDependencies[currentStringPath]; | ||
} | ||
} | ||
} | ||
}; | ||
} | ||
} | ||
export default ProxyStateTree; | ||
export { IS_PROXY }; |
{ | ||
"name": "proxy-state-tree", | ||
"version": "1.0.0-alpha3", | ||
"version": "1.0.0-alpha4", | ||
"description": "An implementation of the Mobx/Vue state tracking approach, for library authors", | ||
@@ -13,3 +13,4 @@ "main": "dist/proxy-state-tree.cjs.js", | ||
"build:es": "rollup src/index.js --file dist/proxy-state-tree.es.js --format es", | ||
"test": "jest" | ||
"test": "jest", | ||
"test:perf": "node --expose-gc node_modules/jest/bin/jest.js --testRegex 'benchmarks/.*?js$'" | ||
}, | ||
@@ -40,5 +41,9 @@ "repository": { | ||
"jest": "^23.1.0", | ||
"lodash.clonedeep": "^4.5.0", | ||
"prettier": "^1.13.5", | ||
"rollup": "^0.60.7" | ||
}, | ||
"dependencies": { | ||
"is-plain-object": "^2.0.4" | ||
} | ||
} |
133
README.md
@@ -6,10 +6,15 @@ # proxy-state-tree | ||
`npm install proxy-state-tree@alpha` | ||
## Why | ||
There are two main approaches to change detection. **immutability** and **setter/getter interception**. Immutability is easy to implement as a library author cause it is really all about comparing values. It is less work for you as a library author, but more work for the developers using the tool. The **setter/getter** approach, popularized by projects like [mobx](), [mobx-state-tree]() and [vuejs](), was traditionally more work for you as a library author, but far less work for the developers consuming your tool. The **setter/getter** approach also has two other prominent benefits: | ||
The **proxy-state-tree** project is created to stimulate innovation in state management. The introduction of [Flux](https://facebook.github.io/flux/) was followed by a big wave of libraries trying to improve on the idea. All these iterations helped moving the community forward and [Redux](https://redux.js.org/) was born a year later. It was frustrating to have all these variations of the same idea, but at the same time it made the core idea better. One factor I believe made this possible is that Flux state management is based on **immutability**. It is a difficult concept to understand, but when you understand it, it is easy to implement the concept of **change**. You literally just check if a value you depend on has changed. That said, immutability tends to put a lof effort on the hands of the consumer. You have to think really hard about how you structure state and expose state to components to avoid performance issues and prevent boilerplate. | ||
- You mutate your state as normal. No special immutable API or writing your changes in an immutable way | ||
- Rerendering and recalculation is optimized out of the box as you track exactly the state that is being used | ||
[vuejs](https://vuejs.org/) and [mobx](https://github.com/mobxjs/mobx) has a different approach to **change**. They use **getter/setter interception** to track access to state and changes to state. This concept completely removes the consumers burden of how the state is structured and how it is exposed to the different parts of the app. You just expose state in any form and the usage is automatically tracked and optimized. The problem with this approach though is that it is difficult to implement as a library author. **I want to change that!** | ||
**proxy-state-tree** allows you to expose a single state tree to your library and track its usage and changes. | ||
**proxy-state-tree** is a low level implementation of the **getter/setter interception** with a **single state tree** to help library authors innovate. I hope to see innovations that removes the burden that immutability currently causes, but keeps the guarantees that was introduced in **Flux**. I invite you to make a mobx and redux baby! ;-) | ||
## Example | ||
You can look at an example of how you could build a state-tree implementation for [vuejs](https://vuejs.org/) on this [codesandbox](https://codesandbox.io/s/5vy5jxrpop). It is a simple implementation that allows you to define a state tree and expose actions to the components. There are many ways to do this, so this is just one example of how proxy-state-tree integrates with existing solutions. You might also imagine additional features here for computing state in the tree, creating reactions in the components etc. | ||
## Create a tree | ||
@@ -29,3 +34,3 @@ | ||
You can track access to the state by using the **trackPaths** method. | ||
You can track access to the state by using the **startPathsTracking** and **stopPathsTracking** methods. | ||
@@ -41,6 +46,6 @@ ```js | ||
const paths = tree.trackPaths(() => { | ||
const foo = state.foo | ||
const bar = state.bar | ||
}) | ||
tree.startPathsTracking() | ||
const foo = state.foo | ||
const bar = state.bar | ||
const paths = tree.stopPathsTracking() | ||
@@ -50,3 +55,3 @@ console.log(paths) // [['foo'], ['bar']] | ||
You would typically use this mechanism to track usage of state. For example rendering a component, calculating a a computed value etc. The returned paths array is stored for later usage. | ||
You would typically use this mechanism to track usage of state. For example rendering a component, calculating a a computed value etc. The returned paths array is stored for later usage. The paths structure is used internally by proxy-state-tree, but you can also consume it as a library author to for example showing components and what paths they depend on in a devtool. | ||
@@ -64,6 +69,6 @@ ## Track mutations | ||
const mutations = tree.trackMutations(() => { | ||
state.foo = 'bar2' | ||
state.bar.push('baz') | ||
}) | ||
tree.startMutationTracking() | ||
state.foo = 'bar2' | ||
state.bar.push('baz') | ||
const mutations = tree.stopMutationTracking() | ||
@@ -84,39 +89,4 @@ console.log(mutations) | ||
You would use **trackMutations** around logic that is allowed to do mutations, for example actions or similar. | ||
You would use **startMutationTracking** and **stopMutationTracking** around logic that is allowed to do mutations, for example actions or similar. Internally **proxy-state-tree** will notify all mutation listeners about updated state, but you can also use this structure in combination with a devtool. Show a list of mutations that occurs in your app, and what action performed the mutation even. | ||
## React to mutations | ||
```js | ||
import ProxyStateTree from 'proxy-state-tree' | ||
const tree = new ProxyStateTree({ | ||
foo: 'bar', | ||
bar: [] | ||
}) | ||
tree.onMutation((mutations) => { | ||
mutations | ||
/* | ||
[{ | ||
method: 'set', | ||
path: ['foo'], | ||
args: ['bar2'] | ||
}, { | ||
method: 'push', | ||
path: ['bar'], | ||
args: ['baz'] | ||
}] | ||
*/ | ||
}) | ||
const state = tree.get() | ||
const mutations = tree.trackMutations(() => { | ||
state.foo = 'bar2' | ||
state.bar.push('baz') | ||
}) | ||
``` | ||
You would typically expose onMutation to parts of your application that has tracked some state. For example a component, a computed etc. This allows parts of the system to react to mutations. | ||
## Check need to update | ||
@@ -134,14 +104,15 @@ | ||
function render () { | ||
return tree.trackPaths(() => { | ||
const foo = state.foo | ||
const bar = state.bar | ||
}) | ||
tree.startPathsTracking() | ||
const foo = state.foo | ||
const bar = state.bar | ||
return tree.stopPathsTracking() | ||
} | ||
const listener = tree.addMutationListener(render(), (mutations) => { | ||
const listener = tree.addMutationListener(render(), () => { | ||
// Runs when mutations matches paths passed in | ||
// Update listener with new paths. Typically you track | ||
// a new set of paths on mutation change, to pick up changes | ||
// to the paths. If statements etc. causes this | ||
// Whenever mutations affecting these paths occurs | ||
// we typically create the paths again due to possible | ||
// conditional logic, in "render" in this example | ||
listener.update(render()) | ||
@@ -153,13 +124,13 @@ | ||
tree.trackMutations(() => { | ||
state.foo = 'bar2' | ||
state.bar.push('baz') | ||
}) | ||
tree.startMutationTracking() | ||
state.foo = 'bar2' | ||
state.bar.push('baz') | ||
tree.stopMutationTracking() | ||
``` | ||
Here we combine the tracked paths with the mutations performed to see if this components, computed or whatever indeed needs to run again, doing a new **trackPaths**. | ||
Here we combine the tracked paths with the mutations performed to see if this components, computed or whatever indeed needs to run again, doing a new **startPathsTracking** and **stopPathsTracking**. | ||
## Dynamic state values | ||
If you insert a function into the state tree it will be called when accessed. The function is passed the **proxy-state-tree** instance and the path of where the function lives in the tree. The allows you to easily extend functionality with for example a computed concept: | ||
If you insert a function into the state tree it will be called when accessed. The function is passed the **proxy-state-tree** instance and the path of where the function lives in the tree. | ||
@@ -169,37 +140,7 @@ ```js | ||
class Computed { | ||
constructor(cb) { | ||
this.isDirty = true; | ||
this.discardOnMutation = null; | ||
this.value = null; | ||
this.paths = null; | ||
this.cb = cb; | ||
return this.evaluate.bind(this); | ||
} | ||
evaluate(proxyStateTree, path) { | ||
if (!this.discardOnMutation) { | ||
this.discardOnMutation = proxyStateTree.onMutation(mutations => { | ||
if (!this.isDirty) { | ||
this.isDirty = proxyStateTree.hasMutated(this.paths, mutations); | ||
} | ||
}); | ||
} | ||
if (this.isDirty) { | ||
this.paths = proxyStateTree.trackPaths(() => { | ||
this.value = this.cb(proxyStateTree.get()); | ||
this.isDirty = false; | ||
}); | ||
} | ||
return this.value; | ||
} | ||
} | ||
const tree = new ProxyStateTree({ | ||
foo: 'bar', | ||
upperFoo: new Computed(state => state.foo.toUpperCase()), | ||
foo: (proxyStateTree, path) => {} | ||
}) | ||
tree.get().upperFoo // 'BAR' | ||
``` | ||
And this computed only recalculates if "foo" changes. | ||
The allows you to easily extend functionality with for example a computed concept that lives in the tree, as you can see in this [codesandbox](https://codesandbox.io/s/xnv45zmkz). |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
20704
6
396
1
15
137
1
+ Addedis-plain-object@^2.0.4
+ Addedis-plain-object@2.0.4(transitive)
+ Addedisobject@3.0.1(transitive)