Comparing version 0.13.0 to 1.0.0-beta
@@ -26,2 +26,4 @@ /** | ||
mReact(f: (value: T) => void, options?: Lifecycle): void; | ||
get(): T; | ||
@@ -48,5 +50,2 @@ | ||
withEquality(equals: (a: any, b: any) => boolean): this; | ||
reactor(r: Reactor<T>): Reactor<T>; | ||
reactor(f: (value: T) => void): Reactor<T>; | ||
} | ||
@@ -88,31 +87,4 @@ | ||
once?: boolean; | ||
onStart?: () => void; | ||
onStop?: () => void; | ||
} | ||
export class Reactor<T> { | ||
constructor (); | ||
start(): Reactor<T>; | ||
stop(): Reactor<T>; | ||
force(): Reactor<T>; | ||
isActive(): boolean; | ||
orphan(): Reactor<T>; | ||
adopt(child: Reactor<any>): Reactor<T>; | ||
react(value: T): void; | ||
onStart(): void; | ||
onStop(): void; | ||
} | ||
function atom<T>(value: T): Atom<T>; | ||
@@ -148,4 +120,2 @@ | ||
function isReactor(obj: any): boolean; | ||
function derive(strings: string[], ...things: any[]): Derivable<string>; | ||
@@ -161,5 +131,5 @@ | ||
function withEquality(equals: (a: any, b: any) => boolean): any; | ||
function wrapPreviousState<A, B>(fn: (currentState: A, previousState: A) => B, init?: A): (currentState: A) => B; | ||
function defaultEquals(a: any, b: any): boolean; | ||
function captureDereferences(fn: () => void): Derivable<any>[]; | ||
@@ -166,0 +136,0 @@ function setDebugMode(debugMode: boolean): void; |
@@ -1,22 +0,13 @@ | ||
// UMD loader | ||
(function (global, factory) { | ||
"use strict"; | ||
if (global && typeof global.define === "function" && global.define.amd) { | ||
global.define(["exports"], factory); | ||
} else if (typeof exports !== "undefined") { | ||
factory(exports); | ||
} else { | ||
factory(global.Derivable = {}); | ||
} | ||
})(this, function (exports) { | ||
"use strict"; | ||
'use strict'; | ||
var util_keys = Object.keys; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
function util_extend(obj) { | ||
var keys = Object.keys; | ||
function assign (obj) { | ||
for (var i = 1; i < arguments.length; i++) { | ||
var other = arguments[i]; | ||
var keys = util_keys(other); | ||
for (var j = keys.length; j--;) { | ||
var prop = keys[j]; | ||
var ks = keys(other || {}); | ||
for (var j = ks.length; j--;) { | ||
var prop = ks[j]; | ||
obj[prop] = other[prop]; | ||
@@ -39,7 +30,7 @@ } | ||
function util_equals (a, b) { | ||
function equals (a, b) { | ||
return _is(a, b) || (a && typeof a.equals === 'function' && a.equals(b)); | ||
} | ||
}; | ||
function util_addToArray (a, b) { | ||
function addToArray (a, b) { | ||
var i = a.indexOf(b); | ||
@@ -49,5 +40,5 @@ if (i < 0) { | ||
} | ||
} | ||
}; | ||
function util_removeFromArray (a, b) { | ||
function removeFromArray (a, b) { | ||
var i = a.indexOf(b); | ||
@@ -57,65 +48,143 @@ if (i >= 0) { | ||
} | ||
} | ||
}; | ||
var nextId = 0; | ||
function util_nextId () { | ||
return nextId++; | ||
} | ||
var _nextId = 0; | ||
function nextId () { | ||
return _nextId++; | ||
}; | ||
function util_slice (a, i) { | ||
function slice (a, i) { | ||
return Array.prototype.slice.call(a, i); | ||
} | ||
}; | ||
var util_unique = Object.freeze({equals: function () { return false; }}); | ||
var unique = Object.freeze({equals: function () { return false; }}); | ||
function util_some (x) { | ||
function some (x) { | ||
return (x !== null) && (x !== void 0); | ||
} | ||
}; | ||
var util_DEBUG_MODE = false; | ||
function util_setDebugMode(val) { | ||
util_DEBUG_MODE = !!val; | ||
} | ||
var DEBUG_MODE = false; | ||
function setDebugMode$1 (val) { | ||
DEBUG_MODE = !!val; | ||
}; | ||
function util_setEquals(derivable, equals) { | ||
function setEquals (derivable, equals) { | ||
derivable._equals = equals; | ||
return derivable; | ||
}; | ||
var ATOM = "ATOM"; | ||
var DERIVATION = "DERIVATION"; | ||
var LENS = "LENS"; | ||
var REACTOR = "REACTOR"; | ||
function isDerivable$1(x) { | ||
return x && | ||
(x._type === DERIVATION || | ||
x._type === ATOM || | ||
x._type === LENS); | ||
} | ||
var epoch_globalEpoch = 0; | ||
function isAtom$1 (x) { | ||
return x && (x._type === ATOM || x._type === LENS); | ||
} | ||
function isDerivation$1 (x) { | ||
return x && (x._type === DERIVATION || x._type === LENS); | ||
} | ||
function isLensed$1 (x) { | ||
return x && x._type === LENS; | ||
} | ||
var UNKNOWN = 0; | ||
var CHANGED = 1; | ||
var UNCHANGED = 2; | ||
var DISCONNECTED = 3; | ||
var parentsStack = []; | ||
var child = null; | ||
function parents_capturingParentsEpochs(f) { | ||
var i = parentsStack.length; | ||
parentsStack.push([]); | ||
try { | ||
f(); | ||
return parentsStack[i]; | ||
} finally { | ||
parentsStack.pop(); | ||
} | ||
function startCapturingParents (_child, parents) { | ||
parentsStack.push({parents: parents, offset: 0, child: _child}); | ||
child = _child; | ||
} | ||
function retrieveParentsFrame () { | ||
return parentsStack[parentsStack.length - 1]; | ||
} | ||
function stopCapturingParents () { | ||
parentsStack.pop(); | ||
child = parentsStack.length === 0 | ||
? null | ||
: parentsStack[parentsStack.length - 1].child; | ||
} | ||
function parents_captureParent(p) { | ||
if (parentsStack.length > 0) { | ||
var top = parentsStack[parentsStack.length - 1]; | ||
top.push(p, 0); | ||
return top.length-1; | ||
} else { | ||
return -1; | ||
function maybeCaptureParent (p) { | ||
if (child !== null) { | ||
var frame = parentsStack[parentsStack.length - 1]; | ||
if (frame.parents[frame.offset] === p) { | ||
// nothing to do, just skip over | ||
frame.offset++; | ||
} else { | ||
// look for this parent elsewhere | ||
var idx = frame.parents.indexOf(p); | ||
if (idx === -1) { | ||
// not seen this parent yet, add it in the correct place | ||
// and push the one currently there to the end (likely that we'll be | ||
// getting rid of it) | ||
// sneaky hack for doing captureDereferences | ||
if (child !== void 0) { | ||
addToArray(p._activeChildren, child); | ||
} | ||
if (frame.offset === frame.parents.length) { | ||
frame.parents.push(p); | ||
} else { | ||
frame.parents.push(frame.parents[frame.offset]); | ||
frame.parents[frame.offset] = p; | ||
} | ||
frame.offset++; | ||
} else { | ||
if (idx > frame.offset) { | ||
// seen this parent after current point in array, so swap positions | ||
// with current point's parent | ||
var tmp = frame.parents[idx]; | ||
frame.parents[idx] = frame.parents[frame.offset]; | ||
frame.parents[frame.offset] = tmp; | ||
frame.offset++; | ||
} | ||
// else seen this parent at previous point and so don't increment offset | ||
} | ||
} | ||
} | ||
}; | ||
function mark (node, reactors) { | ||
for (var i = 0, len = node._activeChildren.length; i < len; i++) { | ||
var child = node._activeChildren[i]; | ||
switch (child._type) { | ||
case DERIVATION: | ||
case LENS: | ||
if (child._state !== UNKNOWN) { | ||
child._state = UNKNOWN; | ||
mark(child, reactors); | ||
} | ||
break; | ||
case REACTOR: | ||
reactors.push(child); | ||
break; | ||
} | ||
} | ||
} | ||
function parents_captureEpoch(idx, epoch) { | ||
if (parentsStack.length > 0) { | ||
parentsStack[parentsStack.length - 1][idx] = epoch; | ||
function processReactors (reactors) { | ||
for (var i = 0, len = reactors.length; i < len; i++) { | ||
var r = reactors[i]; | ||
if (r._reacting) { | ||
throw new Error("Synchronous cyclical reactions disallowed. " + | ||
"Use setImmediate."); | ||
} | ||
r._maybeReact(); | ||
} | ||
} | ||
var types_ATOM = "ATOM", | ||
types_DERIVATION = "DERIVATION", | ||
types_LENS = "LENS", | ||
types_REACTION = "REACTION"; | ||
var TransactionAbortion = {}; | ||
@@ -129,14 +198,22 @@ | ||
this.parent = parent; | ||
this.id2txnAtom = {}; | ||
this.globalEpoch = epoch_globalEpoch; | ||
this.id2originalValue = {}; | ||
this.modifiedAtoms = []; | ||
} | ||
var transactions_currentCtx = null; | ||
function transactions_inTransaction () { | ||
return transactions_currentCtx !== null; | ||
function maybeTrack (atom) { | ||
if (currentCtx !== null) { | ||
if (!(atom._id in currentCtx.id2originalValue)) { | ||
currentCtx.modifiedAtoms.push(atom); | ||
currentCtx.id2originalValue[atom._id] = atom._value; | ||
} | ||
} | ||
} | ||
function transactions_transact (f) { | ||
var currentCtx = null; | ||
function inTransaction () { | ||
return currentCtx !== null; | ||
}; | ||
function transact$1 (f) { | ||
beginTransaction(); | ||
@@ -154,80 +231,228 @@ try { | ||
commitTransaction(); | ||
}; | ||
function atomically$1 (f) { | ||
if (!inTransaction()) { | ||
transact$1(f); | ||
} else { | ||
f(); | ||
} | ||
} | ||
function transaction$1 (f) { | ||
return function () { | ||
var args = slice(arguments, 0); | ||
var that = this; | ||
var result; | ||
transact$1(function () { | ||
result = f.apply(that, args); | ||
}); | ||
return result; | ||
}; | ||
}; | ||
function atomic$1 (f) { | ||
return function () { | ||
var args = slice(arguments, 0); | ||
var that = this; | ||
var result; | ||
atomically$1(function () { | ||
result = f.apply(that, args); | ||
}); | ||
return result; | ||
}; | ||
} | ||
function beginTransaction() { | ||
transactions_currentCtx = new TransactionContext(transactions_currentCtx); | ||
currentCtx = new TransactionContext(currentCtx); | ||
} | ||
function commitTransaction() { | ||
var ctx = transactions_currentCtx; | ||
transactions_currentCtx = ctx.parent; | ||
var reactorss = []; | ||
ctx.modifiedAtoms.forEach(function (a) { | ||
if (transactions_currentCtx !== null) { | ||
a.set(ctx.id2txnAtom[a._id]._value); | ||
} | ||
else { | ||
a._set(ctx.id2txnAtom[a._id]._value); | ||
reactorss.push(a._reactors); | ||
} | ||
}); | ||
if (transactions_currentCtx === null) { | ||
epoch_globalEpoch = ctx.globalEpoch; | ||
} else { | ||
transactions_currentCtx.globalEpoch = ctx.globalEpoch; | ||
var ctx = currentCtx; | ||
currentCtx = ctx.parent; | ||
if (currentCtx === null) { | ||
var reactors = []; | ||
ctx.modifiedAtoms.forEach(function (a) { | ||
if (a.__equals(a._value, ctx.id2originalValue[a._id])) { | ||
a._state = UNCHANGED; | ||
} else { | ||
a._state = CHANGED; | ||
mark(a, reactors); | ||
} | ||
}); | ||
processReactors(reactors); | ||
ctx.modifiedAtoms.forEach(function (a) { | ||
a._state = UNCHANGED; | ||
}); | ||
} | ||
reactorss.forEach(function (reactors) { | ||
reactors.forEach(function (r) { | ||
r._maybeReact(); | ||
}); | ||
}); | ||
} | ||
function abortTransaction() { | ||
var ctx = transactions_currentCtx; | ||
transactions_currentCtx = ctx.parent; | ||
if (transactions_currentCtx === null) { | ||
epoch_globalEpoch = ctx.globalEpoch + 1; | ||
} | ||
else { | ||
transactions_currentCtx.globalEpoch = ctx.globalEpoch + 1; | ||
} | ||
var ctx = currentCtx; | ||
currentCtx = ctx.parent; | ||
ctx.modifiedAtoms.forEach(function (atom) { | ||
atom._value = ctx.id2originalValue[atom._id]; | ||
atom._state = UNCHANGED; | ||
mark(atom, []); | ||
}); | ||
} | ||
function transactions_ticker () { | ||
beginTransaction(); | ||
var disposed = false; | ||
var _tickerRefCount = 0; | ||
function ticker$1 () { | ||
if (_tickerRefCount === 0) { | ||
beginTransaction(); | ||
} | ||
_tickerRefCount++; | ||
var done = false; | ||
return { | ||
tick: function () { | ||
if (disposed) throw new Error("can't tick disposed ticker"); | ||
if (done) throw new Error('trying to use ticker after release'); | ||
commitTransaction(); | ||
beginTransaction(); | ||
}, | ||
stop: function () { | ||
if (disposed) throw new Error("ticker already disposed"); | ||
disposed = true; | ||
commitTransaction(); | ||
}, | ||
resetState: function () { | ||
if (disposed) throw new Error("ticker already disposed"); | ||
reset: function () { | ||
if (done) throw new Error('trying to use ticker after release'); | ||
abortTransaction(); | ||
beginTransaction(); | ||
}, | ||
release: function () { | ||
if (done) throw new Error('ticker already released'); | ||
_tickerRefCount--; | ||
done = true; | ||
if (_tickerRefCount === 0) { | ||
commitTransaction(); | ||
} | ||
}, | ||
}; | ||
}; | ||
function Derivation (deriver) { | ||
this._deriver = deriver; | ||
this._parents = null; | ||
this._type = DERIVATION; | ||
this._value = unique; | ||
this._equals = null; | ||
this._activeChildren = []; | ||
this._state = DISCONNECTED; | ||
if (DEBUG_MODE) { | ||
this.stack = Error().stack; | ||
} | ||
}; | ||
assign(Derivation.prototype, { | ||
_clone: function () { | ||
return setEquals(_derivation(this._deriver), this._equals); | ||
}, | ||
_forceEval: function () { | ||
var that = this; | ||
var newVal = null; | ||
var newNumParents; | ||
try { | ||
if (this._parents === null) { | ||
this._parents = []; | ||
} | ||
startCapturingParents(this, this._parents); | ||
if (!DEBUG_MODE) { | ||
newVal = that._deriver(); | ||
} else { | ||
try { | ||
newVal = that._deriver(); | ||
} catch (e) { | ||
console.error(that.stack); | ||
throw e; | ||
} | ||
} | ||
newNumParents = retrieveParentsFrame().offset; | ||
} finally { | ||
stopCapturingParents(); | ||
} | ||
if (!this.__equals(newVal, this._value)) { | ||
this._state = CHANGED; | ||
} else { | ||
this._state = UNCHANGED; | ||
} | ||
for (var i = newNumParents, len = this._parents.length; i < len; i++) { | ||
var oldParent = this._parents[i]; | ||
detach(oldParent, this); | ||
this._parents[i] = null; | ||
} | ||
this._parents.length = newNumParents; | ||
this._value = newVal; | ||
}, | ||
_update: function () { | ||
if (this._parents === null) { | ||
// this._state === DISCONNECTED | ||
this._forceEval(); | ||
// this._state === CHANGED ? | ||
} else if (this._state === UNKNOWN) { | ||
var len = this._parents.length; | ||
for (var i = 0; i < len; i++) { | ||
var parent = this._parents[i]; | ||
if (parent._state === UNKNOWN) { | ||
parent._update(); | ||
} | ||
if (parent._state === CHANGED) { | ||
this._forceEval(); | ||
break; | ||
} | ||
} | ||
if (this._state === UNKNOWN) { | ||
this._state = UNCHANGED; | ||
} | ||
} | ||
}, | ||
get: function () { | ||
maybeCaptureParent(this); | ||
if (this._activeChildren.length > 0) { | ||
this._update(); | ||
} else { | ||
startCapturingParents(void 0, []); | ||
try { | ||
this._value = this._deriver(); | ||
} finally { | ||
stopCapturingParents(); | ||
} | ||
} | ||
return this._value; | ||
}, | ||
}); | ||
function detach (parent, child) { | ||
removeFromArray(parent._activeChildren, child); | ||
if (parent._activeChildren.length === 0 && parent._parents != null) { | ||
var len = parent._parents.length; | ||
for (var i = 0; i < len; i++) { | ||
detach(parent._parents[i], parent); | ||
} | ||
parent._parents = null; | ||
parent._state = DISCONNECTED; | ||
} | ||
} | ||
var reactorParentStack = []; | ||
function _derivation (deriver) { | ||
return new Derivation(deriver); | ||
} | ||
function Reactor(react, derivable) { | ||
this._derivable = derivable; | ||
if (react) { | ||
this.react = react; | ||
} | ||
this._atoms = []; | ||
this._parent = null; | ||
function Reactor(parent, react, governor) { | ||
this._parent = parent; | ||
this.react = react; | ||
this._governor = governor || null; | ||
this._active = false; | ||
this._yielding = false; | ||
this._reacting = false; | ||
this._type = types_REACTION; | ||
if (util_DEBUG_MODE) { | ||
this._type = REACTOR; | ||
if (DEBUG_MODE) { | ||
this.stack = Error().stack; | ||
@@ -237,37 +462,18 @@ } | ||
var reactors_Reactor = Reactor; | ||
assign(Reactor.prototype, { | ||
start: function () { | ||
this._active = true; | ||
function bindAtomsToReactors(derivable, reactor) { | ||
if (derivable._type === types_ATOM) { | ||
util_addToArray(derivable._reactors, reactor); | ||
util_addToArray(reactor._atoms, derivable); | ||
} | ||
else { | ||
for (var i = 0, len = derivable._lastParentsEpochs.length; i < len; i += 2) { | ||
bindAtomsToReactors(derivable._lastParentsEpochs[i], reactor); | ||
} | ||
} | ||
} | ||
addToArray(this._parent._activeChildren, this); | ||
Object.assign(reactors_Reactor.prototype, { | ||
start: function () { | ||
this._lastValue = this._derivable.get(); | ||
this._lastEpoch = this._derivable._epoch; | ||
this._atoms = []; | ||
bindAtomsToReactors(this._derivable, this); | ||
var len = reactorParentStack.length; | ||
if (len > 0) { | ||
this._parent = reactorParentStack[len - 1]; | ||
} | ||
this._active = true; | ||
this.onStart && this.onStart(); | ||
this._parent.get(); | ||
return this; | ||
}, | ||
_force: function (nextValue) { | ||
try { | ||
reactorParentStack.push(this); | ||
this._reacting = true; | ||
this.react(nextValue); | ||
} catch (e) { | ||
if (util_DEBUG_MODE) { | ||
if (DEBUG_MODE) { | ||
console.error(this.stack); | ||
@@ -278,750 +484,591 @@ } | ||
this._reacting = false; | ||
reactorParentStack.pop(); | ||
} | ||
}, | ||
force: function () { | ||
this._force(this._derivable.get()); | ||
this._force(this._parent.get()); | ||
return this; | ||
}, | ||
_maybeReact: function () { | ||
if (this._reacting) { | ||
throw Error('cyclical update detected!!'); | ||
} else if (this._active) { | ||
if (this._yielding) { | ||
throw Error('reactor dependency cycle detected'); | ||
if (!this._reacting && this._active) { | ||
if (this._governor !== null) { | ||
this._governor._maybeReact(); | ||
} | ||
if (this._parent !== null) { | ||
this._yielding = true; | ||
this._parent._maybeReact(); | ||
this._yielding = false; | ||
// maybe the reactor was stopped by the parent | ||
if (this._active) { | ||
var nextValue = this._parent.get(); | ||
if (this._parent._state === CHANGED) { | ||
this._force(nextValue); | ||
} | ||
} | ||
var nextValue = this._derivable.get(); | ||
if (this._derivable._epoch !== this._lastEpoch && | ||
!this._derivable.__equals(nextValue, this._lastValue)) { | ||
this._force(nextValue); | ||
} | ||
this._lastEpoch = this._derivable._epoch; | ||
this._lastValue = nextValue; | ||
} | ||
}, | ||
stop: function () { | ||
var _this = this; | ||
this._atoms.forEach(function (atom) { | ||
return util_removeFromArray(atom._reactors, _this); | ||
}); | ||
this._atoms = []; | ||
this._parent = null; | ||
detach(this._parent, this); | ||
this._active = false; | ||
this.onStop && this.onStop(); | ||
return this; | ||
}, | ||
orphan: function () { | ||
this._parent = null; | ||
return this; | ||
}, | ||
adopt: function (child) { | ||
child._parent = this; | ||
return this; | ||
}, | ||
isActive: function () { | ||
return this._active; | ||
}, | ||
}); | ||
function derivable_createPrototype (D, opts) { | ||
var x = { | ||
/** | ||
* Creates a derived value whose state will always be f applied to this | ||
* value | ||
*/ | ||
derive: function (f, a, b, c, d) { | ||
var that = this; | ||
switch (arguments.length) { | ||
case 0: | ||
return that; | ||
case 1: | ||
switch (typeof f) { | ||
case 'function': | ||
return D.derivation(function () { | ||
return f(that.get()); | ||
}); | ||
case 'string': | ||
case 'number': | ||
return D.derivation(function () { | ||
return that.get()[D.unpack(f)]; | ||
}); | ||
default: | ||
if (f instanceof Array) { | ||
return f.map(function (x) { | ||
return that.derive(x); | ||
}); | ||
} else if (f instanceof RegExp) { | ||
return D.derivation(function () { | ||
return that.get().match(f); | ||
}); | ||
} else if (D.isDerivable(f)) { | ||
return D.derivation(function () { | ||
var deriver = f.get(); | ||
var thing = that.get(); | ||
switch (typeof deriver) { | ||
case 'function': | ||
return deriver(thing); | ||
case 'string': | ||
case 'number': | ||
return thing[deriver]; | ||
default: | ||
if (deriver instanceof RegExp) { | ||
return thing.match(deriver); | ||
} else { | ||
throw Error('type error'); | ||
} | ||
} | ||
return that.get()[D.unpack(f)]; | ||
}); | ||
} else { | ||
throw Error('type error'); | ||
} | ||
} | ||
break; | ||
case 2: | ||
return D.derivation(function () { | ||
return f(that.get(), D.unpack(a)); | ||
}); | ||
case 3: | ||
return D.derivation(function () { | ||
return f(that.get(), D.unpack(a), D.unpack(b)); | ||
}); | ||
case 4: | ||
return D.derivation(function () { | ||
return f(that.get(), | ||
D.unpack(a), | ||
D.unpack(b), | ||
D.unpack(c)); | ||
}); | ||
case 5: | ||
return D.derivation(function () { | ||
return f(that.get(), | ||
D.unpack(a), | ||
D.unpack(b), | ||
D.unpack(c), | ||
D.unpack(d)); | ||
}); | ||
default: | ||
var args = ([that]).concat(util_slice(arguments, 1)); | ||
return D.derivation(function () { | ||
return f.apply(null, args.map(D.unpack)); | ||
}); | ||
} | ||
}, | ||
function makeReactor (derivable, f, opts) { | ||
if (typeof f !== 'function') { | ||
throw Error('the first argument to .react must be a function'); | ||
} | ||
opts = assign({ | ||
once: false, | ||
from: true, | ||
until: false, | ||
when: true, | ||
skipFirst: false, | ||
}, opts); | ||
var skipFirst = opts.skipFirst; | ||
reactor: function (f) { | ||
if (typeof f === 'function') { | ||
return new reactors_Reactor(f, this); | ||
} else if (f instanceof reactors_Reactor) { | ||
if (typeof f.react !== 'function') { | ||
throw new Error('reactor missing .react method'); | ||
} | ||
f._derivable = this; | ||
return f; | ||
} else if (f && f.react) { | ||
return Object.assign(new reactors_Reactor(null, this), f); | ||
// coerce fn or bool to derivable<bool> | ||
function condDerivable(fOrD, name) { | ||
if (!isDerivable$1(fOrD)) { | ||
if (typeof fOrD === 'function') { | ||
return _derivation(fOrD); | ||
} else if (typeof fOrD === 'boolean') { | ||
return _derivation(function () { return fOrD; }); | ||
} else { | ||
throw new Error("Unrecognized type for reactor " + f); | ||
throw Error('react ' + name + ' condition must be derivable, got: ' + JSON.stringify(fOrD)); | ||
} | ||
}, | ||
} | ||
return fOrD; | ||
} | ||
react: function (f, opts) { | ||
if (typeof f !== 'function') { | ||
throw Error('the first argument to .react must be a function'); | ||
// wrap reactor so f doesn't get a .this context, and to allow | ||
// stopping after one reaction if desired. | ||
var reactor = new Reactor(derivable, function (val) { | ||
if (skipFirst) { | ||
skipFirst = false; | ||
} else { | ||
f(val); | ||
if (opts.once) { | ||
this.stop(); | ||
controller.stop(); | ||
} | ||
} | ||
}); | ||
opts = Object.assign({ | ||
once: false, | ||
from: true, | ||
until: false, | ||
when: true, | ||
skipFirst: false, | ||
}, opts); | ||
// listen to when and until conditions, starting and stopping the | ||
// reactor as appropriate, and stopping this controller when until | ||
// condition becomes true | ||
var $until = condDerivable(opts.until, 'until'); | ||
var $when = condDerivable(opts.when, 'when'); | ||
// coerce fn or bool to derivable<bool> | ||
function condDerivable(fOrD, name) { | ||
if (!D.isDerivable(fOrD)) { | ||
if (typeof fOrD === 'function') { | ||
fOrD = D.derivation(fOrD); | ||
} else if (typeof fOrD === 'boolean') { | ||
fOrD = D.atom(fOrD); | ||
} else { | ||
throw Error('react ' + name + ' condition must be derivable'); | ||
} | ||
} | ||
return fOrD.derive(function (x) { return !!x; }); | ||
var $whenUntil = _derivation(function () { | ||
return { | ||
until: $until.get(), | ||
when: $when.get(), | ||
}; | ||
}); | ||
var controller = new Reactor($whenUntil, function (conds) { | ||
if (conds.until) { | ||
reactor.stop(); | ||
this.stop(); | ||
} else if (conds.when) { | ||
if (!reactor._active) { | ||
reactor.start().force(); | ||
} | ||
} else if (reactor._active) { | ||
reactor.stop(); | ||
} | ||
}); | ||
// wrap reactor so f doesn't get a .this context, and to allow | ||
// stopping after one reaction if desired. | ||
var reactor = this.reactor({ | ||
react: function (val) { | ||
if (opts.skipFirst) { | ||
opts.skipFirst = false; | ||
} else { | ||
f(val); | ||
if (opts.once) { | ||
this.stop(); | ||
controller.stop(); | ||
} | ||
} | ||
}, | ||
onStart: opts.onStart, | ||
onStop: opts.onStop | ||
}); | ||
reactor._governor = controller; | ||
// listen to when and until conditions, starting and stopping the | ||
// reactor as appropriate, and stopping this controller when until | ||
// condition becomes true | ||
var controller = D.struct({ | ||
until: condDerivable(opts.until, 'until'), | ||
when: condDerivable(opts.when, 'when') | ||
}).reactor(function (conds) { | ||
if (conds.until) { | ||
reactor.stop(); | ||
this.stop(); | ||
} else if (conds.when) { | ||
if (!reactor.isActive()) { | ||
reactor.start().force(); | ||
} | ||
} else if (reactor.isActive()) { | ||
reactor.stop(); | ||
} | ||
}); | ||
// listen to from condition, starting the reactor controller | ||
// when appropriate | ||
var $from = condDerivable(opts.from, 'from'); | ||
var initiator = new Reactor($from, function (from) { | ||
if (from) { | ||
controller.start().force(); | ||
this.stop(); | ||
} | ||
}); | ||
// listen to from condition, starting the reactor controller | ||
// when appropriate | ||
condDerivable(opts.from, 'from').reactor(function (from) { | ||
if (from) { | ||
controller.start().force(); | ||
this.stop(); | ||
} | ||
}).start().force(); | ||
}, | ||
initiator.start().force(); | ||
} | ||
is: function (other) { | ||
return D.lift(this._equals || opts.equals)(this, other); | ||
}, | ||
function Atom (value) { | ||
this._id = nextId(); | ||
this._activeChildren = []; | ||
this._value = value; | ||
this._state = UNCHANGED; | ||
this._type = ATOM; | ||
this._equals = null; | ||
return this; | ||
}; | ||
and: function (other) { | ||
return this.derive(function (x) {return x && D.unpack(other);}); | ||
}, | ||
assign(Atom.prototype, { | ||
_clone: function () { | ||
return setEquals(atom$2(this._value), this._equals); | ||
}, | ||
or: function (other) { | ||
return this.derive(function (x) {return x || D.unpack(other);}); | ||
}, | ||
set: function (value) { | ||
maybeTrack(this); | ||
then: function (thenClause, elseClause) { | ||
return this.derive(function (x) { | ||
return D.unpack(x ? thenClause : elseClause); | ||
}); | ||
}, | ||
var oldValue = this._value; | ||
this._value = value; | ||
mThen: function (thenClause, elseClause) { | ||
return this.derive(function (x) { | ||
return D.unpack(util_some(x) ? thenClause : elseClause); | ||
}); | ||
}, | ||
mOr: function (other) { | ||
return this.mThen(this, other); | ||
}, | ||
mDerive: function (arg) { | ||
if (arguments.length === 1 && arg instanceof Array) { | ||
var that = this; | ||
return arg.map(function (a) { return that.mDerive(a); }); | ||
} else { | ||
return this.mThen(this.derive.apply(this, arguments)); | ||
if (!inTransaction()) { | ||
if (!this.__equals(value, oldValue)) { | ||
try { | ||
this._state = CHANGED; | ||
var reactors = []; | ||
mark(this, reactors); | ||
processReactors(reactors); | ||
} finally { | ||
this._state = UNCHANGED; | ||
} | ||
} | ||
}, | ||
} | ||
}, | ||
mAnd: function (other) { | ||
return this.mThen(other, this); | ||
}, | ||
get: function () { | ||
maybeCaptureParent(this); | ||
return this._value; | ||
}, | ||
}); | ||
not: function () { | ||
return this.derive(function (x) { return !x; }); | ||
}, | ||
function atom$2 (value) { | ||
return new Atom(value); | ||
} | ||
withEquality: function (equals) { | ||
if (equals) { | ||
if (typeof equals !== 'function') { | ||
throw new Error('equals must be function'); | ||
} | ||
} else { | ||
equals = null; | ||
} | ||
function Lens (descriptor) { | ||
Derivation.call(this, descriptor.get); | ||
this._lensDescriptor = descriptor; | ||
this._type = LENS; | ||
} | ||
return util_setEquals(this._clone(), equals); | ||
}, | ||
assign(Lens.prototype, Derivation.prototype, { | ||
_clone: function () { | ||
return setEquals(new Lens(this._lensDescriptor), this._equals); | ||
}, | ||
__equals: function (a, b) { | ||
return (this._equals || opts.equals)(a, b); | ||
}, | ||
}; | ||
x.switch = function () { | ||
var args = arguments; | ||
return this.derive(function (x) { | ||
var i; | ||
for (i = 0; i < args.length-1; i+=2) { | ||
if (opts.equals(x, D.unpack(args[i]))) { | ||
return D.unpack(args[i+1]); | ||
} | ||
} | ||
if (i === args.length - 1) { | ||
return D.unpack(args[i]); | ||
} | ||
set: function (value) { | ||
var that = this; | ||
atomically$1(function () { | ||
that._lensDescriptor.set(value); | ||
}); | ||
}; | ||
return this; | ||
}, | ||
}); | ||
return x; | ||
function lens$2 (descriptor) { | ||
return new Lens(descriptor); | ||
} | ||
function derivation_createPrototype (D, opts) { | ||
return { | ||
_clone: function () { | ||
return util_setEquals(D.derivation(this._deriver), this._equals); | ||
}, | ||
var transact$2 = transact$1; | ||
var setDebugMode$2 = setDebugMode$1; | ||
var transaction$2 = transaction$1; | ||
var ticker$2 = ticker$1; | ||
var isDerivable$2 = isDerivable$1; | ||
var isAtom$2 = isAtom$1; | ||
var isLensed$2 = isLensed$1; | ||
var isDerivation$2 = isDerivation$1; | ||
var derivation$1 = _derivation; | ||
var atom$1 = atom$2; | ||
var atomic$2 = atomic$1; | ||
var atomically$2 = atomically$1; | ||
var lens$1 = lens$2; | ||
_forceEval: function () { | ||
var that = this; | ||
var newVal = null; | ||
var parents = parents_capturingParentsEpochs(function () { | ||
if (!util_DEBUG_MODE) { | ||
newVal = that._deriver(); | ||
} else { | ||
try { | ||
newVal = that._deriver(); | ||
} catch (e) { | ||
console.error(that.stack); | ||
throw e; | ||
} | ||
} | ||
}); | ||
if (!this.__equals(newVal, this._value)) { | ||
this._epoch++; | ||
/** | ||
* Template string tag for derivable strings | ||
*/ | ||
function derive$1 (parts) { | ||
var args = slice(arguments, 1); | ||
return derivation$1(function () { | ||
var s = ""; | ||
for (var i=0; i < parts.length; i++) { | ||
s += parts[i]; | ||
if (i < args.length) { | ||
s += unpack$1(args[i]); | ||
} | ||
} | ||
return s; | ||
}); | ||
}; | ||
/** | ||
* dereferences a thing if it is dereferencable, otherwise just returns it. | ||
*/ | ||
function unpack$1 (thing) { | ||
if (isDerivable$2(thing)) { | ||
return thing.get(); | ||
} else { | ||
return thing; | ||
} | ||
}; | ||
this._lastParentsEpochs = parents; | ||
this._value = newVal; | ||
}, | ||
_update: function () { | ||
var globalEpoch = transactions_currentCtx === null ? | ||
epoch_globalEpoch : | ||
transactions_currentCtx.globalEpoch; | ||
if (this._lastGlobalEpoch !== globalEpoch) { | ||
if (this._value === util_unique) { | ||
// brand spanking new, so force eval | ||
this._forceEval(); | ||
} else { | ||
for (var i = 0, len = this._lastParentsEpochs.length; i < len; i += 2) { | ||
var parent_1 = this._lastParentsEpochs[i]; | ||
var lastParentEpoch = this._lastParentsEpochs[i + 1]; | ||
var currentParentEpoch; | ||
if (parent_1._type === types_ATOM) { | ||
currentParentEpoch = parent_1._getEpoch(); | ||
} else { | ||
parent_1._update(); | ||
currentParentEpoch = parent_1._epoch; | ||
} | ||
if (currentParentEpoch !== lastParentEpoch) { | ||
this._forceEval(); | ||
return; | ||
} | ||
} | ||
} | ||
this._lastGlobalEpoch = globalEpoch; | ||
} | ||
}, | ||
get: function () { | ||
var idx = parents_captureParent(this); | ||
this._update(); | ||
parents_captureEpoch(idx, this._epoch); | ||
return this._value; | ||
}, | ||
/** | ||
* lifts a non-monadic function to work on derivables | ||
*/ | ||
function lift$1 (f) { | ||
return function () { | ||
var args = arguments; | ||
var that = this; | ||
return derivation$1(function () { | ||
return f.apply(that, Array.prototype.map.call(args, unpack$1)); | ||
}); | ||
}; | ||
} | ||
}; | ||
function derivation_construct(obj, deriver) { | ||
obj._deriver = deriver; | ||
obj._lastParentsEpochs = []; | ||
obj._lastGlobalEpoch = epoch_globalEpoch - 1; | ||
obj._epoch = 0; | ||
obj._type = types_DERIVATION; | ||
obj._value = util_unique; | ||
obj._equals = null; | ||
if (util_DEBUG_MODE) { | ||
obj.stack = Error().stack; | ||
function deepUnpack (thing) { | ||
if (isDerivable$2(thing)) { | ||
return thing.get(); | ||
} else if (thing instanceof Array) { | ||
return thing.map(deepUnpack); | ||
} else if (thing.constructor === Object) { | ||
var result = {}; | ||
var keys$$ = keys(thing); | ||
for (var i = keys$$.length; i--;) { | ||
var prop = keys$$[i]; | ||
result[prop] = deepUnpack(thing[prop]); | ||
} | ||
return result; | ||
} else { | ||
return thing; | ||
} | ||
return obj; | ||
} | ||
function mutable_createPrototype (D, _) { | ||
return { | ||
swap: function (f) { | ||
var args = util_slice(arguments, 0); | ||
args[0] = this.get(); | ||
return this.set(f.apply(null, args)); | ||
}, | ||
lens: function (monoLensDescriptor) { | ||
var that = this; | ||
return D.lens({ | ||
get: function () { | ||
return monoLensDescriptor.get(that.get()); | ||
}, | ||
set: function (val) { | ||
that.set(monoLensDescriptor.set(that.get(), val)); | ||
} | ||
}); | ||
}, | ||
}; | ||
} | ||
function struct$1 (arg) { | ||
if (arg.constructor === Object || arg instanceof Array) { | ||
return derivation$1(function () { | ||
return deepUnpack(arg); | ||
}); | ||
} else { | ||
throw new Error("`struct` expects plain Object or Array"); | ||
} | ||
}; | ||
function lens_createPrototype(D, _) { | ||
return { | ||
_clone: function () { | ||
return util_setEquals(D.lens(this._lensDescriptor), this._equals); | ||
}, | ||
set: function (value) { | ||
var that = this; | ||
D.atomically(function () { | ||
that._lensDescriptor.set(value); | ||
}); | ||
return this; | ||
}, | ||
function wrapPreviousState$1 (f, init) { | ||
var lastState = init; | ||
return function (newState) { | ||
var result = f.call(this, newState, lastState); | ||
lastState = newState; | ||
return result; | ||
}; | ||
} | ||
function lens_construct(derivation, descriptor) { | ||
derivation._lensDescriptor = descriptor; | ||
derivation._type = types_LENS; | ||
return derivation; | ||
function captureDereferences$1 (f) { | ||
var captured = []; | ||
startCapturingParents(void 0, captured); | ||
try { | ||
f(); | ||
} finally { | ||
stopCapturingParents(); | ||
} | ||
return captured; | ||
} | ||
function atom_createPrototype (D, opts) { | ||
return { | ||
_clone: function () { | ||
return util_setEquals(D.atom(this._value), this._equals); | ||
}, | ||
set: function (value) { | ||
if (transactions_currentCtx !== null) { | ||
var inTxnThis = void 0; | ||
if ((inTxnThis = transactions_currentCtx.id2txnAtom[this._id]) !== void 0 && | ||
value !== inTxnThis._value) { | ||
transactions_currentCtx.globalEpoch++; | ||
inTxnThis._epoch++; | ||
inTxnThis._value = value; | ||
} else if (!this.__equals(value, this._value)) { | ||
transactions_currentCtx.globalEpoch++; | ||
inTxnThis = this._clone(); | ||
inTxnThis._value = value; | ||
inTxnThis._id = this._id; | ||
inTxnThis._epoch = this._epoch + 1; | ||
transactions_currentCtx.id2txnAtom[this._id] = inTxnThis; | ||
util_addToArray(transactions_currentCtx.modifiedAtoms, this); | ||
function andOrFn (breakOn) { | ||
return function () { | ||
var args = arguments; | ||
return derivation$1(function () { | ||
var val; | ||
for (var i = 0; i < args.length; i++) { | ||
val = unpack$1(args[i]); | ||
if (breakOn(val)) { | ||
break; | ||
} | ||
} else { | ||
if (!this.__equals(value, this._value)) { | ||
this._set(value); | ||
this._reactors.forEach(function (r) { return r._maybeReact(); }); | ||
} | ||
} | ||
}, | ||
_set: function (value) { | ||
epoch_globalEpoch++; | ||
this._epoch++; | ||
this._value = value; | ||
}, | ||
get: function () { | ||
var inTxnThis; | ||
var txnCtx = transactions_currentCtx; | ||
while (txnCtx !== null) { | ||
inTxnThis = txnCtx.id2txnAtom[this._id]; | ||
if (inTxnThis !== void 0) { | ||
parents_captureEpoch(parents_captureParent(this), inTxnThis._epoch); | ||
return inTxnThis._value; | ||
} | ||
else { | ||
txnCtx = txnCtx.parent; | ||
} | ||
} | ||
parents_captureEpoch(parents_captureParent(this), this._epoch); | ||
return this._value; | ||
}, | ||
_getEpoch: function () { | ||
var inTxnThis; | ||
var txnCtx = transactions_currentCtx; | ||
while (txnCtx !== null) { | ||
inTxnThis = txnCtx.id2txnAtom[this._id]; | ||
if (inTxnThis !== void 0) { | ||
return inTxnThis._epoch; | ||
} | ||
else { | ||
txnCtx = txnCtx.parent; | ||
} | ||
} | ||
return this._epoch; | ||
}, | ||
return val; | ||
}); | ||
}; | ||
} | ||
function identity (x) { return x; } | ||
function complement (f) { return function (x) { return !f(x); }; } | ||
var or$1 = andOrFn(identity); | ||
var mOr$1 = andOrFn(some); | ||
var and$1 = andOrFn(complement(identity)); | ||
var mAnd$1 = andOrFn(complement(some)); | ||
function atom_construct (atom, value) { | ||
atom._id = util_nextId(); | ||
atom._reactors = []; | ||
atom._epoch = 0; | ||
atom._value = value; | ||
atom._type = types_ATOM; | ||
atom._equals = null; | ||
return atom; | ||
} | ||
function atom_transaction (f) { | ||
return function () { | ||
var args = util_slice(arguments, 0); | ||
var derivable = Object.freeze({ | ||
transact: transact$2, | ||
setDebugMode: setDebugMode$2, | ||
transaction: transaction$2, | ||
ticker: ticker$2, | ||
isDerivable: isDerivable$2, | ||
isAtom: isAtom$2, | ||
isLensed: isLensed$2, | ||
isDerivation: isDerivation$2, | ||
derivation: derivation$1, | ||
atom: atom$1, | ||
atomic: atomic$2, | ||
atomically: atomically$2, | ||
lens: lens$1, | ||
derive: derive$1, | ||
unpack: unpack$1, | ||
lift: lift$1, | ||
struct: struct$1, | ||
wrapPreviousState: wrapPreviousState$1, | ||
captureDereferences: captureDereferences$1, | ||
or: or$1, | ||
mOr: mOr$1, | ||
and: and$1, | ||
mAnd: mAnd$1 | ||
}); | ||
var derivablePrototype = { | ||
/** | ||
* Creates a derived value whose state will always be f applied to this | ||
* value | ||
*/ | ||
derive: function (f, a, b, c, d) { | ||
var that = this; | ||
var result; | ||
transactions_transact(function () { | ||
result = f.apply(that, args); | ||
}); | ||
return result; | ||
} | ||
} | ||
switch (arguments.length) { | ||
case 0: | ||
throw new Error('.derive takes at least one argument'); | ||
case 1: | ||
switch (typeof f) { | ||
case 'function': | ||
return derivation$1(function () { | ||
return f(that.get()); | ||
}); | ||
case 'string': | ||
case 'number': | ||
return derivation$1(function () { | ||
return that.get()[unpack$1(f)]; | ||
}); | ||
default: | ||
if (f instanceof Array) { | ||
return f.map(function (x) { | ||
return that.derive(x); | ||
}); | ||
} else if (f instanceof RegExp) { | ||
return derivation$1(function () { | ||
return that.get().match(f); | ||
}); | ||
} else if (isDerivable$1(f)) { | ||
return derivation$1(function () { | ||
var deriver = f.get(); | ||
var thing = that.get(); | ||
switch (typeof deriver) { | ||
case 'function': | ||
return deriver(thing); | ||
case 'string': | ||
case 'number': | ||
return thing[deriver]; | ||
default: | ||
if (deriver instanceof RegExp) { | ||
return thing.match(deriver); | ||
} else { | ||
throw Error('type error'); | ||
} | ||
} | ||
}); | ||
} else { | ||
throw Error('type error'); | ||
} | ||
} | ||
case 2: | ||
return derivation$1(function () { | ||
return f(that.get(), unpack$1(a)); | ||
}); | ||
case 3: | ||
return derivation$1(function () { | ||
return f(that.get(), unpack$1(a), unpack$1(b)); | ||
}); | ||
case 4: | ||
return derivation$1(function () { | ||
return f(that.get(), | ||
unpack$1(a), | ||
unpack$1(b), | ||
unpack$1(c)); | ||
}); | ||
case 5: | ||
return derivation$1(function () { | ||
return f(that.get(), | ||
unpack$1(a), | ||
unpack$1(b), | ||
unpack$1(c), | ||
unpack$1(d)); | ||
}); | ||
default: | ||
var args = ([that]).concat(slice(arguments, 1)); | ||
return derivation$1(function () { | ||
return f.apply(null, args.map(unpack$1)); | ||
}); | ||
} | ||
}, | ||
var ticker = null; | ||
react: function (f, opts) { | ||
makeReactor(this, f, opts); | ||
}, | ||
function atom_ticker () { | ||
if (ticker) { | ||
ticker.refCount++; | ||
} else { | ||
ticker = transactions_ticker(); | ||
ticker.refCount = 1; | ||
} | ||
var done = false; | ||
return { | ||
tick: function () { | ||
if (done) throw new Error('tyring to use ticker after release'); | ||
ticker.tick(); | ||
}, | ||
release: function () { | ||
if (done) throw new Error('ticker already released'); | ||
if (--ticker.refCount === 0) { | ||
ticker.stop(); | ||
ticker = null; | ||
mReact: function (f, opts) { | ||
var mWhen = this.mThen(true, false); | ||
if (opts && 'when' in opts && opts.when !== true) { | ||
var when = opts.when; | ||
if (typeof when === 'function' || when === false) { | ||
when = derivation$1(when); | ||
} else if (!isDerivable$1(when)) { | ||
throw new Error('when condition must be bool, function, or derivable'); | ||
} | ||
done = true; | ||
}, | ||
}; | ||
} | ||
mWhen = when.and(mWhen); | ||
} | ||
return this.react(f, assign({}, opts, {when: mWhen})); | ||
}, | ||
var defaultConfig = { equals: util_equals }; | ||
is: function (other) { | ||
var that = this; | ||
return this.derive(function (x) { | ||
return that.__equals(x, unpack$1(other)); | ||
}); | ||
}, | ||
function constructModule (config) { | ||
config = util_extend({}, defaultConfig, config || {}); | ||
and: function (other) { | ||
return this.derive(function (x) {return x && unpack$1(other);}); | ||
}, | ||
var D = { | ||
transact: transactions_transact, | ||
defaultEquals: util_equals, | ||
setDebugMode: util_setDebugMode, | ||
transaction: atom_transaction, | ||
ticker: atom_ticker, | ||
Reactor: reactors_Reactor, | ||
isAtom: function (x) { | ||
return x && (x._type === types_ATOM || x._type === types_LENS); | ||
}, | ||
isDerivable: function (x) { | ||
return x && (x._type === types_ATOM || | ||
x._type === types_LENS || | ||
x._type === types_DERIVATION); | ||
}, | ||
isDerivation: function (x) { | ||
return x && (x._type === types_DERIVATION || x._type === types_LENS) | ||
}, | ||
isLensed: function (x) { | ||
return x && x._type === types_LENS; | ||
}, | ||
isReactor: function (x) { | ||
return x && x._type === types_REACTION; | ||
}, | ||
}; | ||
or: function (other) { | ||
return this.derive(function (x) {return x || unpack$1(other);}); | ||
}, | ||
var Derivable = derivable_createPrototype(D, config); | ||
var Mutable = mutable_createPrototype(D, config); | ||
then: function (thenClause, elseClause) { | ||
return this.derive(function (x) { | ||
return unpack$1(x ? thenClause : elseClause); | ||
}); | ||
}, | ||
var Atom = util_extend({}, Mutable, Derivable, | ||
atom_createPrototype(D, config)); | ||
mThen: function (thenClause, elseClause) { | ||
return this.derive(function (x) { | ||
return unpack$1(some(x) ? thenClause : elseClause); | ||
}); | ||
}, | ||
var Derivation = util_extend({}, Derivable, | ||
derivation_createPrototype(D, config)); | ||
mOr: function (other) { | ||
return this.mThen(this, other); | ||
}, | ||
var Lens = util_extend({}, Mutable, Derivation, | ||
lens_createPrototype(D, config)); | ||
/** | ||
* Constructs a new atom whose state is the given value | ||
*/ | ||
D.atom = function (val) { | ||
return atom_construct(Object.create(Atom), val); | ||
}; | ||
/** | ||
* Returns a copy of f which runs atomically | ||
*/ | ||
D.atomic = function (f) { | ||
return function () { | ||
var result; | ||
mDerive: function (arg) { | ||
if (arguments.length === 1 && arg instanceof Array) { | ||
var that = this; | ||
var args = arguments; | ||
D.atomically(function () { | ||
result = f.apply(that, args); | ||
}); | ||
return result; | ||
} | ||
}; | ||
D.atomically = function (f) { | ||
if (transactions_inTransaction()) { | ||
f(); | ||
return arg.map(function (a) { return that.mDerive(a); }); | ||
} else { | ||
D.transact(f); | ||
return this.mThen(this.derive.apply(this, arguments)); | ||
} | ||
}; | ||
}, | ||
D.derivation = function (f) { | ||
return derivation_construct(Object.create(Derivation), f); | ||
}; | ||
mAnd: function (other) { | ||
return this.mThen(other, this); | ||
}, | ||
/** | ||
* Template string tag for derivable strings | ||
*/ | ||
D.derive = function (parts) { | ||
var args = util_slice(arguments, 1); | ||
return D.derivation(function () { | ||
var s = ""; | ||
for (var i=0; i<parts.length; i++) { | ||
s += parts[i]; | ||
if (i < args.length) { | ||
s += D.unpack(args[i]); | ||
} | ||
not: function () { | ||
return this.derive(function (x) { return !x; }); | ||
}, | ||
withEquality: function (equals) { | ||
if (equals) { | ||
if (typeof equals !== 'function') { | ||
throw new Error('equals must be function'); | ||
} | ||
return s; | ||
}); | ||
}; | ||
/** | ||
* creates a new lens | ||
*/ | ||
D.lens = function (descriptor) { | ||
return lens_construct( | ||
derivation_construct(Object.create(Lens), descriptor.get), | ||
descriptor | ||
); | ||
}; | ||
/** | ||
* dereferences a thing if it is dereferencable, otherwise just returns it. | ||
*/ | ||
D.unpack = function (thing) { | ||
if (D.isDerivable(thing)) { | ||
return thing.get(); | ||
} else { | ||
return thing; | ||
equals = null; | ||
} | ||
}; | ||
/** | ||
* lifts a non-monadic function to work on derivables | ||
*/ | ||
D.lift = function (f) { | ||
return function () { | ||
var args = arguments; | ||
var that = this; | ||
return D.derivation(function () { | ||
return f.apply(that, Array.prototype.map.call(args, D.unpack)); | ||
}); | ||
} | ||
}; | ||
return setEquals(this._clone(), equals); | ||
}, | ||
function deepUnpack (thing) { | ||
if (D.isDerivable(thing)) { | ||
return thing.get(); | ||
} else if (thing instanceof Array) { | ||
return thing.map(deepUnpack); | ||
} else if (thing.constructor === Object) { | ||
var result = {}; | ||
var keys = util_keys(thing); | ||
for (var i = keys.length; i--;) { | ||
var prop = keys[i]; | ||
result[prop] = deepUnpack(thing[prop]); | ||
__equals: function (a, b) { | ||
return (this._equals || equals)(a, b); | ||
}, | ||
}; | ||
derivablePrototype.switch = function () { | ||
var args = arguments; | ||
var that = this; | ||
return this.derive(function (x) { | ||
var i; | ||
for (i = 0; i < args.length-1; i+=2) { | ||
if (that.__equals(x, unpack$1(args[i]))) { | ||
return unpack$1(args[i+1]); | ||
} | ||
return result; | ||
} else { | ||
return thing; | ||
} | ||
} | ||
D.struct = function (arg) { | ||
if (arg.constructor === Object || arg instanceof Array) { | ||
return D.derivation(function () { | ||
return deepUnpack(arg); | ||
}); | ||
} else { | ||
throw new Error("`struct` expects plain Object or Array"); | ||
if (i === args.length - 1) { | ||
return unpack$1(args[i]); | ||
} | ||
}; | ||
}); | ||
}; | ||
function andOrFn (breakOn) { | ||
return function () { | ||
var args = arguments; | ||
return D.derivation(function () { | ||
var val; | ||
for (var i = 0; i < args.length; i++) { | ||
val = D.unpack(args[i]); | ||
if (breakOn(val)) { | ||
break; | ||
} | ||
} | ||
return val; | ||
}); | ||
} | ||
} | ||
function identity (x) { return x; } | ||
function complement (f) { return function (x) { return !f(x); }} | ||
D.or = andOrFn(identity); | ||
D.mOr = andOrFn(util_some); | ||
D.and = andOrFn(complement(identity)); | ||
D.mAnd = andOrFn(complement(util_some)); | ||
var mutablePrototype = { | ||
swap: function (f) { | ||
var args = slice(arguments, 0); | ||
args[0] = this.get(); | ||
return this.set(f.apply(null, args)); | ||
}, | ||
lens: function (monoLensDescriptor) { | ||
var that = this; | ||
return new Lens({ | ||
get: function () { | ||
return monoLensDescriptor.get(that.get()); | ||
}, | ||
set: function (val) { | ||
that.set(monoLensDescriptor.set(that.get(), val)); | ||
} | ||
}); | ||
}, | ||
}; | ||
return D; | ||
} | ||
assign(Derivation.prototype, derivablePrototype); | ||
assign(Lens.prototype, derivablePrototype, mutablePrototype); | ||
assign(Atom.prototype, derivablePrototype, mutablePrototype); | ||
util_extend(exports, constructModule()); | ||
exports.withEquality = function (equals) { | ||
return constructModule({equals: equals}); | ||
}; | ||
exports['default'] = exports; | ||
}); | ||
var transact = transact$2; | ||
var setDebugMode = setDebugMode$2; | ||
var transaction = transaction$2; | ||
var ticker = ticker$2; | ||
var isDerivable = isDerivable$2; | ||
var isAtom = isAtom$2; | ||
var isLensed = isLensed$2; | ||
var isDerivation = isDerivation$2; | ||
var derivation = derivation$1; | ||
var atom = atom$1; | ||
var atomic = atomic$2; | ||
var atomically = atomically$2; | ||
var lens = lens$1; | ||
var derive = derive$1; | ||
var unpack = unpack$1; | ||
var lift = lift$1; | ||
var struct = struct$1; | ||
var wrapPreviousState = wrapPreviousState$1; | ||
var captureDereferences = captureDereferences$1; | ||
var or = or$1; | ||
var mOr = mOr$1; | ||
var and = and$1; | ||
var mAnd = mAnd$1; | ||
exports.transact = transact; | ||
exports.setDebugMode = setDebugMode; | ||
exports.transaction = transaction; | ||
exports.ticker = ticker; | ||
exports.isDerivable = isDerivable; | ||
exports.isAtom = isAtom; | ||
exports.isLensed = isLensed; | ||
exports.isDerivation = isDerivation; | ||
exports.derivation = derivation; | ||
exports.atom = atom; | ||
exports.atomic = atomic; | ||
exports.atomically = atomically; | ||
exports.lens = lens; | ||
exports.derive = derive; | ||
exports.unpack = unpack; | ||
exports.lift = lift; | ||
exports.struct = struct; | ||
exports.wrapPreviousState = wrapPreviousState; | ||
exports.captureDereferences = captureDereferences; | ||
exports.or = or; | ||
exports.mOr = mOr; | ||
exports.and = and; | ||
exports.mAnd = mAnd; | ||
exports['default'] = derivable; | ||
//# sourceMappingURL=derivable.js.map |
@@ -1,7 +0,8 @@ | ||
!function(t,n){"use strict";t&&"function"==typeof t.define&&t.define.amd?t.define(["exports"],n):n("undefined"!=typeof exports?exports:t.Derivable={})}(this,function(t){"use strict";function n(t){for(var n=1;arguments.length>n;n++)for(var e=arguments[n],r=V(e),i=r.length;i--;){var o=r[i];t[o]=e[o]}return t}function e(t,n){return t===n?0!==t||1/t===1/n:t!==t&&n!==n}function r(t,n){return e(t,n)||t&&"function"==typeof t.equals&&t.equals(n)}function i(t,n){var e=t.indexOf(n);0>e&&t.push(n)}function o(t,n){var e=t.indexOf(n);e>=0&&t.splice(e,1)}function u(){return F++}function c(t,n){return Array.prototype.slice.call(t,n)}function a(t){return null!==t&&void 0!==t}function s(t){I=!!t}function f(t,n){return t._equals=n,t}function h(t){var n=z.length;z.push([]);try{return t(),z[n]}finally{z.pop()}}function l(t){if(z.length>0){var n=z[z.length-1];return n.push(t,0),n.length-1}return-1}function p(t,n){z.length>0&&(z[z.length-1][t]=n)}function _(){throw H}function v(t){this.parent=t,this.id2txnAtom={},this.globalEpoch=N,this.modifiedAtoms=[]}function d(){return null!==J}function g(t){y();try{t.call(null,_)}catch(n){if(E(),n!==H)throw n;return}m()}function y(){J=new v(J)}function m(){var t=J;J=t.parent;var n=[];t.modifiedAtoms.forEach(function(e){null!==J?e.set(t.id2txnAtom[e._id]._value):(e._set(t.id2txnAtom[e._id]._value),n.push(e._reactors))}),null===J?N=t.globalEpoch:J.globalEpoch=t.globalEpoch,n.forEach(function(t){t.forEach(function(t){t._maybeReact()})})}function E(){var t=J;J=t.parent,null===J?N=t.globalEpoch+1:J.globalEpoch=t.globalEpoch+1}function b(){y();var t=!1;return{tick:function(){if(t)throw new Error("can't tick disposed ticker");m(),y()},stop:function(){if(t)throw new Error("ticker already disposed");t=!0,m()},resetState:function(){if(t)throw new Error("ticker already disposed");E(),y()}}}function k(t,n){this._derivable=n,t&&(this.react=t),this._atoms=[],this._parent=null,this._active=!1,this._yielding=!1,this._reacting=!1,this._type=B,I&&(this.stack=Error().stack)}function w(t,n){if(t._type===L)i(t._reactors,n), | ||
i(n._atoms,t);else for(var e=0,r=t._lastParentsEpochs.length;r>e;e+=2)w(t._lastParentsEpochs[e],n)}function A(t,n){var e={derive:function(n,e,r,i,o){var u=this;switch(arguments.length){case 0:return u;case 1:switch(typeof n){case"function":return t.derivation(function(){return n(u.get())});case"string":case"number":return t.derivation(function(){return u.get()[t.unpack(n)]});default:if(n instanceof Array)return n.map(function(t){return u.derive(t)});if(n instanceof RegExp)return t.derivation(function(){return u.get().match(n)});if(t.isDerivable(n))return t.derivation(function(){var e=n.get(),r=u.get();switch(typeof e){case"function":return e(r);case"string":case"number":return r[e];default:if(e instanceof RegExp)return r.match(e);throw Error("type error")}return u.get()[t.unpack(n)]});throw Error("type error")}break;case 2:return t.derivation(function(){return n(u.get(),t.unpack(e))});case 3:return t.derivation(function(){return n(u.get(),t.unpack(e),t.unpack(r))});case 4:return t.derivation(function(){return n(u.get(),t.unpack(e),t.unpack(r),t.unpack(i))});case 5:return t.derivation(function(){return n(u.get(),t.unpack(e),t.unpack(r),t.unpack(i),t.unpack(o))});default:var a=[u].concat(c(arguments,1));return t.derivation(function(){return n.apply(null,a.map(t.unpack))})}},reactor:function(t){if("function"==typeof t)return new Q(t,this);if(t instanceof Q){if("function"!=typeof t.react)throw new Error("reactor missing .react method");return t._derivable=this,t}if(t&&t.react)return Object.assign(new Q(null,this),t);throw new Error("Unrecognized type for reactor "+t)},react:function(n,e){function r(n,e){if(!t.isDerivable(n))if("function"==typeof n)n=t.derivation(n);else{if("boolean"!=typeof n)throw Error("react "+e+" condition must be derivable");n=t.atom(n)}return n.derive(function(t){return!!t})}if("function"!=typeof n)throw Error("the first argument to .react must be a function");e=Object.assign({once:!1,from:!0,until:!1,when:!0,skipFirst:!1},e);var i=this.reactor({react:function(t){e.skipFirst?e.skipFirst=!1:(n(t), | ||
e.once&&(this.stop(),o.stop()))},onStart:e.onStart,onStop:e.onStop}),o=t.struct({until:r(e.until,"until"),when:r(e.when,"when")}).reactor(function(t){t.until?(i.stop(),this.stop()):t.when?i.isActive()||i.start().force():i.isActive()&&i.stop()});r(e.from,"from").reactor(function(t){t&&(o.start().force(),this.stop())}).start().force()},is:function(e){return t.lift(this._equals||n.equals)(this,e)},and:function(n){return this.derive(function(e){return e&&t.unpack(n)})},or:function(n){return this.derive(function(e){return e||t.unpack(n)})},then:function(n,e){return this.derive(function(r){return t.unpack(r?n:e)})},mThen:function(n,e){return this.derive(function(r){return t.unpack(a(r)?n:e)})},mOr:function(t){return this.mThen(this,t)},mDerive:function(t){if(1===arguments.length&&t instanceof Array){var n=this;return t.map(function(t){return n.mDerive(t)})}return this.mThen(this.derive.apply(this,arguments))},mAnd:function(t){return this.mThen(t,this)},not:function(){return this.derive(function(t){return!t})},withEquality:function(t){if(t){if("function"!=typeof t)throw new Error("equals must be function")}else t=null;return f(this._clone(),t)},__equals:function(t,e){return(this._equals||n.equals)(t,e)}};return e["switch"]=function(){var e=arguments;return this.derive(function(r){var i;for(i=0;e.length-1>i;i+=2)if(n.equals(r,t.unpack(e[i])))return t.unpack(e[i+1]);return i===e.length-1?t.unpack(e[i]):void 0})},e}function q(t,n){return{_clone:function(){return f(t.derivation(this._deriver),this._equals)},_forceEval:function(){var t=this,n=null,e=h(function(){if(I)try{n=t._deriver()}catch(e){throw console.error(t.stack),e}else n=t._deriver()});this.__equals(n,this._value)||this._epoch++,this._lastParentsEpochs=e,this._value=n},_update:function(){var t=null===J?N:J.globalEpoch;if(this._lastGlobalEpoch!==t){if(this._value===G)this._forceEval();else for(var n=0,e=this._lastParentsEpochs.length;e>n;n+=2){var r,i=this._lastParentsEpochs[n],o=this._lastParentsEpochs[n+1];if(i._type===L?r=i._getEpoch():(i._update(),r=i._epoch),r!==o)return void this._forceEval(); | ||
}this._lastGlobalEpoch=t}},get:function(){var t=l(this);return this._update(),p(t,this._epoch),this._value}}}function O(t,n){return t._deriver=n,t._lastParentsEpochs=[],t._lastGlobalEpoch=N-1,t._epoch=0,t._type=M,t._value=G,t._equals=null,I&&(t.stack=Error().stack),t}function x(t,n){return{swap:function(t){var n=c(arguments,0);return n[0]=this.get(),this.set(t.apply(null,n))},lens:function(n){var e=this;return t.lens({get:function(){return n.get(e.get())},set:function(t){e.set(n.set(e.get(),t))}})}}}function D(t,n){return{_clone:function(){return f(t.lens(this._lensDescriptor),this._equals)},set:function(n){var e=this;return t.atomically(function(){e._lensDescriptor.set(n)}),this}}}function j(t,n){return t._lensDescriptor=n,t._type=U,t}function R(t,n){return{_clone:function(){return f(t.atom(this._value),this._equals)},set:function(t){if(null!==J){var n=void 0;void 0!==(n=J.id2txnAtom[this._id])&&t!==n._value?(J.globalEpoch++,n._epoch++,n._value=t):this.__equals(t,this._value)||(J.globalEpoch++,n=this._clone(),n._value=t,n._id=this._id,n._epoch=this._epoch+1,J.id2txnAtom[this._id]=n,i(J.modifiedAtoms,this))}else this.__equals(t,this._value)||(this._set(t),this._reactors.forEach(function(t){return t._maybeReact()}))},_set:function(t){N++,this._epoch++,this._value=t},get:function(){for(var t,n=J;null!==n;){if(t=n.id2txnAtom[this._id],void 0!==t)return p(l(this),t._epoch),t._value;n=n.parent}return p(l(this),this._epoch),this._value},_getEpoch:function(){for(var t,n=J;null!==n;){if(t=n.id2txnAtom[this._id],void 0!==t)return t._epoch;n=n.parent}return this._epoch}}}function S(t,n){return t._id=u(),t._reactors=[],t._epoch=0,t._value=n,t._type=L,t._equals=null,t}function P(t){return function(){var n,e=c(arguments,0),r=this;return g(function(){n=t.apply(r,e)}),n}}function T(){W?W.refCount++:(W=b(),W.refCount=1);var t=!1;return{tick:function(){if(t)throw new Error("tyring to use ticker after release");W.tick()},release:function(){if(t)throw new Error("ticker already released");0===--W.refCount&&(W.stop(),W=null),t=!0}}}function C(t){ | ||
function e(t){if(f.isDerivable(t))return t.get();if(t instanceof Array)return t.map(e);if(t.constructor===Object){for(var n={},r=V(t),i=r.length;i--;){var o=r[i];n[o]=e(t[o])}return n}return t}function i(t){return function(){var n=arguments;return f.derivation(function(){for(var e,r=0;n.length>r&&(e=f.unpack(n[r]),!t(e));r++);return e})}}function o(t){return t}function u(t){return function(n){return!t(n)}}t=n({},X,t||{});var f={transact:g,defaultEquals:r,setDebugMode:s,transaction:P,ticker:T,Reactor:Q,isAtom:function(t){return t&&(t._type===L||t._type===U)},isDerivable:function(t){return t&&(t._type===L||t._type===U||t._type===M)},isDerivation:function(t){return t&&(t._type===M||t._type===U)},isLensed:function(t){return t&&t._type===U},isReactor:function(t){return t&&t._type===B}},h=A(f,t),l=x(f,t),p=n({},l,h,R(f,t)),_=n({},h,q(f,t)),v=n({},l,_,D(f,t));return f.atom=function(t){return S(Object.create(p),t)},f.atomic=function(t){return function(){var n,e=this,r=arguments;return f.atomically(function(){n=t.apply(e,r)}),n}},f.atomically=function(t){d()?t():f.transact(t)},f.derivation=function(t){return O(Object.create(_),t)},f.derive=function(t){var n=c(arguments,1);return f.derivation(function(){for(var e="",r=0;t.length>r;r++)e+=t[r],n.length>r&&(e+=f.unpack(n[r]));return e})},f.lens=function(t){return j(O(Object.create(v),t.get),t)},f.unpack=function(t){return f.isDerivable(t)?t.get():t},f.lift=function(t){return function(){var n=arguments,e=this;return f.derivation(function(){return t.apply(e,Array.prototype.map.call(n,f.unpack))})}},f.struct=function(t){if(t.constructor===Object||t instanceof Array)return f.derivation(function(){return e(t)});throw new Error("`struct` expects plain Object or Array")},f.or=i(o),f.mOr=i(a),f.and=i(u(o)),f.mAnd=i(u(a)),f}var V=Object.keys,F=0,G=Object.freeze({equals:function(){return!1}}),I=!1,N=0,z=[],L="ATOM",M="DERIVATION",U="LENS",B="REACTION",H={},J=null,K=[],Q=k;Object.assign(Q.prototype,{start:function(){this._lastValue=this._derivable.get(),this._lastEpoch=this._derivable._epoch, | ||
this._atoms=[],w(this._derivable,this);var t=K.length;return t>0&&(this._parent=K[t-1]),this._active=!0,this.onStart&&this.onStart(),this},_force:function(t){try{K.push(this),this._reacting=!0,this.react(t)}catch(n){throw I&&console.error(this.stack),n}finally{this._reacting=!1,K.pop()}},force:function(){return this._force(this._derivable.get()),this},_maybeReact:function(){if(this._reacting)throw Error("cyclical update detected!!");if(this._active){if(this._yielding)throw Error("reactor dependency cycle detected");null!==this._parent&&(this._yielding=!0,this._parent._maybeReact(),this._yielding=!1);var t=this._derivable.get();this._derivable._epoch===this._lastEpoch||this._derivable.__equals(t,this._lastValue)||this._force(t),this._lastEpoch=this._derivable._epoch,this._lastValue=t}},stop:function(){var t=this;return this._atoms.forEach(function(n){return o(n._reactors,t)}),this._atoms=[],this._parent=null,this._active=!1,this.onStop&&this.onStop(),this},orphan:function(){return this._parent=null,this},adopt:function(t){return t._parent=this,this},isActive:function(){return this._active}});var W=null,X={equals:r};n(t,C()),t.withEquality=function(t){return C({equals:t})},t["default"]=t}); | ||
"use strict";function t(t){for(var e=1;e<arguments.length;e++)for(var n=arguments[e],r=Y(n||{}),i=r.length;i--;){var s=r[i];t[s]=n[s]}return t}function e(t,e){return t===e?0!==t||1/t===1/e:t!==t&&e!==e}function n(t,n){return e(t,n)||t&&"function"==typeof t.equals&&t.equals(n)}function r(t,e){var n=t.indexOf(e);n<0&&t.push(e)}function i(t,e){var n=t.indexOf(e);n>=0&&t.splice(n,1)}function s(){return Z++}function o(t,e){return Array.prototype.slice.call(t,e)}function u(t){return null!==t&&void 0!==t}function a(t){tt=!!t}function c(t,e){return t._equals=e,t}function f(t){return t&&(t._type===nt||t._type===et||t._type===rt)}function h(t){return t&&(t._type===et||t._type===rt)}function l(t){return t&&(t._type===nt||t._type===rt)}function p(t){return t&&t._type===rt}function _(t,e){ct.push({parents:e,offset:0,child:t}),ft=t}function v(){return ct[ct.length-1]}function d(){ct.pop(),ft=0===ct.length?null:ct[ct.length-1].child}function g(t){if(null!==ft){var e=ct[ct.length-1];if(e.parents[e.offset]===t)e.offset++;else{var n=e.parents.indexOf(t);if(n===-1)void 0!==ft&&r(t._activeChildren,ft),e.offset===e.parents.length?e.parents.push(t):(e.parents.push(e.parents[e.offset]),e.parents[e.offset]=t),e.offset++;else if(n>e.offset){var i=e.parents[n];e.parents[n]=e.parents[e.offset],e.parents[e.offset]=i,e.offset++}}}}function y(t,e){for(var n=0,r=t._activeChildren.length;n<r;n++){var i=t._activeChildren[n];switch(i._type){case nt:case rt:i._state!==st&&(i._state=st,y(i,e));break;case it:e.push(i)}}}function m(t){for(var e=0,n=t.length;e<n;e++){var r=t[e];if(r._reacting)throw new Error("Synchronous cyclical reactions disallowed. Use setImmediate.");r._maybeReact()}}function w(){throw ht}function x(t){this.parent=t,this.id2originalValue={},this.modifiedAtoms=[]}function b(t){null!==lt&&(t._id in lt.id2originalValue||(lt.modifiedAtoms.push(t),lt.id2originalValue[t._id]=t._value))}function E(){return null!==lt}function k(t){D();try{t.call(null,w)}catch(e){if(R(),e!==ht)throw e;return}C()}function A(t){E()?t():k(t)}function q(t){return function(){ | ||
var e,n=o(arguments,0),r=this;return k(function(){e=t.apply(r,n)}),e}}function O(t){return function(){var e,n=o(arguments,0),r=this;return A(function(){e=t.apply(r,n)}),e}}function D(){lt=new x(lt)}function C(){var t=lt;if(lt=t.parent,null===lt){var e=[];t.modifiedAtoms.forEach(function(n){n.__equals(n._value,t.id2originalValue[n._id])?n._state=ut:(n._state=ot,y(n,e))}),m(e),t.modifiedAtoms.forEach(function(t){t._state=ut})}}function R(){var t=lt;lt=t.parent,t.modifiedAtoms.forEach(function(e){e._value=t.id2originalValue[e._id],e._state=ut,y(e,[])})}function T(){0===pt&&D(),pt++;var t=!1;return{tick:function(){if(t)throw new Error("trying to use ticker after release");C(),D()},reset:function(){if(t)throw new Error("trying to use ticker after release");R(),D()},release:function(){if(t)throw new Error("ticker already released");pt--,t=!0,0===pt&&C()}}}function j(t){this._deriver=t,this._parents=null,this._type=nt,this._value=$,this._equals=null,this._activeChildren=[],this._state=at,tt&&(this.stack=Error().stack)}function V(t,e){if(i(t._activeChildren,e),0===t._activeChildren.length&&null!=t._parents){for(var n=t._parents.length,r=0;r<n;r++)V(t._parents[r],t);t._parents=null,t._state=at}}function S(t){return new j(t)}function M(t,e,n){this._parent=t,this.react=e,this._governor=n||null,this._active=!1,this._reacting=!1,this._type=it,tt&&(this.stack=Error().stack)}function I(e,n,r){function i(t,e){if(!f(t)){if("function"==typeof t)return S(t);if("boolean"==typeof t)return S(function(){return t});throw Error("react "+e+" condition must be derivable, got: "+JSON.stringify(t))}return t}if("function"!=typeof n)throw Error("the first argument to .react must be a function");r=t({once:!1,from:!0,until:!1,when:!0,skipFirst:!1},r);var s=r.skipFirst,o=new M(e,function(t){s?s=!1:(n(t),r.once&&(this.stop(),h.stop()))}),u=i(r.until,"until"),a=i(r.when,"when"),c=S(function(){return{until:u.get(),when:a.get()}}),h=new M(c,function(t){t.until?(o.stop(),this.stop()):t.when?o._active||o.start().force():o._active&&o.stop()});o._governor=h; | ||
var l=i(r.from,"from"),p=new M(l,function(t){t&&(h.start().force(),this.stop())});p.start().force()}function L(t){return this._id=s(),this._activeChildren=[],this._value=t,this._state=ut,this._type=et,this._equals=null,this}function N(t){return new L(t)}function P(t){j.call(this,t.get),this._lensDescriptor=t,this._type=rt}function z(t){return new P(t)}function F(t){var e=o(arguments,1);return bt(function(){for(var n="",r=0;r<t.length;r++)n+=t[r],r<e.length&&(n+=J(e[r]));return n})}function J(t){return yt(t)?t.get():t}function U(t){return function(){var e=arguments,n=this;return bt(function(){return t.apply(n,Array.prototype.map.call(e,J))})}}function B(t){if(yt(t))return t.get();if(t instanceof Array)return t.map(B);if(t.constructor===Object){for(var e={},n=Y(t),r=n.length;r--;){var i=n[r];e[i]=B(t[i])}return e}return t}function G(t){if(t.constructor===Object||t instanceof Array)return bt(function(){return B(t)});throw new Error("`struct` expects plain Object or Array")}function H(t,e){var n=e;return function(e){var r=t.call(this,e,n);return n=e,r}}function K(t){var e=[];_(void 0,e);try{t()}finally{d()}return e}function Q(t){return function(){var e=arguments;return bt(function(){for(var n,r=0;r<e.length&&(n=J(e[r]),!t(n));r++);return n})}}function W(t){return t}function X(t){return function(e){return!t(e)}}Object.defineProperty(exports,"__esModule",{value:!0});var Y=Object.keys,Z=0,$=Object.freeze({equals:function(){return!1}}),tt=!1,et="ATOM",nt="DERIVATION",rt="LENS",it="REACTOR",st=0,ot=1,ut=2,at=3,ct=[],ft=null,ht={},lt=null,pt=0;t(j.prototype,{_clone:function(){return c(S(this._deriver),this._equals)},_forceEval:function(){var t,e=this,n=null;try{if(null===this._parents&&(this._parents=[]),_(this,this._parents),tt)try{n=e._deriver()}catch(r){throw console.error(e.stack),r}else n=e._deriver();t=v().offset}finally{d()}this._state=this.__equals(n,this._value)?ut:ot;for(var i=t,s=this._parents.length;i<s;i++){var o=this._parents[i];V(o,this),this._parents[i]=null}this._parents.length=t,this._value=n},_update:function(){ | ||
if(null===this._parents)this._forceEval();else if(this._state===st){for(var t=this._parents.length,e=0;e<t;e++){var n=this._parents[e];if(n._state===st&&n._update(),n._state===ot){this._forceEval();break}}this._state===st&&(this._state=ut)}},get:function(){if(g(this),this._activeChildren.length>0)this._update();else{_(void 0,[]);try{this._value=this._deriver()}finally{d()}}return this._value}}),t(M.prototype,{start:function(){return this._active=!0,r(this._parent._activeChildren,this),this._parent.get(),this},_force:function(t){try{this._reacting=!0,this.react(t)}catch(e){throw tt&&console.error(this.stack),e}finally{this._reacting=!1}},force:function(){return this._force(this._parent.get()),this},_maybeReact:function(){if(!this._reacting&&this._active&&(null!==this._governor&&this._governor._maybeReact(),this._active)){var t=this._parent.get();this._parent._state===ot&&this._force(t)}},stop:function(){return V(this._parent,this),this._active=!1,this}}),t(L.prototype,{_clone:function(){return c(N(this._value),this._equals)},set:function(t){b(this);var e=this._value;if(this._value=t,!E()&&!this.__equals(t,e))try{this._state=ot;var n=[];y(this,n),m(n)}finally{this._state=ut}},get:function(){return g(this),this._value}}),t(P.prototype,j.prototype,{_clone:function(){return c(new P(this._lensDescriptor),this._equals)},set:function(t){var e=this;return A(function(){e._lensDescriptor.set(t)}),this}});var _t=k,vt=a,dt=q,gt=T,yt=f,mt=h,wt=p,xt=l,bt=S,Et=N,kt=O,At=A,qt=z,Ot=Q(W),Dt=Q(u),Ct=Q(X(W)),Rt=Q(X(u)),Tt=Object.freeze({transact:_t,setDebugMode:vt,transaction:dt,ticker:gt,isDerivable:yt,isAtom:mt,isLensed:wt,isDerivation:xt,derivation:bt,atom:Et,atomic:kt,atomically:At,lens:qt,derive:F,unpack:J,lift:U,struct:G,wrapPreviousState:H,captureDereferences:K,or:Ot,mOr:Dt,and:Ct,mAnd:Rt}),jt={derive:function(t,e,n,r,i){var s=this;switch(arguments.length){case 0:throw new Error(".derive takes at least one argument");case 1:switch(typeof t){case"function":return bt(function(){return t(s.get())});case"string":case"number":return bt(function(){ | ||
return s.get()[J(t)]});default:if(t instanceof Array)return t.map(function(t){return s.derive(t)});if(t instanceof RegExp)return bt(function(){return s.get().match(t)});if(f(t))return bt(function(){var e=t.get(),n=s.get();switch(typeof e){case"function":return e(n);case"string":case"number":return n[e];default:if(e instanceof RegExp)return n.match(e);throw Error("type error")}});throw Error("type error")}case 2:return bt(function(){return t(s.get(),J(e))});case 3:return bt(function(){return t(s.get(),J(e),J(n))});case 4:return bt(function(){return t(s.get(),J(e),J(n),J(r))});case 5:return bt(function(){return t(s.get(),J(e),J(n),J(r),J(i))});default:var u=[s].concat(o(arguments,1));return bt(function(){return t.apply(null,u.map(J))})}},react:function(t,e){I(this,t,e)},mReact:function(e,n){var r=this.mThen(!0,!1);if(n&&"when"in n&&n.when!==!0){var i=n.when;if("function"==typeof i||i===!1)i=bt(i);else if(!f(i))throw new Error("when condition must be bool, function, or derivable");r=i.and(r)}return this.react(e,t({},n,{when:r}))},is:function(t){var e=this;return this.derive(function(n){return e.__equals(n,J(t))})},and:function(t){return this.derive(function(e){return e&&J(t)})},or:function(t){return this.derive(function(e){return e||J(t)})},then:function(t,e){return this.derive(function(n){return J(n?t:e)})},mThen:function(t,e){return this.derive(function(n){return J(u(n)?t:e)})},mOr:function(t){return this.mThen(this,t)},mDerive:function(t){if(1===arguments.length&&t instanceof Array){var e=this;return t.map(function(t){return e.mDerive(t)})}return this.mThen(this.derive.apply(this,arguments))},mAnd:function(t){return this.mThen(t,this)},not:function(){return this.derive(function(t){return!t})},withEquality:function(t){if(t){if("function"!=typeof t)throw new Error("equals must be function")}else t=null;return c(this._clone(),t)},__equals:function(t,e){return(this._equals||n)(t,e)}};jt["switch"]=function(){var t=arguments,e=this;return this.derive(function(n){var r;for(r=0;r<t.length-1;r+=2)if(e.__equals(n,J(t[r])))return J(t[r+1]); | ||
if(r===t.length-1)return J(t[r])})};var Vt={swap:function(t){var e=o(arguments,0);return e[0]=this.get(),this.set(t.apply(null,e))},lens:function(t){var e=this;return new P({get:function(){return t.get(e.get())},set:function(n){e.set(t.set(e.get(),n))}})}};t(j.prototype,jt),t(P.prototype,jt,Vt),t(L.prototype,jt,Vt);var St=_t,Mt=vt,It=dt,Lt=gt,Nt=yt,Pt=mt,zt=wt,Ft=xt,Jt=bt,Ut=Et,Bt=kt,Gt=At,Ht=qt,Kt=F,Qt=J,Wt=U,Xt=G,Yt=H,Zt=K,$t=Ot,te=Dt,ee=Ct,ne=Rt;exports.transact=St,exports.setDebugMode=Mt,exports.transaction=It,exports.ticker=Lt,exports.isDerivable=Nt,exports.isAtom=Pt,exports.isLensed=zt,exports.isDerivation=Ft,exports.derivation=Jt,exports.atom=Ut,exports.atomic=Bt,exports.atomically=Gt,exports.lens=Ht,exports.derive=Kt,exports.unpack=Qt,exports.lift=Wt,exports.struct=Xt,exports.wrapPreviousState=Yt,exports.captureDereferences=Zt,exports.or=$t,exports.mOr=te,exports.and=ee,exports.mAnd=ne,exports["default"]=Tt; | ||
//# sourceMappingURL=dist/derivable.min.js.map | ||
//# sourceMappingURL=derivable.min.js.map |
{ | ||
"name": "derivable", | ||
"version": "0.13.0", | ||
"version": "1.0.0-beta", | ||
"description": "Functional Reactive State for JavaScript & TypeScript", | ||
@@ -9,2 +9,15 @@ "author": "David Sheldrick", | ||
], | ||
"scripts": { | ||
"build": "node scripts/build.js", | ||
"test": "mocha --recursive", | ||
"bench": "node scripts/bench.js", | ||
"coverage": "./scripts/coverage.sh && istanbul report --include=coverage-final.json text", | ||
"show-coverage": "./scripts/coverage.sh && istanbul report --include=coverage-final.json html && open coverage/index.html", | ||
"report-coverage": "./scripts/coverage.sh && istanbul report --include=coverage-final.json lcov && cat coverage/lcov.info | coveralls", | ||
"stats": "node scripts/stats.js", | ||
"docs": "node scripts/make-docs.js", | ||
"toc": "doctoc README.md", | ||
"clean": "rm -rf dist", | ||
"all": "npm run clean && npm run build && npm run test && npm run coverage && npm run docs && npm run stats && npm run bench && npm run toc" | ||
}, | ||
"main": "dist/derivable.js", | ||
@@ -27,13 +40,16 @@ "typings": "dist/derivable.d.ts", | ||
"devDependencies": { | ||
"babel": "^5.6.14", | ||
"babel": "^6.5.2", | ||
"babel-preset-es2015": "^6.6.0", | ||
"babel-register": "^6.7.2", | ||
"benchmark": "^2.1.0", | ||
"bluebird": "^2.9.34", | ||
"grunt": "^0.4.5", | ||
"grunt-contrib-clean": "^0.6.0", | ||
"grunt-contrib-concat": "^0.5.1", | ||
"grunt-contrib-jshint": "^0.11.2", | ||
"grunt-mocha-test": "^0.12.7", | ||
"grunt-release": "^0.13.0", | ||
"chai": "^3.5.0", | ||
"colors": "^1.1.2", | ||
"coveralls": "^2.11.9", | ||
"doctoc": "^1.0.0", | ||
"immutable": "^3.7.4", | ||
"jshint": "^2.8.0", | ||
"mobx": "^2.3.3", | ||
"mocha": "^2.2.5", | ||
"mocha-istanbul": "^0.2.0", | ||
"rollup": "^0.26.1", | ||
"source-map-support": "^0.3.2", | ||
@@ -40,0 +56,0 @@ "uglify-js": "^2.4.24" |
223
README.md
<h1 align="center">DerivableJS</h1> | ||
<h3 align="center">Observable State done Right</h3> | ||
<h3 align="center">State made simple → Effects made easy</h3> | ||
[![Join the chat at https://gitter.im/ds300/derivablejs](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ds300/derivablejs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![npm version](https://badge.fury.io/js/derivable.svg)](http://badge.fury.io/js/derivable) | ||
[![npm](https://img.shields.io/npm/v/derivable.svg?maxAge=2592000)](https://www.npmjs.com/package/derivable) [![Build Status](https://travis-ci.org/ds300/derivablejs.svg?branch=new-algo)](https://travis-ci.org/ds300/derivablejs) [![Coverage Status](https://coveralls.io/repos/github/ds300/derivablejs/badge.svg?branch=new-algo)](https://coveralls.io/github/ds300/derivablejs?branch=new-algo) [![Join the chat at https://gitter.im/ds300/derivablejs](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ds300/derivablejs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Empowered by Futurice's open source sponsorship program](https://img.shields.io/badge/sponsor-chilicorn-ff69b4.svg)](http://futurice.com/blog/sponsoring-free-time-open-source-activities?utm_source=github&utm_medium=spice&utm_campaign=derivablejs) [![.min.gz size](https://img.shields.io/badge/.min.gz%20size-3.4k-blue.svg)](http://github.com) | ||
--- | ||
DerivableJS is a JavaScript implementation of **Derivables**. | ||
Derivables are an Observable-like state container with superpowers. Think [MobX](https://github.com/mobxjs/mobx) distilled to a potent essence, served with two heaped spoonfuls of extra performance, a garnish of side effects innovation, and a healthy side-salad of immutability. | ||
<!-- START doctoc generated TOC please keep comment here to allow auto update --> | ||
@@ -14,16 +13,13 @@ <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> | ||
- [Derivables](#derivables) | ||
- [Tradeoffs](#tradeoffs) | ||
- [Quick start](#quick-start) | ||
- [Reactors](#reactors) | ||
- [Usage](#usage) | ||
- [API](#api) | ||
- [Debugging](#debugging) | ||
- [Examples (very wip)](#examples-very-wip) | ||
- [npm](#npm) | ||
- [Browser](#browser) | ||
- [Batteries Not Included](#batteries-not-included) | ||
- [Equality Woes](#equality-woes) | ||
- [1.0.0 Roadmap](#100-roadmap) | ||
- [Future Work](#future-work) | ||
- [With React](#with-react) | ||
- [With Redux](#with-redux) | ||
- [Debugging](#debugging) | ||
- [Examples (very wip)](#examples-very-wip) | ||
- [Browser](#browser) | ||
- [Equality Woes](#equality-woes) | ||
- [Contributing](#contributing) | ||
- [Thanks](#thanks) | ||
- [Inspiration <3](#inspiration-3) | ||
- [License](#license) | ||
@@ -33,25 +29,81 @@ | ||
## Derivables | ||
Derivables make it trivial to maintain consistent (i.e. sense-making) state at all times without requiring that it be kept all in one place. This is a huge win for those of us who develop complex systems with lots of moving parts because it eradicates an entire class of subtle-but-devastating bugs along with all the incidental complexity they fed upon, allowing us to spend more quality time getting intimate with our problem domain. | ||
## Quick start | ||
This library satisfies the notion that **changes in state should not cause state changes**, i.e. if the value of state A depends on the value of state B, updates to B should atomically include updates to A—*they should be the same update*. We don't seem to have a handle on this issue, and it causes serious mess in our brains and code. | ||
There are two types of Derivable: | ||
Derivables clean that mess up by enabling you to make elegant declarative statements about how your bits of state are related. Then, when you update any bits of 'root' state, clever computer-sciency stuff happens in order to keep everything—*every goshdarn thing*—consistent 100% of the time. | ||
- **Atoms** | ||
There are two types of Derivable: | ||
Atoms are simple mutable references to immutable values. They represent the ground truth from which all else is derived. | ||
- **Atoms** are simple references to immutable values. They are the roots; the ground truth from which all else is derived. | ||
- **Derivations** represent pure (as in function) transformation of values held in atoms. | ||
```javascript | ||
import {atom} from 'derivable'; | ||
Changes in atoms or derivations can be monitored by **Reactors**, which do not encapsulate values and exist solely for executing side-effects in reaction to state changes. Reactors can also be stopped and restarted when appropriate, and offer lifecycle hooks for the sake of resource management. | ||
const $Name = atom('Richard'); | ||
$Name.get(); // => 'Richard' | ||
$Name.set('William'); | ||
$Name.get(); // => 'William' | ||
``` | ||
<em>N.B. The dollar-sign prefix is just a convention I personally use to create a syntactic distinction between ordinary values and derivable values.</em> | ||
- **Derivations** | ||
Derivations represent pure (as in 'pure function') transformation of values held in atoms. You can create them with the `.derive` method, which is a bit like the `.map` method of Arrays and Observables. | ||
```javascript | ||
const cyber = word => word.toUpperCase().split('').join(' '); | ||
const $cyberName = $Name.derive(cyber); | ||
$cyberName.get(); // 'W I L L I A M' | ||
$Name.set('Sarah'); | ||
$cyberName.get(); // 'S A R A H' | ||
``` | ||
Derivations cannot be modified directly with `.set`, but change in accordance with their dependencies, of which there may be many. Here is an example with two dependencies which uses the fundamental `derivation` constructor function: | ||
```javascript | ||
import {derivation} from 'derivable'; | ||
const $Transformer = atom(cyber); | ||
const $transformedName = derivation(() => | ||
$Transformer.get()($Name.get()) | ||
); | ||
$transformedName.get(); // => 'S A R A H' | ||
const reverse = string => string.split('').reverse().join(''); | ||
$Transformer.set(reverse); | ||
$transformedName.get(); // => 'haraS' | ||
$Name.set('Fabian'); | ||
$transformedName.get(); // => 'naibaF' | ||
``` | ||
`derivation` takes a function of zero arguments which should | ||
dereference one or more Derivables to compute the new derived value. DerivableJS then sneakily monitors who | ||
is dereferencing who to infer the parent-child relationships. | ||
## Reactors | ||
Declarative state management is nice in and of itself, but the real benefits come from how it enables us to more effectively manage side effects. DerivableJS has a really nice story on this front: changes in atoms or derivations can be monitored by things called **Reactors**, which do not themselves have any kind of 'current value', but are more like independent agents which exist solely for executing side effects. | ||
Let's have a look at a tiny example app which greets the user: | ||
```javascript | ||
import {atom, derive, transact} from 'derivable' | ||
import {atom, derivation, transact} from 'derivable' | ||
// global application state | ||
const name = atom("World"); // the name of the user | ||
const countryCode = atom("en"); // for i18n | ||
const $Name = atom("World"); // the name of the user | ||
const $CountryCode = atom("en"); // for i18n | ||
@@ -68,12 +120,18 @@ // static constants don't need to be wrapped | ||
// derive a greeting message based on the user's name and country. | ||
const greeting = countryCode.derive(cc => greetings[cc]); | ||
const message = derive`${greeting}, ${name}!`; // es6 tagged template strings! | ||
const $greeting = $CountryCode.derive(cc => greetings[cc]); | ||
const $message = derivation(() => | ||
`${$greeting.get()}, ${$name.get()}!` | ||
); | ||
// set up a Reactor to print the message every time it changes | ||
message.react(msg => console.log(msg)); | ||
// set up a Reactor to print the message every time it changes, as long as | ||
// we know how to greet people in the current country. | ||
$message.react( | ||
msg => console.log(msg), | ||
{when: $greeting} | ||
); | ||
// $> Hello, World! | ||
countryCode.set("de"); | ||
$CountryCode.set("de"); | ||
// $> Hallo, World! | ||
name.set("Dagmar"); | ||
$Name.set("Dagmar"); | ||
// $> Hallo, Dagmar! | ||
@@ -83,6 +141,12 @@ | ||
transact(() => { | ||
countryCode.set("fr"); | ||
name.set("Étienne"); | ||
$CountryCode.set("fr"); | ||
$Name.set("Étienne"); | ||
}); | ||
// $> Bonjour, Étienne! | ||
// if we set the country code to a country whose greeting we don't know, | ||
// $greeting becomes undefined, so the $message reactor won't run | ||
// In fact, the value of $message won't even be recomputed. | ||
$CountryCode.set('dk'); | ||
// ... crickets chirping | ||
``` | ||
@@ -94,40 +158,29 @@ | ||
The DAG edges are automatically inferred by DerivableJS. It is important to understand that they (the edges) do not represent data flow in any temporal sense. They are not streams or channels or even some kind of callback chain. When you change the value of an atom, its whole propagation graph updates in atomic accord. There is no accessible point in time between the fact of changing an atom and the fact of its dependents becoming aware of the change. | ||
## Usage | ||
To put it another way: the (atoms + derivations) part of the graph is conceptually a single gestalt reference to a value. In this case the value is a virtual composite of the two atoms' states. The individual nodes are merely views into this value; they constitute the same information presented differently, like light through a prism. The gestalt is always internally consistent no matter which specific parts of it you inspect at any given time. | ||
DerivableJS is becoming fairly mature, and has been used for serious stuff in production with very few issues. I think it is safe to consider it beta quality at this point. | ||
This property is super important and useful. It cannot be replicated with Observables or any other callback-based mechanism (without doing extra stuff involving topological sorting, and even then only in a single threaded environment). | ||
If your app is non-trivial, use [Immutable](https://facebook.github.io/immutable-js/). | ||
The other thing which truly sets derivations apart is that they are *totally lazy*. Like values in Haskell they are computed just-in-time, i.e. on demand. This is another huge win because: | ||
### With React | ||
- It decouples the computational complexity of updating atoms with that of computing their derivations. Derivations are only re-computed at atom-change time if they (the derivations) are actually used by an affected reactor. So, for example, you can declare an eternal relationship between *n* and *n*<sup>2</sup> without needing to fear the cost of re-computing *n*<sup>2</sup> every time *n* changes. That fear is transferred to whoever decides that they want to know the value of *n*<sup>2</sup> at all times, which is just how it should be. | ||
- It allows derivations to be automatically garbage collected when you don't need them any more, just like any other object. This is simple to the max! In fact, you don't need any special knowledge to avoid memory leaks with DerivableJS—it Just Works. | ||
- It permits true short-circuiting boolean logic in derivation structures, which turns out to be extraordinarily practical. | ||
[react-derivable](https://github.com/jevakallio/react-derivable) is where it's at. | ||
### With Redux | ||
### Tradeoffs | ||
DerivableJS can be used as a kind-of replacement for reselect, by just doing something like this: | ||
You may be wondering how these benefits are achieved. The answer is simple: mark-and-sweep. Yes, [just like your trusty Garbage Collectors](https://en.wikipedia.org/wiki/Tracing_garbage_collection#Basic_algorithm) have been doing since the dawn of Lisp. It is actually more like mark-*react*-sweep, and it brings a couple of performance hits over streams, channels, and callback chains: | ||
```javascript | ||
const $Store = atom(null); | ||
* When an atom is changed, its entire derivation graph is traversed and 'marked'. All active reactors found in the graph are then gently prodded and told to decide whether they need to re-run themselves. This amounts to an additional whole-graph traversal in the worst case. The worst case also happens to be the common case :( | ||
* The sweep phase involves yet another probably-whole-graph traversal. | ||
myReduxStore.subscribe(() => $Store.set(myReduxStore.getState())); | ||
``` | ||
So really each time an atom is changed, its entire derivation graph is likely to be traversed 3 times. I would argue that this is negligible for most UI-ish use cases. The traversal is really simple stuff: following pointers and doing numeric assignments/comparisons. Computers are stupidly good at that kind of thing. But if you're doing something *intense* then perhaps DerivableJS isn't the best choice and you should pick something with eager evaluation. Be appraised, however, that I've got a [fairly promising idea](#future-work) for how to reduce the traversal overhead after v1.0.0 drops. | ||
and then you derive all your derived state from $Store, rather than | ||
*Side note: during transactions only the mark phase occurs. And if an atom is changed more than once during a single transaction, only the bits of the derivation graph that get dereferenced between changes are re-marked.* | ||
### Debugging | ||
A final potential drawback is that DerivableJS requires one to think and design in terms of pure functions and immutable data being lazily computed, which I think takes a little while to get comfortable with coming directly from an OO background. | ||
## Usage | ||
DerivableJS is still quite new, but has been used for serious stuff in production. I think it is safe to consider it beta quality at this point. | ||
##### API | ||
[See Here](https://ds300.github.com/derivablejs) | ||
##### Debugging | ||
Due to inversion of control, the stack traces you get when your derivations throw errors can be totally unhelpful. There is a nice way to solve this problem for dev time. See [setDebugMode](https://ds300.github.com/derivablejs/#derivable-setDebugMode) for more info. | ||
##### Examples (very wip) | ||
### Examples (very wip) | ||
@@ -140,22 +193,13 @@ The best example of writing good code with Derivables right now is the [talk demo](https://github.com/ds300/derivables-talk-demo), which is presented as a 'diff tutorial' and should be read from the initial commit. | ||
And there are a few others [here](https://github.com/ds300/derivablejs/tree/master/examples/) too. | ||
There is a proper gitbook tutorial on the way! | ||
More coming! | ||
### Browser | ||
Either with browserify/webpack/common-js-bundler-du-jour or build as umd bundle with `npm run build -- --umd` | ||
##### npm | ||
Available as `derivable`. | ||
##### Browser | ||
Either with browserify or, if need be, import `dist/derivable.min.js` directly (find it at `window.Derivable`). | ||
##### Batteries Not Included | ||
DerivableJS expects you to use immutable (or effectively immutable) data. It also expects derivation functions to be pure. JavaScript isn't really set up to handle such requirements out of the box, so you would do well to look at an FP library like [Ramda](http://ramdajs.com/) to make life easier. Also, if you want to do immutable collections properly, [Immutable](https://facebook.github.io/immutable-js/) or [Mori](http://swannodette.github.io/mori/) are probably the way to go. Godspeed! | ||
##### Equality Woes | ||
### Equality Woes | ||
JavaScript is entirely whack when it comes to equality. People do [crazy jazz](https://github.com/ramda/ramda/blob/v0.16.0/src/internal/_equals.js) trying to figure out if some stuff is the same as some other stuff. | ||
If the data you're threading through DerivableJS needs its own notion of equality, make sure it has a `.equals` method and everything will be fine. | ||
If the data you're threading through DerivableJS needs its own notion of equality, make sure it has a sensible `.equals` method and everything will be fine. | ||
If you're using a data library with some custom non-standard mechanism for doing equality checks (e.g. Mori), then you'll need to re-initialize DerivableJS with a custom equality function. | ||
If you're using a data library with some custom non-standard mechanism for doing equality checks (e.g. mori), then you'll need to re-initialize DerivableJS with a custom equality function. | ||
@@ -168,29 +212,14 @@ ```javascript | ||
## 1.0.0 Roadmap | ||
DerivableJS's API will be unstable until version 1.0.0 is released, whereafter the project will use [Semantic Versioning](http://semver.org/). | ||
I plan to wait for the project to pick up a bit more steam so I can get serious community feedback before pumping out a 1.0.0 release. This is to allow for breaking changes if the need arises. | ||
## Future Work | ||
1. <s>Shrink the code base. It is currently 5.4k minified and gzipped, but I didn't write the code with size in mind so I think it can get much smaller.</s> now about 3.6k, but could probably get smaller still | ||
1. Dynamic graph optimization. e.g. collapsing derivation branches of frequently-executed reactions into one derivation, maybe trying to align all the data in memory somehow. This would be similar to JIT tracing sans optimization, and could make enormous derivation graphs more feasible (i.e. change propagation could become linear in the number of reactors rather than linear in the number of derivation nodes. It wouldn't work with parent inference though; you'd have to write derivations in the `x.derive((x, y, z) => ..., y, z)` or `derive(x, (x, y, z) => ..., y z)` fashions. So do that if you want to get ahead of the curve! | ||
2. Investigate whether asynchronous transactions are possible, or indeed desirable. | ||
3. <s>Investigate debugging support.</s> - implemented in 0.10.0 | ||
4. I've got a feeling one of the whole-graph traversals mentioned in [Tradeoffs](#tradeoffs) can be eliminated while maintaining all the goodness DerivableJS currently provides, but it would involve a lot of extra caching and it won't even be needed if (1) turns out to be fruitful, so I'll try that first. | ||
## Contributing | ||
I heartily welcome questions, feature requests, bug reports, and general suggestions/criticism on the github issue tracker. I also welcome bugfixes via pull request (please read CONTRIBUTING.md before sumbitting). | ||
I heartily welcome questions, feature requests, bug reports, and general suggestions/criticism on the github issue tracker. I also welcome bugfixes via pull request (please read CONTRIBUTING.md before sumbmitting). | ||
## Thanks | ||
## Inspiration <3 | ||
Special thanks to: | ||
- [Are we there yet?](https://www.infoq.com/presentations/Are-We-There-Yet-Rich-Hickey) | ||
- The [re-frame README](https://github.com/Day8/re-frame) | ||
- [ratom.cljs](https://github.com/reagent-project/reagent/blob/master/src/reagent/ratom.cljs) | ||
- [Turning the database inside out](https://www.youtube.com/watch?v=fU9hR3kiOK0) | ||
- [Simple Made Easy](https://www.infoq.com/presentations/Simple-Made-Easy) | ||
- Alan Dipert and Micha Niskin, creators of Javelin (and Boot!). [Their talk on Javelin](http://www.infoq.com/presentations/ClojureScript-Javelin) was the first exposure I had to these ideas. | ||
- Michael Thompson for the [re-frame README](https://github.com/Day8/re-frame) which was an awesome resource and gave me enough enthusiasm for the idea to hunker down and do it. | ||
- David Weir and Jeremy Reffin for their invaluable mentorship. | ||
- Rich Hickey and the Clojure community for being a constant source of ideas and for making programming even more fun. | ||
## License | ||
@@ -197,0 +226,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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
251223
13
2082
235
16
1