This library is based on the principles of message passing found in languages like Elm and Elixir/Erlang. The purpose is to be able to build modular state with controlled side-effects through messaging.
type State<T, I, M: Message> = {
name: string,
init: (init: I) => DataUpdate<T> | MessageUpdate<T>,
update: (state: T, msg: M) => Update<T>,
subscriptions: (state: T) => Array<Subscription>,
};
Sates use messages to communicate with other state-instances and the runtime.
type Message = { tag: string };
A message is just plain data, a JavaScript object, with two mandatory properties
named tag
. The tag
is supposed to work as a discriminator, informing the
receivers of what type of message it is, what possible data it contains, and
what it means.
Note that these messages are to be completely serializable by JSON.stringify
to facilitate resumable sever-rendering, logging, history playback, inspection,
and other features.
const ADD = "add";
let msg = {
tag: ADD,
value: 2,
};
type StateUpdate = <T, M: Message>(state: T, msg: M) => Update<T>;
Conceptually update
is responsible for receiving messages, interpreting
them, updating the state, and send new messages in case other components need
to be informed or additional data requested.
This is very similar to Redux's Reducer concept with the main difference
being that the update
-function can send new messages.
import { NONE, updateData } from "crustate";
function update(state, message) {
switch(message.tag) {
case ADD:
return updateData(state + message.value);
}
return NONE;
}
Messages sent from the update function are propagated upwards in the state-hierarchy and can be subscribed to in supervising states.
For a state to actually receive messages it first needs to subscribe to messages; which tags it is interested in, if they have any specific requirements, and if it is supposed to be the primary handler for messages of that type.