module derivable

DerivableJS is an holistic state management library. This is its API documentation.

function atom<T>

(value: T) => Atom<T>

Returns a new Atom containing value.

const myAtom = atom(`a string`);

myAtom.get();
// => `a string`

function derivation<T>

(f: () => T) => Derivable<T>

Returns a new derivable encapsulating the result returned by f which should be pure aside from dereferencing one or more Derivables.

const x = atom(1),
      y = atom(2);

const z = derivation(() => x.get() + y.get());

z.get();
// => 3

x.set(2);
z.get();
// => 4

function lens<T>

(lens: CompositeLens<T>) => Atom<T>

Creates a new lensed atom using the given composite lens descriptor.

See also: CompositeLens

function transact

(f: () => void) => void

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!

function transaction

(f: (…args: any[]) => any) => (…args: any[]) => any

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

function atomically

(f: () => void) => void

as transact but will not create a (nested) transaction if already in a transaction.

function atomic

(f: (…args: any[]) => any) => (…args: any[]) => any

as transaction but will not create a (nested) transaction if the returned function is invoked within a transaction.

function struct

(obj: any) => Derivable<any>

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

function unpack

(obj: any) => any

If obj is derivable, returns obj.get(), otherwise returns obj.

function lift

(f: (…args: any[]) => any) => (…args: Derivable<any>[]) => Derivable<any>

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

<A, B, E>(f: (a: A, b: B) => E) => (a: (A | Derivable<A>), b: (B | Derivable<B>)) => Derivable<E>

<A, B, C, E>(f: (a: A, b: B, c: C) => E) => (a: (A | Derivable<A>), b: (B | Derivable<B>), c: (C | Derivable<C>)) => Derivable<E>

function isAtom

(obj: any) => boolean

Returns true iff obj is an Atom.

function isDerivable

(obj: any) => boolean

Returns true iff obj is a Derivable.

function isDerivation

(obj: any) => boolean

Returns true iff obj is a derivation, i.e. a derivable which is not an atom.

function isLensed

(obj: any) => boolean

Returns true iff obj is a lensed atom.

See also: Lens, Atom::lens

function isReactor

(obj: any) => boolean

Returns true iff obj is a Reactor.

function derive

(strings: string[], …things: any[]) => Derivable<string>

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.`

function or

(…conditions: any[]) => Derivable<any>

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`

See also: Derivable::or

function mOr

(…conditions: any[]) => Derivable<any>

Returns a derivable which encapsulates the first non-(null-or-undefined) value in the unpacked conditions.

See also: Derivable::mOr

function and

(…conditions: any[]) => Derivable<any>

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

See also: Derivable::and

function mAnd

(…conditions: any[]) => Derivable<any>

Returns a derivable which represents the last value in …conditions iff all values are not null or undefined. Otherwise it returns null or undefined.

function withEquality

(equals: (a: any, b: any) => boolean) => any

Returns a new instance of DerivableJS with a new notion of equality. Does not affect the current instance.

function defaultEquals

(a: any, b: any) => boolean

DerivableJS's inbuilt equality-checking function. Note that calling withEquality will not affect the value of this property.

function setDebugMode

(debugMode: boolean) => void

Enable or disable debug mode.

This causes Errors 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.

function ticker

() => Ticker

Creates a new Ticker

interface Derivable<T>

A thing that is derivable

.derive<E>(f: (value: T) => E) => Derivable<E>

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

.derive(prop: (string | Derivable<string>)) => Derivable<any>

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!'

.derive(index: (number | Derivable<number>)) => Derivable<any>

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'

.derive(re: (RegExp | Derivable<RegExp>)) => Derivable<string[]>

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']

.derive<E>(f: Derivable<(value: T) => E>) => Derivable<E>

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

.derive(args: any[]) => Derivable<any>[]

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'

.derive<A, E>(f: (value: T, a: A) => E, a: (A | Derivable<A>)) => Derivable<E>

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

.derive<A, B, E>(f: (value: T, a: A, b: B) => E, a: (A | Derivable<A>), b: (B | Derivable<B>)) => Derivable<E>

.derive<E>(f: (value: T, …args: any[]) => E, …args: any[]) => Derivable<E>

Any-arity version of above. Unpacks all args.

See also: unpack

.mDerive<E>(f: (value: T) => E) => Derivable<E>

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

.mDerive<A, E>(f: (value: T, a: A) => E, a: (A | Derivable<A>)) => Derivable<E>

.mDerive<A, B, E>(f: (value: T, a: A, b: B) => E, a: (A | Derivable<A>), b: (B | Derivable<B>)) => Derivable<E>

