Muster
A universal, graph-structured data layer for front-end components and back-end services
Muster is a reactive state and data management library that gives you a unified view of as much or as little data as you need.
It can handle UI component state and help combine all your backend APIs into a unified graph. Muster is a single language for front-end and back-end which enables code to be shared and moved easily between client and server.
The below code snippet introduces you to what a Muster graph can look like, and gives you the opportunity to experiment with it by following the link to the Muster Playground.
// Define your graph
{
greeting: 'Hello, world!',
user: 'world',
welcome: format('${salutation}, ${name}!', {
salutation: ref('greeting'),
name: ref('user'),
}),
}
// Request a node
ref('welcome')
// Get the output
"Hello, world!"
Data Graph
Muster organises your data into a graph of nodes.
{
en: {
greeting: 'Hello',
name: 'World',
},
de: {
greeting: 'Guten Tag',
name: 'Welt',
},
}
ref('en', 'greeting');
"Hello"
A node can also reference other nodes that use ref()
.
{
lang: ref('enUS'),
en: {
greeting: 'Hello',
name: 'World',
},
enUS: {
greeting: 'Howdy',
name: ref('en', 'name'),
},
}
ref('lang', 'name');
"World"
We can create a node that depends on others with the computed()
node.
{
lang: ref('de'),
de: {
greeting: 'Guten Tag',
name: 'Welt',
},
welcome: computed(
[
ref('lang', 'greeting'),
ref('lang', 'name'),
],
(greeting, name) => `${greeting}, ${name}!`
),
}
ref('welcome');
"Guten Tag, Welt!"
So far, our graph has been static, our node values are constants. We can wrap a node's default value in the variable()
node to allow it to be modified. Any time one of the dependencies change, the computed node will automatically update to match.
{
en: {
greeting: 'Hello',
name: variable('World'),
},
welcome: computed(
[
ref('en', 'greeting'),
ref('en', 'name'),
],
(greeting, name) => `${greeting}, ${name}!`
),
}
set(ref('en', 'name'), 'Muster');
ref('welcome');
"Hello, Muster!"
Virtual Graph
The graph is virtual, in that its nodes are not resolved until they are requested. This enables us to work with billions of potential nodes without issue.
For example, we could have a node for every integer to determine if it is even or not.
{
[match(types.integer, 'i')]: {
isEven: computed(
[param('i')],
(i) => i % 2 === 0
)
}
}
ref(2147483647, 'isEven')
//output
false
We can also do this recursively.
{
[match(types.integer, 'i')]: {
fibonacci: computed(
[param('i')],
(i) => (
i < 2
? i
: add(ref(i - 1, 'fibonacci'), ref(i - 2, 'fibonacci'))
)
)
}
}
ref(100, 'fibonacci')
354224848179262000000
Asynchronous & Streaming Data
A powerful feature of Muster is that synchronous and asynchronous data are both handled in the same manner.
For example, a computed node that depends on an asynchronous node can be written as though it is synchronous.
{
user: {
name: fromPromise(() =>
fetch('https://jsonplaceholder.typicode.com/users/1')
.then((res) => res.json())
.then((user) => user.name)
),
},
welcome: computed(
[ref('user', 'name')],
(name) => `Hello, ${name}!`
)
}
ref('welcome')
"Hello, Leanne Graham!"
Integration with View Frameworks
Muster is suitable to be used with any view framework that can work well with reactive data.
An integration with React is provided and explored in the tutorials. Other view frameworks such as Angular and Vue can be integrated, but no official packages are currently provided. If you are interested in using these with Muster, please get in touch.