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

@orodio/central

v0.0.2

Published

orodio does central

Downloads

1

Readme

@orodio/central

Build Status

Install

yarn add @orodio/central

General terms

intent future tense

A request from the user to do something or know about something.

  • Only ever called from the view or from another intent.
  • The primary way to interact with the api.
  • The only thing that ever creates/dispatches an event.

event past tense

Something that did happen.

  • Will posibly trigger a change in the applications state.
  • Can only be created by the dispatch function.

handler current tense

Takes an event and the current state and gives us the new state.

  • Can only be triggered by an event.
  • Must return the new state.

Usage

createStore is passed an initialState and returns the following functions.

const initialState({
  counters:new Map({
    "counter_1": new $Counter({
      id: "counter_1",
      title: "foo",
      count: 0,
    })
  })
})
const { dispatch, getState, connect, handler, intent, $ } = createStore(initialState)
  • initialState must be an immutable Map from ImmutableJS.

dispatch dispatches events into the store

dispatch('counter_set', counterId, counter)
  • Should only ever be called from inside of an intent (never directly from the view)

handler registers how an event transforms the current state

handler('counter_set', (state, counterId, counter) => {
  return state.setIn(['counters', counterId], counter)
})
  • Must return the next state
  • creates a default intent with the same event name. ie: counter_set

intent registers how an event is created

intent('counter_set', (counter) => {
  return dispatch('counter_set', counter.id, counter)
})

intent('counter_get', ({ id }) => {
  return getCounterFromApi({ id })
    .then(normalizeCounter)
    .then($('counter_set'))
})
  • All async needs to happen in an intent
  • the only place you should ever call dispatch is in an intent

intent and $ how you access the intents you registerd

const counter2 = new $Counter({
  id: 'counter_2',
  title: 'bar',
  count: 10,
})

$('counter_set')(newCounter)

const counter3 = new $Counter({
  id: 'counter_3',
  title: 'baz',
  count: 15,
})

const counterSet = $('counter_set')
counterSet(counter3)
  • $/intents and connect should be the only parts of a store you call in your view.

getState gets the current state

getState() // Map({ counters:Map({ "counter_1":$Counter({ id:..., title:..., count:... }) }) })
getState(['counters', 'counter_1', 'title']) // "foo"
getState(['counters', 'counter_1', 'doesnt_exist'], 'default value') // "default_value"
  • Returns the current state (Immutable Map)
  • If if passed arguments acts like .getIn([cursor], defaultValue)

connect connects a react component to the store

const inc = $('counter_inc')

const Counter = ({ id, title, count }) =>
  <div>
    <strong>{ title }:</strong> { count }
    <button onClick={() => inc(id)}>+</button>
  </div>

const mapStateToProps = (getState, props) => {
  const { id } = props
  return {
    title: getState(['counters', id, 'title'], ""),
    count: getState(['counters', id, 'count'], 0),
  }
}

export default connect(mapStateToProps)(Counter)
  • getState in mapStateToProps is an actual getState function like the one returned from createStore
  • You want to do as little computation inside of mapStateToProps as possible

Something to keep in mind

dispatch, intents and the callback passed into handler are all variadic, for example:

  • Given a dispatch call like: dispatch("event_name", a, b, c, d, e, f)
  • It will be handled by the handler: handler("event_name", (currentState, a, b, c, d, e, f) => nextState)
  • Which creates a default intent that is equivalent to:
    intent("event_name", (a, b, c, d, e, f) =>
         dispatch('event_name', a, b, c, d, e, f))
  • Which can then be called with: $('event_name')(a, b, c, d, e, f)

In the future the following will be equivalent (using the above as an example)

This is not the case right now.

  $('event_name')(a, b, c, d, e, f)
  $`event_name`(a, b, c, d, e, f)
  $.event_name(a, b, c, d, e, f)

Example

import createStore from '@orodio/central'
import { Map, Record } from 'immutable'

// Implementation detail
const $Counter = new Record({
  id:    null,
  title: "",
  count: 0,
})

// our stores initial state
const initialState = new Map({
  counters: new Map({
    "a": new $Counter({ id:"a", title:"foo", count:"10" }),
    "b": new $Counter({ id:"b", title:"bar", count:"15" }),
  }),
})

// the creation of our store
const {
  dispatch,
  getState,
  connect,
  handler,
  intent,
  $,
} = createStore(initialState)


// register how an "inc_by" event can transform the state
handler('inc_by', (state, id, delta = 1) =>
  state.updateIn(['counters', id, 'count'], count => count + delta))