.mDerive<E>(f: (value: T, …args: any[]) => E, …args: any[]) => Derivable<E>

.react(f: (value: T) => void, options?: Lifecycle) => void

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)

.get() => T

Returns the current value of this derivable.

const x = atom(`a string`);
x.get();
// => 'a string'

.is(other: any) => Derivable<boolean>

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

.and(other: any) => Derivable<any>

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.

.mAnd(other: any) => Derivable<any>

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.

.or(other: any) => Derivable<any>

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`

.mOr(other: any) => Derivable<any>

Returns a derivable which represents other iff this derivable represents null or undefined. Otherwise represents the same value as this derivable

.then(thenD: any, elseD: any) => Derivable<any>

Returns a derivable which represents the value of thenD if this is truthy, or elseD otherwise.

Equivalent to:

this.and(thenD).or(elseD);

.mThen(thenD: any, elseD: any) => Derivable<any>

Returns a derivable which represents the value of thenD if this is not null or undefined, otherwise elseD.

.not() => Derivable<boolean>

Returns a derivable which represents the boolean complement of the truthiness of the the value represented by this.

Equivalent to:

this.derive(x => !x);

.switch(…args: any[]) => Derivable<any>

…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`

.withEquality(equals: (a: any, b: any) => boolean) => this

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!

.reactor(r: Reactor<T>) => Reactor<T>

Creates a link between r and this derivable. r should be a 'fresh' reactor. i.e. it should not have been linked to any other derivable in the past. For examples see Reactor.

Only use this if Lifecycle doesn't fit your needs.

.reactor(f: (value: T) => void) => Reactor<T>

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

interface Atom<T>

extends Derivable<T>

Represents a mutable reference. Should hold immutable, or effectively immutable data.

.set<E>(value: E) => Atom<E>

Sets the value of this atom to be value, returns this atom

.swap<E>(f: (value: T, …args: any[]) => E, …args: any[]) => Atom<E>

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)));

.lens<E>(lens: Lens<T, E>) => Atom<E>

Returns a new atom connected to this one via the logic encapsulated by lens.

See also: Lens

interface Lens<ParentType, ChildType>

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.

.get(source: ParentType) => ChildType

Returns the lensed value extracted from source.

Called with this set to null.

.set(source: ParentType, value: ChildType) => ParentType

Returns the transformed version of source which includes value.

Called with this set to null.

interface CompositeLens<T>

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

.get() => T

Should dereference one or more derivables to return a value of type T

.set(value: T) => void

Should propagate value back up to any relevant atoms.

interface Lifecycle

This lets one configure the lifecycle of reactors. There are three core options:

  • from - used to determine the start of a reactor's lifecycle
  • when - used to toggle activity during a reactor's lifecycle
  • until - used to determine the end of a reactor's lifecycle

Each 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 ignored
  • once - causes the reactor to be killed immediately following its first invocation

In addition, one may use this configuration object to override the .onStart and .onStop hooks.

from?: ((() => boolean) | Derivable<boolean>)

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.

when?: ((() => boolean) | Derivable<boolean>)

Causes the reactor to be started and stopped based on the truthiness of the given condition.

until?: ((() => boolean) | Derivable<boolean>)

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.

skipFirst?: boolean

Causes the first invocation (and only the first invocation) of the reactor to be ingored.

once?: boolean

Causes the reactor to be killed immediately following its first invocation (not counting the skipped invocation, if skipFirst is set to true).

onStart?: () => void

Invoked immediately after a reactor is started (e.g. as a result of when becoming truthy) but before its first invocation

See also: Reactor::onStart

onStop?: () => void

Invoked immediately after a reactor is stopped (e.g. as a result of when becoming falsey).

See also: Reactor::onStop

interface Ticker

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

.tick() => void

Runs all pending reactions

.release() => void

Releases this ticker, rendering it useless

class Reactor<T>

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.

constructor()

.start() => Reactor<T>

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

.stop() => Reactor<T>

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

.force() => Reactor<T>

Forces reaction.

.isActive() => boolean

Returns true iff the reactor is actively listening to changes in a derivable

.orphan() => Reactor<T>

Turns a dependent reactor into an independent reactor.

This method is a no-op if the reactor is already independent.

.adopt(child: Reactor<any>) => Reactor<T>

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.

.react(value: T) => void

Abstract
Required

This method is responsible for carrying out reactive side-effects based on value.

.onStart() => void

Abstract

Called when the reactor is started. Use for acquiring resources.

.onStop() => void

Abstract

Called when the reactor is stopped. Use for releasing resources.

See also: stop, start