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

@set-state/core

v0.1.4

Published

state management in less than 1 Kb

Downloads

12

Readme

@set-state/core

npm version Build Status Coverage Status License: MIT Codestyle Prettier Standard Version conduct

state management in less than 1 Kb

By Paul Grenier (@AutoSponge)

After using Redux, I looked for a simpler approach for some of my work. I also wanted the flexibility to manage state at the part level rather than the app level. I wanted to keep the reactive aspects that I liked in Redux, but be able to react to smaller, scoped changes (like a cursor).

@set-state helps you simplify state management. In @set-state, changes are synchronous. This makes app logic easier to write and understand. It was heavily inspired by PureState. @set-state/core was designed to be small yet flexible so you can create your own types of systems.

Getting Started

npm install --save @set-state/core

// import factory, {state} from '@set-state/core'
const factory = require('./dist/core')
const state = factory.state

How to use it

@set-state uses three main types of functions:

Factories create states and states create nodes. Nodes make up the heart of @set-state. When nodes read the values of other nodes, they create a graph/tree structure. This tree will update "downstream" nodes when "upstream" nodes change.

Other types of functions, plugin, listener, and projection, allow you to change the behavior of nodes and respond to changes in value.

See the API docs.

// Stateful variables are just JS values wrapped with a `state` call.
// Since the resulting function acts like a container, you can use `const`
const x = state(0)

// This reads a stateful variable's current value
x() // => 0

// This writes a stateful variable, changing it's current value
x(1)

// Stateful variables can depend on other stateful variables
const y = state(() => x() + 1)
const z = state(() => [x(), y(), x() + y()])

x() // => 1
y() // => 2
z() // => [1, 2, 3]

// If you change a stateful variable, all variables that depend on it are updated synchronosly.

x(10) // sets x to 10

x() // => 10
y() // => 11
z() // => [10, 11, 21]

// If you use branching logic in a stateful variable, set the dependencies as default arguments to avoid issues with the dependency tree not updating
const a = state(true)
const b = state(2)
const c = state(($b = b()) => a() ? 1 : $b)
a(false)
c() // => 2
a(true)
b(3)
c() // => 1
a(false)
c() // => 3

Factory

Factory functions, like the default export of core, can create state functions that use any number of custom plugins. You can pass a factory a context which can be used to differentiate state or node functions.

factory
/*
{ [Function: factory]
  isState: [Function: isState],   <== fn to identify states
  state:
   { [Function: state]
   ...
   context: 'set-state:core' }}
*/

// For most applications, you'll only need a single context
// but creating a separate graph (like for a web worker) allows for
// separate graphs to maintain their own synchonous graphs.
state.context // => 'set-state:core'
const workerState = factory('worker')
workerState.context // => 'worker'
const w1 = workerState(1)
w1() // => 1
w1.context // => 'worker'
workerState.isOwnNode(w1) // => true
state.isNode(w1) // => true
state.isNode(w1()) // => false
state.isOwnNode(w1) // => false

State

State functions, like the named state export of core, create node functions. Create complex apps by composing state nodes and reacting to changes.

state
/*
{ [Function: state]
  use: [Function],        <== add a plugin
  isOwnNode: [Function],  <== compare contexts of a node
  plugins: Set {},        <== list of plugins
  updating: Set {},       <== list of updating nodes
  capturing: Set {},      <== list of recalculating nodes
  is: Symbol(set-state),  <== type identification symbol
  of: [Circular],         <== alias state()
  END: Symbol(set-state:end),       <== symbol passed to end/freeze a node
  GUARD: Symbol(set-state:guard),   <== symbol passed to seal a node
  isNode: [Function: isNode],       <== fn to identify nodes
  end: [Function],                  <== end/freeze a node
  freeze: [Function],               <== end/freeze a node
  seal: [Function],                 <== seal a node
  isSealed: [Function: isSealed],   <== determine if a node is sealed
  isFinished: [Function: isFrozen], <== determine if a node is is frozen
  isFrozen: [Function: isFrozen],   <== alias isFinished
  context: 'set-state:core' }       <== context pass to the factory
*/

Plugin

Factories can also take a list of plugins. Plugins run like middleware when new nodes are created. You can also add individual plugins to a state later using the .use() method. Plugins take node as a parameter. Most plugins will attach methods to the node (like a serialize method) or create new, dependent nodes called projections. But you're not limited to those actions.

