npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

stx

v0.6.14

Published

a blazing fast state manager with network sync out of the box

Downloads

85

Readme

stx

A blazing fast state manager with network sync out of the box.

Build Status js-standard-style npm version Coverage Status

  • 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)

Here is a persistency plugin for RocksDB

Qucik Start Guide

CRUD operations

Create

const { create } = require('stx')
const state = create({ firstKey: 'value' })

Serialize

state.serialize() // → { firstKey: 'value' }

Set

⚠ Default behaviour is merge.

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

Get

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

Remove

state.set({ firstKey: null })
state.get('firstKey') // → undefined
state.serialize() // → { second: { subKey: 'subValue' } }

Compute

⚠ Paths are represented as arrays for nested keys.

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

Get with set

Second parameter of get is a default value for the path.

⚠ It will be set and returned if the relative path is undefined, otherwise it will be ignored.

state.get('first', 1).compute() // → 1
state.get('first').compute() // → 1

Navigate

Path

subKey.path() // → [ 'second', 'subKey' ]

Parent

subKey.parent().serialize() // → { subKey: 'subValue' }

Root

subKey.root().serialize() // → { second: { subKey: 'subValue' }, first: 1 }

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 // → [ 'set-changed' ]
state.set({ third: 'again' })
fired // → [ 'set-changed', 'set-again' ]

Off

listener.off()
third.set('yet again')
fired // → [ 'set-changed', 'set-again' ]

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 // → [ 'satellites are not aligned' ]
subKey.on('error', err => errors.push(err))
subKey.emit('error', 'splines are not reticulated')
errors // → [ 'satellites are not aligned', 'splines are not reticulated' ]

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') // → undefined

branchA.get(['movies', 'theEdukators']).serialize()
// → { favourite: true, year: 2004, imdb: 7.5, title: 'The Edukators' }
branchB.get(['movies', 'theEdukators']).serialize()
// → { year: 2004, imdb: 7.5, title: 'The Edukators' }
master.get(['movies', 'theEdukators']).serialize()
// → { year: 2004, imdb: 7.5, title: 'The Edukators' }

master.get(['movies', 'runLolaRun', 'rating'], 'R')
branchB.get(['movies', 'runLolaRun', 'rating']).compute() // → R
branchA.get(['movies', 'runLolaRun', 'rating']).compute() // → R

branchB.get(['movies', 'runLolaRun', 'rating']).set('G')
branchA.get(['movies', 'runLolaRun', 'rating']).compute() // → R

master.get(['movies', 'runLolaRun', 'rating']).set('PG')
branchA.get(['movies', 'runLolaRun', 'rating']).compute() // → PG
branchB.get(['movies', 'runLolaRun', 'rating']).compute() // → G

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 // → [ 'A-now', 'B-now', 'A-later' ]

References

branchB.set({
  watched: {
    runLolaRun: [ '@', 'movies', 'runLolaRun' ],
    goodByeLenin: [ '@', 'movies', 'goodByeLenin' ]
  } 
})

branchB.get([ 'watched', 'goodByeLenin', 'favourite' ]).compute() // → true
branchB.get([ 'watched', 'runLolaRun', 'favourite' ]) // → undefined

Origin

branchB.get([ 'watched', 'goodByeLenin' ]).serialize()
// → [ '@', 'movies', 'goodByeLenin' ]
branchB.get([ 'watched', 'goodByeLenin' ]).origin().serialize()
// → { favourite: true, year: 2003, imdb: 7.7, title: 'Good Bye Lenin' }

Data listeners on references

⚠ It is 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 // → [ 'add-key-true' ]

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 // → 1 (fired once for existing path)

items.set({
  i2: {
    title: 'Title2'
  }
})
count // → 2 (fired once more for immediate child)

items.get('i3').set({
  title: 'Title3'
})
count // → 3 (fired once more for nested child)
// i2.items.sub1.items.sub3.title === i3.title

subscription.unsubscribe()

Subscription options

count = 0
subscription = items.get('i2').subscribe({
  keys: [ 'items' ],
  depth: 3
}, () => { count++ })
count // → 1 (fired once for existing path)

items.set({
  i2: {
    title: 'Title2'
  }
})
count // → 1 (did not fire for ignored key)

items.get('i1').set({
  title: 'Title1'
})
count // → 2 (fired once more for 3rd level depth nested)

items.get('i3').set({
  description: 'Description3'
})
count // → 2 (did not fire for more than 3rd level depth)

subscription.unsubscribe()

Over the wire

Server

const server = items.listen(7171)
items.on('log', line => {
  line // → Hello!
  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() // → { i1: { title: 'Title1', items: {} } }
      cItems.emit('log', 'Hello!')
      client.socket.close()
    }
  }
)