stx documentation
ⓘ The line below is removed from every example for sake of readability. Consider it as the first line of every example.
const { create } = require('stx')
const { create } = require('stx/dist/browser')
Mutation
ⓘ Every state has a root and can have sub leaves: state
means root
or leaf
.
create([value])
or root.create([value])
Creates a new master state or creates a new branch from an existing master state.
Arguments
- value Primitive or JSON Object optional
Initial value of the state. Nested objects are allowed.
Valid types: Boolean
, String
, Number
and Array
Invalid types: Function
, Null
, Undefined
, Symbol
and circular references
Returns
Root of newly created state.
Examples
Create with initial value
const state = create({
key: 'value',
nested: {
one: 1
}
})
Create without initial value
const state = create()
Create a branch from master
const master = create({
first: 1
})
const branch1 = master.create({
second: 2
})
const branch2 = branch1.create({
third: 3
})
master.serialize()
branch1.serialize()
branch2.serialize()
state.get(path, [value])
Gets a sub leaf from any state.
Arguments
- path String or Array required
Relative path of a sub leaf.
String: To get an immediate child leaf
Array: To get second level or deeper leaf
- value Primitive or JSON Object optional
If the relative path is undefined, it will be set to this value. Nested objects are allowed.
Valid types: Boolean
, String
, Number
and Array
Invalid types: Function
, Null
, Undefined
, Symbol
and circular references
Returns
Sub leaf for the relative path.
Examples
const state = create({
key: 'value'
})
state.get('key').compute()
Get a second level leaf
const state = create({
nested: {
key: 'value'
}
})
state.get([ 'nested', 'key' ]).compute()
Get inexisting leaf
const state = create()
state.get([ 'nested', 'key' ], 'value').compute()
state.serialize()
Get sub leaf of a leaf
const state = create({
nested: {
key: 'value'
}
})
const nested = state.get('nested')
nested.get('key').compute()
state.set(value)
Update value of an existing state.
Arguments
- value Primitive or JSON Object required
New value to be merged with existing one. Nested objects are allowed. Null
means remove.
Valid types: Boolean
, String
, Number
, Null
and Array
Invalid types: Function
, Undefined
, Symbol
and circular references
Returns
Same state.
Examples
Update a primitive value
const state = create({
key: 'value'
})
state.get('key').set('updated value').compute()
Update a nested value
const state = create({
first: 1
})
state.set({
first: 'updated 1',
second: 'added 2'
})
state.serialize()
Adding a sub leaf to a primitive leaf
ⓘ Unlike a JSON object, a state can have own value and children at the same time.
const state = create({
key: 'value'
})
state.get('key').set({
subKey: 'subValue'
})
state.get('key').compute()
state.get('key').serialize()
state.serialize()
Using reserved key val
const state = create({
nested: {
key: 'value'
}
})
const nested = state.get('nested')
nested.set({ val: 'parent value' })
nested.get('key').set({ val: 'updated value' })
nested.compute()
nested.serialize()
Removing
ⓘ Nested children of a leaf will be also removed recursively.
const state = create({
nested: {
first: 1,
second: 2,
third: 3
}
})
state.get([ 'nested', 'second' ]).set(null)
state.get('nested').serialize()
state.set({
nested: null
})
state.serialize()
Navigation
leaf.parent()
Returns
Parent state of the leaf.
Examples
Get parent of a leaf
const state = create({
key: 'value'
})
state.get('key').parent().serialize()
Get parent of the root
const state = create()
state.parent()
state.root()
Returns
Root for the leaf.
Examples
Get root of a leaf
const state = create({
nested: {
key: 'value'
}
})
const nestedKey = state.get([ 'nested', 'key' ])
nestedKey.root().serialize()
Get root of the root
const state = create('value')
state.root().compute()
state.path()
⚠ Despite being useful for debugging, this operation has a high time complexity. Avoid using it in production code.
Returns
Path array for the state.
Examples
Get path of a leaf
const state = create({
nested: {
key: 'value'
}
})
const nestedKey = state.get([ 'nested', 'key' ])
nestedKey.path()
Get path of the root
const state = create()
state.path()
References
Special Notation
A reference is an absolute path array starting with an @
sign.
ⓘ get
and compute
will follow references by default, serialize
will not.
const state = create({
nested: {
key: 'value'
},
pointer: {
p1: [ '@', 'nested', 'key' ],
p2: [ '@', 'nested' ]
}
})
state.get([ 'pointer', 'p1' ]).compute()
state.get([ 'pointer', 'p2' ]).serialize()
state.get([ 'pointer', 'p2' ]).origin().serialize()
state.get([ 'pointer', 'p2', 'key' ]).compute()
state.get('pointer').serialize()
Merge of children
References will have children leaves both from their own and from referred leaf.
const state = create({
nested: {
first: 1
},
pointer: {
val: [ '@', 'nested' ],
second: 2
}
})
const pointer = state.get('pointer')
const nested = state.get('nested')
pointer.get('first').compute()
pointer.get('second').compute()
pointer.set({ first: 'local 1' })
pointer.get('first').compute()
nested.set('value')
pointer.compute()
nested.set({ third: 3 })
pointer.get('third').compute()
Inheritence of references and referred
const master = create({
nested: {
first: 1,
second: 2
},
pointer: {
p1: [ '@', 'nested', 'first' ]
}
})
const branch1 = master.create({
nested: {
first: '1 in branch'
}
})
branch1.get([ 'pointer', 'p1' ]).compute()
const branch2 = branch1.create({
pointer: {
p1: [ '@', 'nested', 'second' ]
}
})
branch2.get([ 'pointer', 'p1' ]).compute()
Corner cases of set with get
const state = create({
nested: {
first: 1
},
pointer: {
val: [ '@', 'nested' ]
}
})
const pointer = state.get('pointer')
pointer.get('second', 2).path()
pointer.get([ 'first', 'subKey' ], 'value').path()
Iteration
state.forEach(fn)
Iterate over children leaves of a state.
Arguments
- fn Function required
This function will be called for every child leaf.
fn(leaf, key)
- leaf
Child leaf
- key
Key of child leaf
Examples
Recursive iteration
const state = create({
nested: {
first: {
f1: 1,
f2: 2
},
second: {
s1: 1,
s2: 2
}
}
})
const keys = []
const iterator = (leaf, key) => {
keys.push(key)
leaf.forEach(iterator)
}
state.forEach(iterator)
keys
state.map(fn)
Map children leaves of a state to an Array.
Arguments
- fn Function required
This function will be called for every child leaf.
fn(leaf, key)
- leaf
Child leaf
- key
Key of child leaf
Examples
List computed values
const state = create({
first: 1,
second: 2,
third: 3
})
const values = state.map(item => item.compute())
values
state.filter(fn)
Filter children leaves of a state into an Array.
Arguments
- fn Function required
This function will be called for every child leaf.
fn(leaf, key)
- leaf
Child leaf
- key
Key of child leaf
Returns
Array of leaves which passed the filter.
Examples
Filter and map the values
const state = create({
first: 1,
second: 2,
third: 3,
fourth: 4
})
const values = state
.filter(item => item.compute() % 2)
.map(item => item.compute())
values
state.find(fn)
Find a certain child of a state.
Arguments
- fn Function required
This function will be called for every child leaf.
fn(leaf, key)
- leaf
Child leaf
- key
Key of child leaf
Returns
The first child leaf matching the condition.
Examples
Find the strange key
const state = create({
first: 1,
strange: 'value',
third: 3
})
const strange = state.find((_, key) => key === 'strange')
strange.compute()
state.reduce(fn, [accumulator])
Create a single variable out of all children leaves of a state.
Arguments
- fn Function required
This function will be called for every child leaf.
fn(accumulator, leaf, key)
- accumulator
Accumulator variable
- leaf
Child leaf
- key
Key of child leaf
- accumulator anything optional
Initial value of accumulator. This value will be updated during the iteration. If an accumulator is not provided, initial value will be the computed value of the first child.
Returns
Final state of the accumulator
Examples
Sum of values
const state = create({
first: 1,
second: 2,
third: 3
})
const sum = state.reduce((sum, item) => sum + item.compute())
sum
Listeners
state.on([eventName], fn)
Register a new listener for an event.
Arguments
- eventName String optional
Name of event, default value is data
when it’s not provided.
- fn Function required
This function will be called every time relevant event is fired.
fn(val, stamp, state)
- val
A value sent by the emitter of the event. For data
events possible values are: set
, remove
, add-key
and remove-key
.
- stamp
Unique timestamp of the emit.
- state
State for the event. Same state where the listener is placed and event is emitted.
Returns
A listener
object where you can turn off the event listening later.
Examples
Data events
const state = create({
nested: {
first: 1
}
})
const events = []
state.get('nested').on(val => {
events.push(`nested-${val}`)
})
state.get('nested').set('value')
events
state.get('nested').set({ second: 2 })
events
events.length = 0
state.get([ 'nested', 'second' ]).on(val => {
events.push(`second-${val}`)
})
state.get('nested').set({ first: null })
events
state.get('nested').set(null)
events
state.serialize()
Other events
const state = create({
key: 'value'
})
const events = []
state.get('key').on('anEvent', val => {
events.push(val)
})
state.emit('anEvent', 'aValue')
events
state.get('key').emit('anEvent', 'aValue')
events
Data events on references
const state = create({
nested: {
first: 1
},
pointer: [ '@', 'nested' ]
})
const events = []
state.get('pointer').on(val => {
events.push(`pointer-${val}`)
})
state.get('nested').set('value')
events
state.get('nested').set({ second: 2 })
events
events.length = 0
state.get('nested').set({ first: null })
events
state.get('nested').set(null)
events
state.serialize()
Other events on references
const state = create({
key: 'value',
pointer: [ '@', 'key' ]
})
const events = []
state.get('pointer').on('anEvent', val => {
events.push(val)
})
state.get('key').emit('anEvent', 'aValue')
events
state.get('pointer').emit('anEvent', 'anotherValue')
events
listener.off()
Turns event listener off so it will not be fired for the following events emitted.
Examples
Data events
const state = create({
key: 'value'
})
const events = []
const listener = state.get('key').on('data', val => {
events.push(`key-${val}`)
})
state.get('key').set('updated')
events
listener.off()
state.get('key').set('again')
events
Other events
const state = create({
key: 'value'
})
const events = []
const listener = state.get('key').on('anEvent', val => {
events.push(val)
})
state.get('key').emit('anEvent', 'aValue')
events
listener.off()
state.get('key').emit('anEvent', 'aValue')
events
Subscription
Subscriptions are necessary to transfer data over the network. Only the necessary parts of your state will be transferred to the clients, depending on your subscriptions.
state.subscribe([options], fn)
Create a new subscription on a state.
Arguments
- options JSON Object optional
Subscription options as keys and values.
- keys Array
List of sub keys to listen for deep data updates.
- excludeKeys Array
List of sub keys to exclude from listening deep data updates.
- depth Number
Limit of level depth for listening data updates.
- sort Object
When used with limit, this parameter defines which children will be transfered over the network.
- path Array
Relative path of value to sort the children by
- type Type (Number or String)
Sorting behaviour, 2 comes before 17 for Number sorting
- desc Boolean
Set true for descending sort
- limit Number
Limit for number of children to be sent over the network. It is useful together with sort.
- fn Function required
This function will be called every time a deep data update happens.
fn(state)
- state
The state where the subscription is placed.
Returns
A subscription
object where you can unsubscribe later.
Examples
Subscribe without options
Data updates will fire subscriptions on references as well.
const state = create({
first: {
p1: [ '@', 'second' ]
},
second: {
p2: [ '@', 'third' ]
},
third: {
id: 3
}
})
const fires = []
state.get('first').subscribe(first => fires.push('first'))
state.get('second').subscribe(second => fires.push('second'))
state.get('third').subscribe(third => fires.push('third'))
fires
fires.length = 0
state.get([ 'third', 'id' ]).set('three')
fires
Subscribe with depth option
const state = create({
first: {
p1: [ '@', 'second' ]
},
second: {
p2: [ '@', 'third' ]
},
third: {
id: 3
}
})
const fires = []
state.get('first').subscribe(
{ depth: 2 }, first => fires.push('first')
)
state.get('second').subscribe(
{ depth: 2 }, second => fires.push('second')
)
fires
fires.length = 0
state.get([ 'third', 'id' ]).set('three')
fires
Subscribe with keys option
const state = create({
first: {
id: 1
},
second: {
id: 2
},
third: {
id: 3
}
})
const fires = []
state.subscribe(
{ keys: [ 'first', 'third' ] },
state => fires.push('first or third')
)
state.subscribe(
{
excludeKeys: [ 'first' ]
},
state => fires.push('second or third')
)
fires
fires.length = 0
state.set({
second: {
id: 'two'
}
})
fires
subscription.unsubscribe()
Turns subscription off so it will not be fired for the following data updates.
Example
const state = create({
first: {
p1: [ '@', 'second' ]
},
second: {
id: 2
}
})
const fires = []
const sub1 = state.get('first').subscribe(
first => fires.push('first')
)
const sub2 = state.get('second').subscribe(
second => fires.push('second')
)
fires
sub2.unsubscribe()
fires.length = 0
state.get([ 'second', 'id' ]).set('wwo')
fires
Persistency
Persistency is supported through plugins, so anyone can create a new plugin to persist any state in any database or storage. There is only one persistency plugin implemented so far and it supports RocksDB.
Persistency plugins are utilized by a createPersist
method replacing the non-persistent create
method.
async createPersist([value], persistencyInstance)
Creates a new master state or creates a new branch from an existing master state.
Arguments
- value Primitive or JSON Object optional
Initial value of the state. Just same with the original create
method.
- persistencyInstance
An instance of the persistency plugin.
Returns
Root of newly created state.
Example
Persistency with RocksDB
const { createPersist } = require('stx')
const { PersistRocksDB } = require('stx-persist-rocksdb')
createPersist(
{
},
new PersistRocksDB('database_file_name')
)
.then(master => {
})
Network
state.listen(port)
Creates a websocket server listening the given port.
Arguments
Returns
A server
object where you can stop listening the port later.
Example
const state = create()
state.listen(7171)
server.close()
Stops listening the port for a graceful exit.
Example
const state = create()
const server = state.listen(7171)
server.close()
state.connect(url)
Creates a websocket client and connects to the url of a websocket server.
Arguments
Returns
A client
object where you can close the connection later.
Example
const state = create()
state.connect('ws://localhost:7171')
client.socket.close()
Closes the connection to the server.
Example
const state = create()
const client = state.connect('ws://localhost:7171')
state.on('connected', val => {
if (val) {
client.socket.close()
}
})
Example server and client working together
Server
const state = create({
items: {
i1: {
id: 1
},
i2: 2
}
})
const server = state.listen(7171)
state.on('log', line => {
line
server.close()
})
Client
const state = create()
const client = state.connect('ws://localhost:7171')
state.get('items', {}).subscribe(
{ depth: 1 },
items => {
if (items.get('i2')) {
items.serialize()
state.emit('log', 'Hello!')
client.socket.close()
}
}
)
state.switchBranch(branchKey)
When this method is called with a branch key in the client, server.switchBranch
method will be fired on the server and client will get into sync with returned branch on the server.
Arguments
- branchKey String required
This should be a unique identifier to get a certain branch of master data. A further authentication might be performed on the server depending on this key passed by a client.
Example
const state = create()
state.connect('ws://localhost:7171')
state.on('connected', val => {
if (val) {
state.switchBranch('branchKey')
}
})
async server.switchBranch(masterRoot, branchKey, async switcher)
This method should be implemented on server to define branch switching logic. If this method is not implemented on the server, state.switchBranch
method on client will not do anything.
Arguments
These arguments will be passed by stx to the implemented method.
- masterRoot root
Root of master state.
- branchKey String
The branch key sent from client.
- switcher async Function
A stx internal function which creates and returns a branch from master for a given identifier.
async switcher(branchKey, [persistencyInstance])
- branchKey String
The branch key to create and/or retrieve the corresponding branch of the master state.
- persistencyInstance optional
An instance of the persistency plugin. This is necessary to persist the state of the branch. It will save only the difference of branch from master.
Examples
Create branch with incoming key
This is the most simple approach for creating a branch from master state. It is using the branch key as received from the client without any validation or translation.
const state = create()
const server = state.listen(7171)
server.switchBranch = async (_, branchKey, switcher)
=> switcher(branchKey)
Create branch with translated key
If the client sends a username/password pair, a social media token or any other kind of claimed identifier, this is the function where it has to be verified and translated.
const state = create()
const server = state.listen(7171)
server.switchBranch = async (_, branchKey, switcher) => {
const derivedBranchKey =
switcher(derivedBranchKey)
}
master.branch.newBranchMiddleware(newBranch)
Whenever a branch is created from a state, stx will call a middleware method if it is implemented. This middleware is useful to add listeners to every branch of a master state on the server.
Arguments
These arguments will be passed by stx to the implemented method.
- newBranch root
Root of newly created branch.
Example
Updating read count of articles
const master = create({
articles: {
articleA: {
title: 'Article A',
readCount: 0
},
articleB: {
title: 'Article B',
readCount: 0
}
}
})
const incrementRead = (articleId, _, branchArticles) => {
const readCount = master.get(
['articles', articleId, 'readCount']
)
if (readCount) {
const isRead = branchArticles.get(
[articleId, 'read'], false
)
if (!isRead.compute()) {
isRead.set(true)
readCount.set(readCount.compute() + 1)
}
}
}
master.branch.newBranchMiddleware = newBranch => {
newBranch.get('articles').on('read', incrementRead)
}
newBranch.branch.clientCanUpdate
By default, updates to state on client are not sycnhronised back to server as it would be a security threat. It is possible to whitelist some certain paths of state on the server, so client can update directly. When a path is whitelisted by server as clientCanUpdate
, that path still has to be already generated in the server. Client can only set a value to an existing path, creating new paths is not allowed and should be implemented in other ways.
This is an Array of whitelisted paths by the server. Each item on the array should be an Object with the following schema.
Schema of every item on clientCanUpdate
- path Array required
Full path from root, A star sign *
will match any key in the path.
- authorize Function optional
If implemented, this function will be called for every update attempt by a client. Must return true
or false
to authorize or not.
authorize(state)
- state state
The state on the path which is subject to the update
- after Function optional
If implemented, this function will be called for every successful update of the state on the path.
after(state)
- state state
The state on the path which is subject to the update
Example
Updating favourite count of articles
const master = create({
articles: {
articleA: {
title: 'Article A',
favourite: false,
favCount: 0
},
articleB: {
title: 'Article B',
favourite: false,
favCount: 0
}
}
})
const updateFavCount = favourite => {
const favCount = master.get(
favourite.parent().path()
).get('favCount')
if (favourite.compute()) {
favCount.set(favCount.compute() + 1)
} else {
favCount.set(favCount.compute() - 1)
}
}
master.branch.newBranchMiddleware = newBranch => {
newBranch.branch.clientCanUpdate = [
{
path: ['articles', '*', 'favourite'],
after: updateFavCount
}
]
}
stx documentation
ⓘ The line below is removed from every example for sake of readability. Consider it as the first line of every example.
Mutation
ⓘ Every state has a root and can have sub leaves:
state
meansroot
orleaf
.create([value])
orroot.create([value])
Creates a new master state or creates a new branch from an existing master state.
Arguments
Initial value of the state. Nested objects are allowed.
Valid types:
Boolean
,String
,Number
andArray
Invalid types:
Function
,Null
,Undefined
,Symbol
and circular referencesReturns
Root of newly created state.
Examples
Create with initial value
Create without initial value
Create a branch from master
state.get(path, [value])
Gets a sub leaf from any state.
Arguments
Relative path of a sub leaf.
String: To get an immediate child leaf
Array: To get second level or deeper leaf
If the relative path is undefined, it will be set to this value. Nested objects are allowed.
Valid types:
Boolean
,String
,Number
andArray
Invalid types:
Function
,Null
,Undefined
,Symbol
and circular referencesReturns
Sub leaf for the relative path.
Examples
Get an immediate child leaf
Get a second level leaf
Get inexisting leaf
Get sub leaf of a leaf
state.set(value)
Update value of an existing state.
Arguments
New value to be merged with existing one. Nested objects are allowed.
Null
means remove.Valid types:
Boolean
,String
,Number
,Null
andArray
Invalid types:
Function
,Undefined
,Symbol
and circular referencesReturns
Same state.
Examples
Update a primitive value
Update a nested value
Adding a sub leaf to a primitive leaf
ⓘ Unlike a JSON object, a state can have own value and children at the same time.
Using reserved key
val
Removing
ⓘ Nested children of a leaf will be also removed recursively.
Navigation
leaf.parent()
Returns
Parent state of the leaf.
Examples
Get parent of a leaf
Get parent of the root
state.root()
Returns
Root for the leaf.
Examples
Get root of a leaf
Get root of the root
state.path()
⚠ Despite being useful for debugging, this operation has a high time complexity. Avoid using it in production code.
Returns
Path array for the state.
Examples
Get path of a leaf
Get path of the root
References
Special Notation
A reference is an absolute path array starting with an
@
sign.ⓘ
get
andcompute
will follow references by default,serialize
will not.Merge of children
References will have children leaves both from their own and from referred leaf.
Inheritence of references and referred
Corner cases of set with get
Iteration
state.forEach(fn)
Iterate over children leaves of a state.
Arguments
This function will be called for every child leaf.
fn(leaf, key)
Child leaf
Key of child leaf
Examples
Recursive iteration
state.map(fn)
Map children leaves of a state to an Array.
Arguments
This function will be called for every child leaf.
fn(leaf, key)
Child leaf
Key of child leaf
Examples
List computed values
state.filter(fn)
Filter children leaves of a state into an Array.
Arguments
This function will be called for every child leaf.
fn(leaf, key)
Child leaf
Key of child leaf
Returns
Array of leaves which passed the filter.
Examples
Filter and map the values
state.find(fn)
Find a certain child of a state.
Arguments
This function will be called for every child leaf.
fn(leaf, key)
Child leaf
Key of child leaf
Returns
The first child leaf matching the condition.
Examples
Find the strange key
state.reduce(fn, [accumulator])
Create a single variable out of all children leaves of a state.
Arguments
This function will be called for every child leaf.
fn(accumulator, leaf, key)
Accumulator variable
Child leaf
Key of child leaf
Initial value of accumulator. This value will be updated during the iteration. If an accumulator is not provided, initial value will be the computed value of the first child.
Returns
Final state of the accumulator
Examples
Sum of values
Listeners
state.on([eventName], fn)
Register a new listener for an event.
Arguments
Name of event, default value is
data
when it’s not provided.This function will be called every time relevant event is fired.
fn(val, stamp, state)
A value sent by the emitter of the event. For
data
events possible values are:set
,remove
,add-key
andremove-key
.Unique timestamp of the emit.
State for the event. Same state where the listener is placed and event is emitted.
Returns
A
listener
object where you can turn off the event listening later.Examples
Data events
Other events
Data events on references
Other events on references
listener.off()
Turns event listener off so it will not be fired for the following events emitted.
Examples
Data events
Other events
Subscription
Subscriptions are necessary to transfer data over the network. Only the necessary parts of your state will be transferred to the clients, depending on your subscriptions.
state.subscribe([options], fn)
Create a new subscription on a state.
Arguments
Subscription options as keys and values.
List of sub keys to listen for deep data updates.
List of sub keys to exclude from listening deep data updates.
Limit of level depth for listening data updates.
When used with limit, this parameter defines which children will be transfered over the network.
Relative path of value to sort the children by
Sorting behaviour, 2 comes before 17 for Number sorting
Set true for descending sort
Limit for number of children to be sent over the network. It is useful together with sort.
This function will be called every time a deep data update happens.
fn(state)
The state where the subscription is placed.
Returns
A
subscription
object where you can unsubscribe later.Examples
Subscribe without options
Data updates will fire subscriptions on references as well.
Subscribe with depth option
Subscribe with keys option
subscription.unsubscribe()
Turns subscription off so it will not be fired for the following data updates.
Example
Persistency
Persistency is supported through plugins, so anyone can create a new plugin to persist any state in any database or storage. There is only one persistency plugin implemented so far and it supports RocksDB.
Persistency plugins are utilized by a
createPersist
method replacing the non-persistentcreate
method.async createPersist([value], persistencyInstance)
Creates a new master state or creates a new branch from an existing master state.
Arguments
Initial value of the state. Just same with the original
create
method.An instance of the persistency plugin.
Returns
Root of newly created state.
Example
Persistency with RocksDB
Network
state.listen(port)
Creates a websocket server listening the given port.
Arguments
Returns
A
server
object where you can stop listening the port later.Example
server.close()
Stops listening the port for a graceful exit.
Example
state.connect(url)
Creates a websocket client and connects to the url of a websocket server.
Arguments
Returns
A
client
object where you can close the connection later.Example
client.socket.close()
Closes the connection to the server.
Example
Example server and client working together
Server
Client
state.switchBranch(branchKey)
When this method is called with a branch key in the client,
server.switchBranch
method will be fired on the server and client will get into sync with returned branch on the server.Arguments
This should be a unique identifier to get a certain branch of master data. A further authentication might be performed on the server depending on this key passed by a client.
Example
async server.switchBranch(masterRoot, branchKey, async switcher)
This method should be implemented on server to define branch switching logic. If this method is not implemented on the server,
state.switchBranch
method on client will not do anything.Arguments
These arguments will be passed by stx to the implemented method.
Root of master state.
The branch key sent from client.
A stx internal function which creates and returns a branch from master for a given identifier.
async switcher(branchKey, [persistencyInstance])
The branch key to create and/or retrieve the corresponding branch of the master state.
An instance of the persistency plugin. This is necessary to persist the state of the branch. It will save only the difference of branch from master.
Examples
Create branch with incoming key
This is the most simple approach for creating a branch from master state. It is using the branch key as received from the client without any validation or translation.
Create branch with translated key
If the client sends a username/password pair, a social media token or any other kind of claimed identifier, this is the function where it has to be verified and translated.
master.branch.newBranchMiddleware(newBranch)
Whenever a branch is created from a state, stx will call a middleware method if it is implemented. This middleware is useful to add listeners to every branch of a master state on the server.
Arguments
These arguments will be passed by stx to the implemented method.
Root of newly created branch.
Example
Updating read count of articles
newBranch.branch.clientCanUpdate
By default, updates to state on client are not sycnhronised back to server as it would be a security threat. It is possible to whitelist some certain paths of state on the server, so client can update directly. When a path is whitelisted by server as
clientCanUpdate
, that path still has to be already generated in the server. Client can only set a value to an existing path, creating new paths is not allowed and should be implemented in other ways.This is an Array of whitelisted paths by the server. Each item on the array should be an Object with the following schema.
Schema of every item on
clientCanUpdate
Full path from root, A star sign
*
will match any key in the path.If implemented, this function will be called for every update attempt by a client. Must return
true
orfalse
to authorize or not.authorize(state)
The state on the path which is subject to the update
If implemented, this function will be called for every successful update of the state on the path.
after(state)
The state on the path which is subject to the update
Example
Updating favourite count of articles