brisky-struct

An observable data structure

Usage no npm install needed!

<script type="module">
  import briskyStruct from 'https://cdn.skypack.dev/brisky-struct';
</script>

README

brisky-struct

An observable data structure

Build Status js-standard-style npm version Coverage Status

  • Deep memory efficient prototypes
  • Every value is observable
  • Serializable references
  • Fast reactive state management. Inspired by virtual-dom tree-diffing algorithms and merkle-trees
  • Powerful query syntax
  • Fast emitters
  • Async helpers, work with generators, promises and iterators
  • Low footprint (6kb gzipped)

CRUD

Create

const struct = require('brisky-struct')
const root = struct.create({ firstKey: 'value' })

Serialize

root.serialize() // → { "firstKey": "value" }

Set

⚠ Default behaviour is merge.

root.set({ second: { subKey: 'subValue' } })
root.serialize() // → { "firstKey": "value", "second": { "subKey": "subValue" } }

Get

root.get('second').serialize() // → { "subKey": "subValue" }

Keys

root.keys() // → [ "firstKey", "second" ]

Remove

root.set({ firstKey: null })
root.get('firstKey') // → undefined
root.keys() // → [ "second" ]

Compute

const sub = root.get(['second', 'subKey'])
sub.compute() // → "subValue"

Navigate

Key

sub.key // → "subKey"

Path

sub.path() // → ["second", "subKey"]

Parent

sub.parent().key // → "second"
sub.parent().serialize() // → { "subKey": "subValue" }

Root

sub.root().serialize() // → { "second": { "subKey": "subValue" } }
sub.root() === root // → true

Listen

On

Default listener

var results = []
root.set({ on: val => results.push(val) })
root.set({ third: 3 })
results // → [ { "third": 3 } ]

Data listener

root.set({ on: { data: val => results.push(val) } })
root.set({ fourth: 4 })
results // → [ { "third": 3 }, { "fourth": 4 } ]

Named data listener

⚠ Only named listeners won't override existing listeners. Notice that fifth appears twice in the results array.

root.set({ on: { data: { namedListener: val => results.push(val) } } })
root.set({ fifth: 5 })
results // → [ { "third": 3 }, { "fourth": 4 }, { "fifth": 5 }, { "fifth": 5 } ]

On as a method

results = []
const third = root.get('third')
third.on(val => results.push(val))
third.set('changed')
results // → [ "changed" ]
root.set({ third: 'again' })
results // → [ "changed", "again" ]

Once

Once as a method

results = []
const fourth = root.get('fourth')
fourth.once('four', val => results.push(val))
fourth.set('will be ignored')
results // → [ ]
fourth.set('four')
results // → [ "four" ]

Once as a promise

results = []
fourth.once().then(val => results.push(val))
fourth.set('changed')
results // → [ "changed" ]
fourth.set('will be ignored')
results // → [ "changed" ]

Emit

⚠ Events fired on a path can be listened only at exact same path.

const errors = []
root.on('error', err => errors.push(err))
root.emit('error', 'satellites are not aligned')
errors // → [ "satellites are not aligned" ]
sub.once('error', err => errors.push(err))
sub.emit('error', 'splines are not reticulated')
errors // → [ "satellites are not aligned", "splines are not reticulated" ]

More about get and set

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.

root.get('firstKey', 'newValue').compute() // → "newValue"
root.get('firstKey').compute() // → "newValue"
root.get('fifth', 'newValue').compute() // → 5

Get a path with methods

⚠ Available methods are root, parent and compute.

root.get(['firstKey', 'compute']) // → "newValue"
root.get(['second', 'subKey', 'parent']).serialize() // → { "subKey": "subValue" }
sub.get(['root', 'fifth', 'compute']) // → 5

Set without merge (reset)

Third parameter of set is a reset flag.

⚠ Second parameter is a stamp, will come to our plate on further chapters.

const second = root.get('second')
second.set({ newSubKey: 'newSubValue' })
second.serialize() // → { "subKey": "subValue", "newSubKey": "newSubValue" }
second.set({ onlySubKey: 'onlySubValue' }, void 0, true)
second.serialize() // → { "onlySubKey": "onlySubValue" }

Master and branches

const master = struct.create({
  movies: {
    tt0130827: {
     year: 1998,
     imdb: 7.7,
     title: 'Run Lola Run'
    },
    tt0301357: {
      year: 2003,
      imdb: 7.7,
      title: 'Good Bye Lenin'
    },
    tt0408777: {
      year: 2004,
      imdb: 7.5,
      title: 'The Edukators'
    }
  }
})

const branchM = master.create({
  userName:'Mustafa',
  movies: {
    tt0130827: { favourite: true },
    tt0408777: { favourite: true }
  }
})

const branchJ = master.create({
  userName:'Jim',
  movies: {
    tt0301357: { favourite: true }
  }
})

master.get('userName') // → undefined

branchM.get(['movies', 'tt0408777']).serialize()
// → { "year": 2004, "imdb": 7.5, "title": "The Edukators", "favourite": true }
branchJ.get(['movies', 'tt0408777']).serialize()
// → { "year": 2004, "imdb": 7.5, "title": "The Edukators" }
master.get(['movies', 'tt0408777']).serialize()
// → { "year": 2004, "imdb": 7.5, "title": "The Edukators" }

master.get(['movies', 'tt0130827', 'rating'], 'R')
branchJ.get(['movies', 'tt0130827', 'rating', 'compute']) // → "R"
branchM.get(['movies', 'tt0130827', 'rating', 'compute']) // → "R"

branchJ.get(['movies', 'tt0130827', 'rating']).set('G')
branchM.get(['movies', 'tt0130827', 'rating', 'compute']) // → "R"

master.get(['movies', 'tt0130827', 'rating']).set('PG')
branchM.get(['movies', 'tt0130827', 'rating', 'compute']) // → "PG"
branchJ.get(['movies', 'tt0130827', 'rating', 'compute']) // → "G"