// Create a JSON serializer.
const isPrimitive = x => Object(x) !== x
const native = x => (x.toJSON === undefined ? x : x.toJSON())
const toJSON = node => {
  node.toJSON = () => isPrimitive(node.value) ? node.value : native(node.value)
}
const serializableState = factory().use(toJSON)
const s1 = serializableState({_id: 1, created: new Date(Date.UTC(2018, 0, 1))})
JSON.stringify(s1) // => '{"_id":1,"created":"2018-01-01T00:00:00.000Z"}'

// Create a projection.
const map = node => node.map = fn => node.state(() => fn(node())).seal()
state.use(map)
const m1 = state(10)
const m2 = m1.map(n => n * n)
m1() // => 10
m2() // => 100
m2(`this won't worked, it's sealed`) // => 100

// Create a plugin that keeps node value types the same.
const setValue = node => {
  node.set = val => {
    if (typeof val === typeof node.value) {
      return node(val)
    }
    // throw new TypeError(...)
  }
}
const typedState = factory('typed', [setValue])
const t1 = typedState(new Date(Date.UTC(2018, 0, 1)))
t1.set(Date.now()) // ignored update since Date.now() is a number
t1().toUTCString() // => 'Mon, 01 Jan 2018 00:00:00 GMT'

// Create a "safe" plugin
const myPlugin = node => {
  // namespace your plugin
  const name = 'myPlugin'
  const state = node.state
  // isolate this plugin's vars
  const vars = node.locals[name] = node.locals[name] || {}
  const pluginFn = () => {/* ...node, state, vars */}
  // check that the plugin's namespace isn't taken
  if (!node[name]) {
    node[name] = pluginFn
  } else {
    // throw new Error(`unable to use plugin: ${name}, namespace in use.`)
  }
}

// You can interact with the plugins added
state.plugins.size // => 1
serializableState.plugins.has(toJSON) // => true
typedState.plugins.clear()

Node

Nodes properties help keep values consistent across a context. You can also use these properties when writing your own plugins.

state(0)
/*
{ [Function: node]         <== this is your new node
  locals: {},              <== this is where you can put variables from plugins
  state:                   <== this is a ref to the state fn that created this node
   { [Function: state]
     ...
     context: 'set-state:core' },
  is: Symbol(set-state:node), <== type identification symbol
  context: 'set-state:core',  <== context will match the state that created it
  listeners: Set {},        <== you can tell if a node has listeners
  dependents: Set {},       <== you can tell if a node has dependents
  dependencies: Set {},     <== you can tell if a node has dependencies
  on: [Function],           <== add a listener
  seal: [Function],         <== seal this node
  end: [Function],          <== freeze/end this node
  freeze: [Function],       <== freeze/end this node
  toString: [Function],     <== convenience fn
  valueOf: [Function],      <== convenience fn
  compute: [Function],      <== value as a fn (compute.toString())
  value: 0 }                <== the raw value (always up-to-date)
*/

Projection

Projections are special nodes. By convention, all projections depend on at least one other node for their value and are "sealed". This means that the function used to calculate their value can not be changed after they are created.

const p0 = state('beep')

// simple projections of p0
const p1 = state.seal(() => `${p0()}!`)
const p2 = state(() => p0().replace(/e/g, 'o')).seal()
const p3 = state(() => `${p0()} ${p2()} ${p1()}` )
p1() // => 'beep!'
p2() // => 'boop'
p3() // => 'beep boop beep!'

// projection using a plugin
const either = node =>
  node.either = (a, b) =>
    node.state(() => // <<< NOTE: use the 'state' from the same context
      a(node()) || b(node())).seal()

state.use(either)
const e0 = state(6)
const e1 = e0.either(n => n % 3, n => n % 2)
e1() // => 0
e0(9)
e1() // => 1

Listener

A listener gets called when a change occurs on the node attached to it. When called, a listener receives nextValue and previousValue as arguments.

const l0 = state('#foo')
const l1 = state('foo')
const link = state((href = l0(), text = l1()) => ({href, text}))
const render = ({href, text}) => {
  root.innerHTML = `<a href="${href}">${text}</a>`
}
link.on(render)
// listeners only fire on change
root.innerHTML // => ''
l1('bar')
root.innerHTML // => '<a href="#foo">bar</a>'