Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

elmaz - 690 bytes Elm-inspired state management #92

Open
tunnckoCore opened this issue Aug 20, 2017 · 3 comments
Open

elmaz - 690 bytes Elm-inspired state management #92

tunnckoCore opened this issue Aug 20, 2017 · 3 comments

Comments

@tunnckoCore
Copy link
Owner

tunnckoCore commented Aug 20, 2017

index.js

import emitter from './emitter.js'

/* eslint-disable jsdoc/require-param-description */

/**
 * effects:
 * ({ name, actions }, arg1, arg2) => Promise.resolve(123)
 *
 * // aka "hard update"
 * ({ name, actions }, arg1, arg2) => ({ state }) => { whole: 'new state' }
 * ({ name, actions }, arg1, arg2) => Promise.resolve(({ state }) => { whole: 'new state' })
 *
 * // aka "soft update"
 * ({ actions, update }, posts) => ({ state }) => update({ posts })
 * ({ actions, update }, posts) => Promise.resolve(({ state }) => update({ posts })
 *
 * ({ actions, update }, posts) => () => update({ posts })
 * ({ actions, update }, posts) => Promise.resolve(() => update({ posts })
 *
 * reducers:
 *
 * // aka "soft update"
 * ({ state, name, actions, update }, posts) => update({ posts })
 *
 * // above is equivalent to this
 * ({ state, name, actions }, posts) => Object.assign({}, state, { posts })
 *
 * // aka "hard update"
 * ({ state, name, actions }, posts) => { totally: 'new state', posts }
 *
 * ({ actions, update }, posts, comments, likes) => update({ posts, comments, likes })
 * ({ state, actions, update }) => update({ count: state.count + 1 })
 *
 * @param {*} opts
 */
function elmaz (opts) {
  let { name, state, reducers, effects } = opts || {}

  state = Object.assign({}, state)
  effects = Object.assign({}, effects)
  reducers = Object.assign({}, reducers)

  const store = emitter({}).use((app) => ({
    name,
    state,
    effects,
    reducers,
    actions: {},
  }))

  Object.keys(Object.assign({}, reducers, effects)).map((name) => {
    store.actions[name] = createAction(store, name)

    // - ({ actions }, data, foo, arg3) -> actions.fooBar({ some: 'hi ' + data.name })
    // - ({ dispatch, emit }, str, abc, arg3) -> dispatch('fooBar', { some: 'data' + str })

    store.on(`${store.name}::${name}`, store.actions[name])
  })

  return store
}

elmaz.elmaz = elmaz
export default elmaz

/**
 * utils
 */

function createAction (store, name) {
  // eslint-disable-next-line
  store.dispatch = (actionName, aa, bb, cc) => {
    store.emit(`${store.name}::${actionName}`, aa, bb, cc)
  }

  return function action_ (a, b, c) {
    store.emit('action', name, a, b, c)

    const effect = store.effects[name]
    const oldState = Object.assign({}, store.state)
    const CTX = {
      name: store.name,
      emit: store.emit,
      actions: store.actions,
      dispatch: store.dispatch,
      update: updateState(store),
    }

    if (effect) {
      const applyReducer = (result) => {
        if (typeof result === 'function') {
          store.state = updateState(store, 1)(result({ state: oldState }))
          return
        }
        return result
      }

      const promise = new Promise((resolve) => {
        store.emit('effect', name, a, b, c)
        resolve(effect(CTX, a, b, c))
      })

      return promise.then(applyReducer).catch((er) => {
        store.emit('error', er)
      })
    }

    store.emit('reducer', name, a, b, c)

    CTX.state = oldState

    const reducer = store.reducers[name]
    let result = null

    try {
      result = reducer(CTX, a, b, c)
    } catch (er) {
      store.emit('error', er)
    }

    if (result && typeof result === 'object') {
      store.state = updateState(store, 1)(result) || store.state
    }
  }
}

function updateState (store, hard) {
  let called = null

  return function update_ (partialState) {
    if (!partialState) {
      return null
    }
    if (called) {
      return called
    }

    called = hard
      ? Object.assign({}, partialState)
      : Object.assign({}, store.state, partialState)

    store.emit('stateUpdate', {
      prev: store.state,
      state: called,
      partial: partialState,
    })

    return called
  }
}

emitter.js

function emitter (events) {
  const app = {
    on (name, listener) {
      ;(events[name] || (events[name] = [])).push(listener)
    },

    off (name, listener) {
      if (events[name]) {
        events[name].splice(events[name].indexOf(listener) >>> 0, 1)
      }
    },

    emit (name, ...args) {
      ;(events[name] || []).map((listener) => {
        listener(...args)
      })
      ;(events['*'] || []).map((listener) => {
        listener(name, ...args)
      })
    },

    use (plugin) {
      return Object.assign(app, plugin(app))
    },
  }

  return app
}

emitter.emitter = emitter
export default emitter
@tunnckoCore
Copy link
Owner Author

tunnckoCore commented Aug 20, 2017

example

'use strict'

const createStore = require('elmaz')

const state = {
  text: 'Hello',
  name: 'Charlike',
}
const effects = {
  changeBoth: ({ actions, dispatch, emit }, data, arg2) => {
    actions.changeText({ welcome: data.text })
    dispatch('changeName', data.name, arg2)
    emit('someCustom',12, 'woohoo')
  },
}
const reducers = {
  // - ({ actions }, data, foo, arg3) -> actions.fooBar({ some: 'hi ' + data.name })
  // - ({ dispatch, emit }, str, abc, arg3) -> dispatch('fooBar', { some: 'data' + str })
  changeText: ({ emit, state, update }, data) => update({ text: data.welcome }),
  changeName: ({ actions, state, update }, name, arg) => update({ name, arg }),
}

const store = createStore({ state, effects, reducers })

store.on('error', (er) => console.error('ER!', er))
store.on('action', (name) => console.log('action:', name))
store.on('stateUpdate', ({ prev, state, partial }) => {
  console.log('prev:', prev)
  console.log('state:', state)
  console.log('partial:', partial)
  console.log('====')
})

store.on('someCustom', (num, str) => {
  console.log('custom event:', num, str)
})

store.actions.changeBoth({ text: 'Landing!', name: 'Jon Doe' }, 123)
// equivalent:
// store.dispatch('changeBoth', { text: 'Landing!', name: 'Jon Doe' }, 123)

console.log(store.state)

@tunnckoCore
Copy link
Owner Author

without transpiling

2017-08-20-04 44 23_1280x1024_scrot

with transpilation for browsers:

2017-08-20-04 44 03_1280x1024_scrot

@tunnckoCore
Copy link
Owner Author

tunnckoCore commented Sep 10, 2017

https://github.com/tunnckoCore/elmaz

// mich-h ~700b
// mich-morph ~700b
// elmaz ~820b (elmaz + elmaz-view + elmaz-emitter + elmaz-store + elmaz-actor)
// elmaz-view ~300b
// elmaz-store ~255b
// elmaz-actor ~600b
// elmaz-emitter ~350b

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant