DerivableJS is an holistic state management library. This is its API documentation.
Returns a new derivable encapsulating the result returned by f
which should be pure aside from dereferencing one or more Derivable
s.
const x = atom(1),
y = atom(2);
const z = derivation(() => x.get() + y.get());
z.get();
// => 3
x.set(2);
z.get();
// => 4
Creates a new lensed atom using the given composite lens descriptor.
See also: CompositeLens
Executes f
in the context of a transaction.
In a transactional context, changes to atoms do not have side effects. All changes made during the transaction are propagated for side effects when the transaction commits.
const firstName = `Joe`,
lastName = `Schmoe`;
derive`My name is ${firstName} ${lastName}`.react(
x => console.log(x);
);
// $> My name is Joe Schmoe
All good, but now we want to change the name to Tigran Hamasayan.
firstName.set(`Tigran`);
// $> My name is Tigran Schmoe
Doh! Tigran Schmoe isn't a person! We certainly don't want reactors to think he is, that could be totally confusing for someone.
transact
to the rescue! Let's abort the previous mission and try changing the name to William Blake without ever having a William Schmoe.
transact(() => {
firstName.set(`William`);
lastName.set(`Blake`);
});
// $> My name is William Blake
Success!
Wraps f
such that its body is executed in a transaction. Preserves its input and output semantics.
const firstName = `Joe`,
lastName = `Schmoe`;
derive`My name is ${firstName} ${lastName}`.react(
x => console.log(x);
);
// $> My name is Joe Schmoe
setTimeout(transaction(() => {
firstName.set(`William`);
lastName.set(`Blake`);
}), 1000);
// ... 1 second later ...
// $> My name is William Blake
See also: transact
as transact
but will not create a (nested) transaction if already in a transaction.
as transaction
but will not create a (nested) transaction if the returned function is invoked within a transaction.
Given some plain JavaScript Object
or Array
(or some nested combination thereof) containing one or more derivable things, returns a new derivable representing the input collection with unpacked values. e.g.
const a = atom(`Andrew`),
b = atom(`Bernice`),
c = atom(`Charlie`);
const together = struct({a: a, bandc: [b, c]});
together.react(names => {
console.log(`A stands for ${names.a}`);
console.log(`B stands for ${names.bandc[0]}`);
console.log(`C stands for ${names.bandc[1]}`);
});
// $> A stands for Andrew
// $> B stands for Bernice
// $> C stands for Charlie
c.set(`Chris`);
// $> A stands for Andrew
// $> B stands for Bernice
// $> C stands for Chris
Lifts a function that works on normal values to work on derivable values.
const add = (a, b) => a + b,
x = atom(5),
y = atom(10);
const addD = lift(add);
const xplusy = addD(x, y);
xplusy.get();
// => 15
x.set(20);
xplusy.get();
// => 30
Returns true iff obj
is a derivation, i.e. a derivable which is not an atom.
Tagged template string version of derive. Returns a derivable which represents strings
interploated with …things
, which latter may contain derivables.
const myAtom = atom(`what`),
myConstant = 4;
const myString =
derive`Atom: '${myAtom}', constant: ${myConstant}.`;
myString.get();
// => `Atom: 'what', constant: 4.`
myAtom.set(`nothing`);
myString.get();
// => `Atom: 'nothing', constant: 4.`
Returns a derivable which represents the first value in …conditions
which is truthy when passed through unpack
;
or(false, false, null, undefined, 0, ``, `yes`).get();
// => `yes`
or(atom(false),
atom(null),
atom(undefined),
atom(0),
atom(``),
atom(`yes`)).get();
// => `yes`
Returns a derivable which encapsulates the first non-(null-or-undefined) value in the unpacked conditions.
Returns a derivable which represents the last value in …conditions
iff all values are truthy when passed through unpack
. Otherwise it represents whichever first value was falsey.
and(1, true, `yes`, `lastvalue`).get();
// => `lastValue`
and(1, true, 0, `lastvalue`).get();
// => 0
Returns a derivable which represents the last value in …conditions
iff all values are not null or undefined. Otherwise it returns null or undefined.
Returns a new instance of DerivableJS with a new notion of equality. Does not affect the current instance.
DerivableJS's inbuilt equality-checking function. Note that calling withEquality
will not affect the value of this property.
Enable or disable debug mode.
This causes Error
s to be created (but not thrown) alongside derivations in order to capture the stack trace at their point of instantiation. In case a derivation throws an error itself when being computed, its instantiation stack trace is logged such that it becomes easy to determine exactly which derivation is throwing. Creating errors is quite slow, so you probably should not keep this enabled for production.
A thing that is derivable
Creates a new derivation based on the application of f
to the current value of this derivable. e.g.
const x = atom(4);
const timesTwo = (n) => n * 2;
const xTimesTwo = x.derive(timesTwo);
xTimesTwo.get();
// => 8
Creates a new derivation based on property lookup. e.g.
const obj = atom({foo: 'FOO!', bar: 'BAR!'});
const foo = obj.derive('foo');
foo.get();
// => 'FOO!'
const obj = atom({foo: 'FOO!', bar: 'BAR!'});
const prop = atom('foo');
const item = obj.derive(prop);
item.get();
// => 'FOO!'
prop.set('bar');
item.get();
// => 'BAR!'
Creates a new derivation based on index lookup. e.g.
const arr = atom(['one', 'two', 'three']);
const middle = arr.derive(1);
middle.get();
// => 'two'
const arr = atom(['one', 'two', 'three']);
const idx = atom(0);
const item = arr.derive(idx);
item.get();
// => 'one'
idx.set(1);
item.get();
// => 'two'
Creates a new derivation based on regexp matching. e.g.
const str = atom('hello there');
const match = str.derive(/\w+/g);
match.get()
// => ['hello', 'there']
const str = atom('hello there');
const re = atom(/\w+/);
const match = str.derive(re);
match.get()
// => [ 'hello', index: 0, input: 'hello there' ]
re.set(/e.*?\b/g);
match.get()
// => ['ello', 'ere']
Creates a new derivation based on the application of f
to the current value of this derivable. e.g.
const x = atom(4);
const timesTwo = (n) => n * 2;
const timesFour = (n) => n * 4;
const f = atom(timesTwo);
const fx = x.derive(f);
fx.get();
// => 8
f.set(timesFour);
fx.get();
// => 16
Lets one combine all the above and destructure the result. e.g.
const x = atom('hello there');
const index = atom(0);
const [words, len, upper, char] = x.derive([
/\w+/g, 'length', s => s.toUpperCase(), index
]);
words.get();
// => ['hello', 'there']
len.get();
// => 11
upper.get();
// => 'HELLO THERE'
char.get();
// => 'h'
index.set(2);
char.get();
// => 'l'
Creates a new derivation based on the application of f
to the current value of this derivable and a
. e.g.
const x = atom(4);
const times = (a, b) => a * b;
const xTimesTwo = x.derive(times, 2);
xTimesTwo.get();
// => 8
const y = atom(5);
const xTimesY = x.derive(times, y);
xTimesY.get();
// => 20
y.set(10);
xTimesY.get();
// => 40
Any-arity version of above. Unpacks all args.
See also: unpack
nil-shortcutting version of derive
. e.g.
const x = atom(null);
const timesTwo = (n) => n * 2;
const xTimesTwo = x.derive(timesTwo);
const mXTimesTwo = x.mDerive(timesTwo);
mXTimesTwo.get();
// => null
xTimesTwo.get();
// throws error because we try to multiply null by 2
Declares an anonymous reactor based on f
being applied to this derivable.
The default lifecycle options are:
{
from: true, // i.e. the reactor is initialized immediately
when: true, // i.e. the reactor is always active
until: false, // i.e. the reactor is never killed
skipFirst: false,
once: false
}
e.g.
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)
Returns the current value of this derivable.
const x = atom(`a string`);
x.get();
// => 'a string'
Returns a derivable which represents the equality of the values held in this and other
.
let a = atom(`a`),
b = atom(`b`);
let same = a.is(b);
same.get();
// => false
b.set(`a`);
same.get();
// => true
Returns a derivable which represents the boolean AND composition of the values held in this and other
.
const condition = atom(`truthy value`);
const result = condition.and(`yes truthy`);
result.get();
// => `yes truthy`
condition.set(false);
result.get();
// => false
other
can be a derivable.
const result2 = result.and(condition.derive(s => s.split(` `)[0]));
result2.get();
// => false
condition.set(`some string`);
result2.get();
// => `some`
Due to derivable's laziness, we can be sure that the derivation of condition
(the string splitting bit) will only ever be evaluated when condition
contains a truthy value.
Returns a derivable which represents other
iff this derivable doesn't represent null
or undefined
. Otherwise the returned derivable representsthe same value as this derivable.
Returns a derivable which represents the boolean OR composition of the values held in this and other
.
const first = atom(`truthy value`);
const second = atom(false);
first.or(second).get();
// => `truthy value`
second.or(first).get();
// => `truthy value`
first.set(false);
second.or(first).get();
// => false
first.set(`first`);
second.set(`second`);
first.or(second).get();
// => `first`
second.or(first).get();
// => `second`
Returns a derivable which represents other
iff this derivable represents null
or undefined
. Otherwise represents the same value as this derivable
Returns a derivable which represents the value of thenD
if this is truthy, or elseD
otherwise.
Equivalent to:
this.and(thenD).or(elseD);
Returns a derivable which represents the value of thenD
if this is not null
or undefined
, otherwise elseD
.
Returns a derivable which represents the boolean complement of the truthiness of the the value represented by this.
Equivalent to:
this.derive(x => !x);
…args
is a flat list of (comparatee, value) pairs. If the value represented by this derivable is equal to a comparatee (which can be derivable or not), a derivable representing the comparatee's corresponding value is returned. If none match, the value represented is undefined
unless a trailing value is supplied.
const x = atom(3);
const y = x.switch(
1, `one`,
2, `two`,
3, `three`
);
y.get();
// => `three`
x.set(4);
y.get();
// => undefined
const z = x.switch(
5, `five`,
6, `six`,
`other`
);
z.get();
// => `other`
Returns an copy of this derivable, except the equality checking logic is provided by equals
.
DerivableJS provides strict equality checking by default, delegating to a .equals method if present. But if e.g. you need deep equality checking for plain objects as provided by some deepEquals
function:
const $SomeState = atom({blah: true})
.withEquality(deepEquals);
$SomeState.reactor(() => console.log('side effects!')).start();
$SomeState.set({blah: true});
// no side effects occurred!
Creates and returns a new Reactor
based on f
being applied to this derivable. e.g.
Only use this if Lifecycle
doesn't fit your needs.
const x = atom(4);
const r = x.reactor(x => console.log(`x is`, x));
x.set(8);
// .reactor creats the reactor, but doesn't start it,
// so nothing gets printed yet
r.start();
// now that the reactor has started it will respond
// to changes in x
x.set(16);
// $> x is 16
x.set(32);
// $> x is 32
Represents a mutable reference. Should hold immutable, or effectively immutable data.
Sets the value of this atom to be the value returned when f
is applied to the current value of this and …args
. Returns this.
Equivalent to:
this.set(f.apply(null, [this.get()].concat(args)));
Returns a new atom connected to this one via the logic encapsulated by lens
.
See also: Lens
The value returned from Atom::lens
is a lensed atom. This is a kind of cross between an atom and a derivation. It acts as a mutable proxy for some base atom, which lets consumers modify the base atom without knowing precisely how. This knowledge is encoded in a lens as get
and set
functions.
For example, you could (but probably shouldn't) store your entire application state as a json string, e.g.
const jsonState = atom(JSON.stringify({
username: `Tigran`,
sessionID: `x3rfs`
}));
and then use you could create a generic json lens to access and mutate properties within that string:
function jsonLens (prop) {
return {
get (json) {
return JSON.parse(json)[prop];
},
set (json, value) {
let state = JSON.parse(json);
state[prop] = value;
return JSON.stringify(state);
}
}
}
const username = jsonState.lens(jsonLens(`username`));
username.get();
// => `Tigran`
username.set(`Franny`);
jsonState.get();
// => `{"username": "Franny", "sessionID": "x3rfs"}`
username.get();
// => `Franny`
If someone has the username
atom, they can be completely oblivious to the fact that it is an abstraction over a json string, treating it like a normal atom. This is the power of lenses!
But lenses also compose!
For example, if wanted to pass username
to someone who can only read backwards, we could do the following:
function stringReverse (s) {
let result = ``;
for (let c of s) {
result = c + result;
}
return result;
}
const reverseLens = {
get: s => stringReverse(s),
set: (_, s) => stringReverse(s)
};
const emanresu = username.lens(reverseLens);
emanresu.get();
// => `ynnarF`
emanresu.set(`drahciR`);
jsonState.get();
// => `{"username": "Richard", "sessionID": "x3rfs"}`
Now the poor soul who can only read backwards doesn't ever have to think about reversing strings ever again.
Respresents a lens over one or more atoms, using lexical closures to imperatively retrieve and set values from/to the atoms in question.
For use with the lens
top level function.
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'
See also: lens
This lets one configure the lifecycle of reactors. There are three core options:
from
- used to determine the start of a reactor's lifecyclewhen
- used to toggle activity during a reactor's lifecycleuntil
- used to determine the end of a reactor's lifecycleEach of these options may be set as either a derivable boolean, or as a function of no arguments which may be passed to derivation
to create a derivable boolean.
There are two additional convenience options to provide behaviour for common use cases:
skipFirst
- causes the first invocation of the reactor to be ignoredonce
- causes the reactor to be killed immediately following its first invocationIn addition, one may use this configuration object to override the .onStart
and .onStop
hooks.
Used to determine the start of the reactor's lifecycle. When it becomes truthy, the reactor is initialized. After which point this property is not used.
Causes the reactor to be started and stopped based on the truthiness of the given condition.
Used to determine the end of a reactor's lifecycle. When it becomes truthy the reactor is killed, after which point the reactor will never be used again and is eligible for garbage collection.
Causes the first invocation (and only the first invocation) of the reactor to be ingored.
Causes the reactor to be killed immediately following its first invocation (not counting the skipped invocation, if skipFirst
is set to true).
Tickers provide a facility for running reactions globally at custom intervals. Tickers have a lifecycle which begins when they are created by the ticker
function and end when their .release()
method is invoked. A ticker may not be used after it has been released.
While at least one ticker is alive, the Atom::set
method will not cause reactions to be run.
See also: ticker
A Reactor is a stateful object which can be attached to a particular derivable. Changes in this derivable will cause a Reactor to be executed.
Starts this reactor. After this is called, any changes in the derivable to which the reactor is attached will cause the reactor's .react
method to be called.
Calls the onStart
method if defined.
This method is a no-op if the reactor is already active
See also: isActive
Stops this reactor. Changes in the attached derivable will no longer cause reaction.
Calls the onStop
method if defined.
This method is a no-op if this reactor is already inactive
See also: isActive
Turns a dependent reactor into an independent reactor.
This method is a no-op if the reactor is already independent.
Makes the child transiently dependent on this reactor iff they are both active. If the child is active but this reactor is not, the child is stopped. If the child reactor is not active, adoption is a noop.