- Renamed
lens
toproxy
, andisLensed
toisProxy
derivation
was renamed toderive
.derive
has a more flexible call signature, where all extra arguments will be unpacked and used as arguments to the function.- Removed the
lift
method. Instead oflift(f)
, you can instead doderive.bind(null, f)
. - Renamed
swap
toupdate
- Removed stray const declarations (thanks, @liron00)
Derivable#derive
does more stuff (destructuring, property/index lookup, regex matching)Derivable#react
now provides options for declarative lifecycle control.- Composite Lenses
- Fine-grained Equality checks
- Removed a bunch of API cruft.
Prior to this release, the lifecycles of Reactors were controlled imperatively using theReactor#start
and Reactor#stop
methods. This was somewhat verbose and, more importantly, went against the grain of this whole declarative/reactive thing we have going here.
Now there is a better way: providing a lifetime configuration object as a second argument to the .react
method. Here are the options:
interface Lifecycle {
from?: (() => boolean) | Derivable<boolean>;
when?: (() => boolean) | Derivable<boolean>;
until?: (() => boolean) | Derivable<boolean>;
skipFirst?: boolean;
once?: boolean;
onStart?: () => void;
onStop?: () => void;
}
from
determines this initialization time of the reactor. i.e. when the given derivable becomes truthy, the reactor is initialized.
when
causes .start
and .stop
to be called when the given derivable becomes truthy and falsey respectively (but not before the reactor has been initialized, and not after it has been killed).
until
causes the reactor to be killed when the given derivable becomes truthy.
skipFirst
causes the first invocation of the reactor (after it has been initialized, and when when
is truthy) to be ignored. This is typically used if the state of the world at the time of declaration is such that invoking the reactor would be redundant or harmful.
once
causes the reactor to be killed immediately following its first invocation.
onStart
and onStop
are the same lifecycle hooks that were previously provided.
Example usage:
const n = atom(0);
n.react(n => console.log(`n is ${n}`), {
from: () => n.get() > 0, // start when n > 0
when: () => n.get() %2 === 0, // only react when n is even
until: () => n.get() => 5 // stop when n >= 5
});
// ... no output
n.set(1);
// ... no output (n is odd)
n.set(2);
// $> n is 2
n.set(3);
// ... no output (n is odd)
n.set(4);
// $> n is 4
n.set(5);
// ... no output (reactor was killed)
n.set(4);
// ... no output (reactors don't come back from the dead)
-
RegExp matching
const string = atom('hello world'); const firstLetters = string.derive(/\b\w/g); firstLetters.get(); // => ['h', 'w']
-
Property/Index lookup
const obj = atom({foo: 'FOO!'}); const foo = obj.derive('foo'); foo.get(); // => 'FOO!' const arr = atom(['one', 'two']); const first = arr.derive(0); first.get(); // => 'one'
-
Destructuring
const string = atom('hello world') const [len, upper, firstChar, words] = string.derive([ 'length', s => s.toUpperCase(), 0, /\w+/g ]);
Also note that these work with derivable versions of the arguments:
const arr = atom(['one', 'two', 'three']);
const idx = atom(0);
const item = arr.derive(idx);
item.get();
// => 'one'
idx.set(1);
item.get();
// => 'two'
Previously 'lensed atoms' could only have one underlying atom. It is now possible to lens over an arbitrary number of atoms using the new CompositeLens
interface:
type CompositeLens<T> = {
// no-arg getter uses lexical closure to deref and combine atoms
get: () => T,
// one-arg setter to tease apart the value being set and push it
// up to the atoms manually
// runs in an implicit transaction.
set: (value: T) => void
}
Instances of which may be passed to a new 1-arity version of the top-level lens
function to create lensed atoms:
const $FirstName = atom('John');
const $LastName = atom('Steinbeck');
const $Name = lens({
get: () => $FirstName.get() + ' ' + $LastName.get(),
set: (val) => {
const [first, last] = val.split(' ');
$FirstName.set(first);
$LastName.set(last);
}
});
$Name.get(); // => 'John Steinbeck'
$Name.set('James Joyce').
$LastName.get(); // => 'Joyce'
Because JS has no standard way to override equality comparisons, DerivableJS makes it possible to inject equality-checking logic at the module level using the top-level function withEquality
which returns a new instance of DerivableJS using the given equality-checking function.
It is now also possible to do this on a per-derivable basis.
The new Derivable#withEquality
method creates a clone of a derivable, which new derivable uses the given equality-checking function. It looks like this:
import { equals } from 'ramda'
const $Person = atom({name: "Steve"}).withEquality(equals);
$Person.react(({name}) => console.log(`name is ${name}`));
// $> name is Steve
$Person.set({name: "Steve"});
// ... no output (this would print the name again
// if using DerivableJS's standard equality function
// which only does strict-equality (===) checks if no .equals
// method is present on the arguments being compared)
These new top-level functions are identical to transaction
/transact
respectively except that they do not create new (nested) transactions if already in a transaction. This is almost always the desired behaviour, unless you want to gracefully abort transactions.
- no longer returns a Reactor.
- only accepts functions as the first argument.
- does not bind the given function to the context of the resultant reactor.
You can get the old behaviour by converting
d.react(f);
to
d.reactor(f).start().force();
Although it is recommended to switch to using the new declarative lifecycle stuffs if possible.
Use $d.react(r, {when: $when})
.
That was a silly idea...
derive
(except the tagged template string version, that's still there). Use theDerivable#derive
method instead.mDerive
. Use theDerivable#mDerive
method instead.- 2+ arity version of
lens
. Use theDerivable#lens
method instead. lookup
. Use theDerivable#derive(string|number)
method instead.destruct
. Use theDerivable#derive([string|number])
method instead.ifThenElse
. Use theDerivable#then
method instead.mIfThenElse
. Use theDerivable#mThen
method instead.not
. Use theDerivable#not
method instead.switchCase
. Use theDerivable#switch
method instead.get
. Use theDerivable#get
method instead.set
. Use theAtom#set
method instead.swap
. Use theAtom#swap
method instead.
A very common pattern I've needed in my use of DerivableJS has been starting and stopping some reactor based on the value of some piece of state. Dependent Reactors were implemented for this very reason, and it seems to be an elegant way to express many kinds of effectful logic.
This new method enables one to avoid giving the dependent reactor a lexical binding. e.g. before you would do this:
const r = $thing.reactor(doEffects);
$condition.react(cond => {
if (cond) r.start.force();
else r.stop();
};
now you can just write:
$thing.reactWhen($condition, doEffects);
lemon squeezy
Due to inversion of control, the stack traces you get when your derivations or reactors throw errors can be totally unhelpful. This pull request solves that issue by enabling JS Errors to be created (but not thrown) when derivations are instantiated in order to capture the stack trace at the point of instantiation. Then if a derivation throws an error, its instantiation stack trace is logged so we can easily identify exactly which derivation threw the error, and which derivations the error propagated up through.
Creating errors is quite expensive, and can cause noticeable slowdown if there are enough derivations being instantiated, so this mode can be toggled on/off for dev/prod respectively. It is off by default.
See the top-level setDebugMode function.
It seems like the typescript compiler now figures out how to get the typings for an npm module by interrogating the "typings" field in its package.json. It also seems like .d.ts files are now expected to explicitly declare an export.
This library was using Ramda.js' equality function which does deep equality checking on ordinary javascript objects. That's all fine, except not when it sometimes throws up false positives! The newest version fixed that, but I don't feel comfortable using Ramda's stuff anymore.
Luckily, most of the time identity checks are all we need, and they're lovely and fast.
People can, of course, still inject their own equality junk if they need extra protection against redundant computation.
New Stuff:
adopt
method for allowing reactors to become dependent without needing to be started in a reaction cycle. Should have been in 0.8.0.
BREAKING CHANGES:
some
function renamed tomIfThenElse
Derivable#some
method renamed tomThen
I know this is uglier, but is ultimately consistent with the following:
New Stuff:
-
Derivable#mDerive
for nil-shortcutting derivations, e.g.atom(null).mDerive(x => x.toString()).get()
simply returnsnull
, doesn't throw an error. Think of it like the elvis operator in c#. -
Derivable#mOr
for nil-only 'or' semantics e.g.atom(false).mOr(5) === false
whileatom(false).or(5) === 5
-
Derivable#mAnd
for nil-only 'and' semantics e.g.atom('').mAnd(5) === 5
whileatom('').and(5) === ''
-
top level functions
mDerive
,mOr
andmAnd
for the above. -
top level function
lookup
for performing ordinary javascript property lookup on derivables. -
top level function
destruct
for destructuring derivables. <3 this function.
BREAKING CHANGES:
- Reaction class renamed to Reactor
- Derivable#reaction method renamed to Derivable#reactor
Other Changes
Reactors can now 'depend' on other reactors. That is, if Reactor A starts Reactor B (via the Reactor#start method), B is said to be dependent on A. If A is stopped, B will be also be stopped (but if A is started, B is not also started). More importantly, if A and B need to react to the same change, A is guaranteed to react before B. This lets reactors control each other's lifecycles without fear of race conditions.
Reactors now have a method Reactor#orphan which removes any dependency the reactor has.
A minor bug involving creating reactors within transactions was fixed.
Fix bug where parents were being traversed in the wrong order during reaction phases. This precluded true laziness in some cases.
Rename Havelock to DerivableJS
some
fn and method for derivables. Like if
but for null/undefined checks
rather than truthiness checks.
Cycle checks now catch all cases.
Custom reaction intervals
Cyclical graph structures disallowed again. They are nice but incompatible with havelock's central tenets of laziness, consistency, and automatic memory management.
Cyclical graph structures now allowed. You get stack overflows if you make an infinitely-repeating cycle.
transaction
-ized functions can now return a valuestruct
only accepts plain objects and arraysdefaultEquals
exposed for use when writing custom equality-checking functions.
.d.ts reinstated
Fix transaction abortion sweep bug.
- Fallback to ES5 for implementation. Babel was doing weird junk and I don't want to have to deal with that again.
- Introduce
transaction
function. no api docs yet.
Fixed parent management bug for disowned children.
Switched to Apache 2.0 license.
sorted out npm module to only include dist.
- changed behaviour of method
Derivable::derive
to work likeswap
in terms of arguments. e.g. now you can dolet five = three.derive(plus, two)
; - changed function
derive
to match. i.e. it is now just a functional interface to the method. - new function
derivation(f)
which does whatderive(f)
used to do. derive