// register our intents
// our above handler already created an `inc_by` intent under the hood
// but if it didnt the following would do the same thing.
// We could also overload the 'inc_by' intent here if we wanted to, as
// the 'inc_by' handler is only actually called if we dispatch an 'inc_by' event.
// we default the intent to do this as a convenience but its important to
// remember that there isnt always a one-to-one mapping of intents and handlers
// later we can access this intent with `$('inc_by')`
// or call it with `$('inc_by')("a", 10)` which would 'increment counter "a" by 10'
intent('inc_by')

// this intent is calling another intent
// it can be accessed by `$('inc')`
// and be called with `$('inc')("a")` which would 'increment counter "a" by 1'
intent('inc', (id) => $('inc_by')(id, 1))

// this is an intent dispatching a totally different event
// it could just as easily have been done the same way as our 'inc' intent above
// it can be accessed by `$('dec')`
// and be called with `$('dec')("a")` which would 'increment counter "a" by -1'
intent('dec', (id) => dispatch('inc', id, -1))

// if we had async stuff (like hitting an api) we would do it in an intent.
handler('counters_set', (state, counters=new Map()) => {
  return state.setIn(['counters'], counters)
})
intent('counters_get', () => {
  return getCountersFromApi() // conveniently returns the data structure our store uses :P
    .then(counters => dispatch('counters_set', counters))
})

handler('counter_set', (state, id, counter) => {
  return state.setIn(['counters', id], counter)
})
intent('counter_get', ({ id }) => {
  return getCounterFromApi({ id })
    .then(counter => new $Counter(counter))
    .then(counter => dispatch('counter_set', counter.id, counter))
})

// we will leverage the fact that our `counters_get` and `counter_get` intents return
// a promise later in our components to show a loading state

//
// In our Components we could do some things like this
//

const inc = id => () =>
  $('inc')(id)

const dec = id => () =>
  $('dec')(id)

// what does it look like?
export const Counter = ({
  id      = null,
  title   = '',
  count   = 0,
  total   = 0,
  loading = false,
  dec     = dec, // lets us mock dec for testing
  inc     = inc, // lets us mock inc for testing
}) => {
  if (loading) return <div>Loading...</div>
  return <div>
    <strong>{ title }:</strong>
    <span>{ count }/{ total }</span>
    <button onClick={dec(id)}>-</button>
    <button onClick={inc(id)}>+</button>
  </div>
}

// what is the current state of our component?
// do we need any additional data from the server?
export class XhrCounter extends React.Component {
  state = {
    loading: true
  }

  componentDidMount () {
    this.mount = true
    $('counter_get')(this.props)
      .then(() => this.mount && this.setState({ loading:false }))
  }

  componentWillUnmount () {
    this.mount = false
  }

  render () {
    const { loading } = this.state
    return <Counter {...this.props}/>
  }
}

export const mapStateToProps = (state, { id }) => ({
  title: state(['counters', id, 'title'], ''),
  count: state(['counters', id, 'count'], 0),
  total: state()
          .getIn(['counters'], new Map())
          .reduce((total, counter) => total + counter.count, 0),
})

export const mapPropsToKey = props =>
  props.id

// connect returns a component that can be used as a covariant functor in a map function.
// how do we get the data from our store?
export const ConnectedCounter =
  connect(mapStateToProps)(XhrCounter, mapPropsToKey) // mapPropsToKey is completely optional

export default ConnectedCounter

//
// Another Component
//

// what does it look like?
export const Counters = ({ counters = [], loading = false }) => {
  if (loading) return <div>Loading...</div>

  return <div>
    <h1>Counters</h1>
    <div>
      { !counters.length
          ? "No Counters"
          : counters.map(ConnectedCounter)
      }
    </div>
  </div>
}

// what is the current state of our component?
// do we need any additional data from the server?
export class XhrCounters extends React.Component({
  state = {
    loading: true,
  }

  componentDidMount () {
    this.mount = true
    $('counters_get')()
      .then(() => this.mount && this.setState({ loading:false }))
  }

  componentWillUnmount () {
    this.mount = false
  }

  render () {
    const { loading } = this.state
    const { counters=[] } = this.props

    return <Counters
      counters={ counters }
      loading={ loading }
    />
  }
})

// how do we get the data from our store?
export const ConnectedCounters = connect(state => ({
  counters: state(['counters'], new Map())
              .map(counter => ({ id:counter.id }))
              .toArray(),
}))(XhrCounters)

export default ConnectedCounters


// Render it! :tada:
ReactDOM.render(<ConnectedCounters/>, domNodeToRenderIn)