stx
Batteries included data structure / state manager.
- Set and get deep nested paths
- Data listeners for watching changes
- Subscriptions for watching deep nested changes
- In-state references with special notation
- Create branches from a master state
- Minimum state diff is synchronised over web sockets
- Works both on server and browser
Here is a fully working Vue example
Here is the complete documentation (WIP)
Qucik Start Guide
CRUD operations
Create
const { create } = require('stx')
const state = create({ firstKey: 'value' })
Serialize
state.serialize()
Set
⚠ Default behaviour is merge.
state.set({ second: { subKey: 'subValue' } })
state.serialize()
Get
state.get('second').serialize()
Remove
state.set({ firstKey: null })
state.get('firstKey')
state.serialize()
Compute
⚠ Paths are represented as arrays for nested keys.
const subKey = state.get(['second', 'subKey'])
subKey.compute()
Get with set
Second parameter of get is a default value for the path.
⚠ It'll be set
and returned in absence of given path otherwise it'll be ignored.
state.get('first', 1).compute()
state.get('first').compute()
Navigate
Path
subKey.path()
Parent
subKey.parent().serialize()
Root
subKey.root().serialize()
Listeners
On
⚠ A listener without a name is by default a data listener. Fires on set
, remove
, add-key
, remove-key
.
let fired = []
state.set({ third: 3 })
const third = state.get('third')
const listener = third.on((val, stamp, item) => fired.push(`${val}-${item.compute()}`))
fired
third.set('changed')
fired
state.set({ third: 'again' })
fired
Off
listener.off()
third.set('yet again')
fired
Emit
⚠ Events fired on a path can be listened only at exact same path.
const errors = []
state.on('error', err => errors.push(err))
state.emit('error', 'satellites are not aligned')
errors
subKey.on('error', err => errors.push(err))
subKey.emit('error', 'splines are not reticulated')
errors
Creating branches from master state
const master = create({
movies: {
runLolaRun: {
year: 1998,
imdb: 7.7,
title: 'Run Lola Run'
},
goodByeLenin: {
year: 2003,
imdb: 7.7,
title: 'Good Bye Lenin'
},
theEdukators: {
year: 2004,
imdb: 7.5,
title: 'The Edukators'
}
}
})
const branchA = master.create({
userName:'A',
movies: {
runLolaRun: { favourite: true },
theEdukators: { favourite: true }
}
})
const branchB = master.create({
userName:'B',
movies: {
goodByeLenin: { favourite: true }
}
})
master.get('userName')
branchA.get(['movies', 'theEdukators']).serialize()
branchB.get(['movies', 'theEdukators']).serialize()
master.get(['movies', 'theEdukators']).serialize()
master.get(['movies', 'runLolaRun', 'rating'], 'R')
branchB.get(['movies', 'runLolaRun', 'rating']).compute()
branchA.get(['movies', 'runLolaRun', 'rating']).compute()
branchB.get(['movies', 'runLolaRun', 'rating']).set('G')
branchA.get(['movies', 'runLolaRun', 'rating']).compute()
master.get(['movies', 'runLolaRun', 'rating']).set('PG')
branchA.get(['movies', 'runLolaRun', 'rating']).compute()
branchB.get(['movies', 'runLolaRun', 'rating']).compute()
Listeners on branches
⚠ Events fired on master can be listened on branches and branches of branches.
fired = []
branchA.get('movies').on('reload', val => fired.push(`A-${val}`))
branchB.get('movies').on('reload', val => fired.push(`B-${val}`))
master.get('movies').emit('reload', 'now')
branchA.get('movies').emit('reload', 'later')
fired
References
branchB.set({
watched: {
runLolaRun: [ '@', 'movies', 'runLolaRun' ],
goodByeLenin: [ '@', 'movies', 'goodByeLenin' ]
}
})
branchB.get([ 'watched', 'goodByeLenin', 'favourite' ]).compute()
branchB.get([ 'watched', 'runLolaRun', 'favourite' ])
Origin
branchB.get([ 'watched', 'goodByeLenin' ]).serialize()
branchB.get([ 'watched', 'goodByeLenin' ]).origin().serialize()
Data listeners on references
⚠ It's also possible to listen data
events explicitly.
fired = []
branchB.get([ 'watched', 'runLolaRun' ])
.on('data', (val, stamp, item) => {
fired.push(`${val}-${item.get('favourite').compute()}`)
})
branchB.get([ 'movies', 'runLolaRun' ]).set({ favourite: true })
fired
Subscriptions
let count = 0
const items = create({
i1: {
title: 'Item 1',
items: {
sub2: ['@', 'i2'],
sub3: ['@', 'i3']
}
},
i2: {
title: 'Item2',
items: {
sub1: ['@', 'i1']
}
},
i3: {
title: 'Item3',
items: {
sub2: ['@', 'i2']
}
}
})
let subscription = items.get('i2').subscribe(() => { count++ })
count
items.set({
i2: {
title: 'Title2'
}
})
count
items.get('i3').set({
title: 'Title3'
})
count
subscription.unsubscribe()
Subscription options
count = 0
subscription = items.get('i2').subscribe({
keys: [ 'items' ],
depth: 3
}, () => { count++ })
count
items.set({
i2: {
title: 'Title2'
}
})
count
items.get('i1').set({
title: 'Title1'
})
count
items.get('i3').set({
description: 'Description3'
})
count
subscription.unsubscribe()
Over the wire
Server
const server = items.listen(7171)
items.on('log', line => {
line
server.close()
})
Client
const cItems = create()
const client = cItems.connect('ws://localhost:7171')
cItems.get('i1', {}).subscribe(
{ depth: 1 },
i1 => {
if (i1.get('title')) {
cItems.serialize()
cItems.emit('log', 'Hello!')
client.socket.close()
}
}
)