Skip to content

Instantly share code, notes, and snippets.

@goatslacker
Last active August 29, 2015 14:20
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save goatslacker/da0377e1413a526aa5ce to your computer and use it in GitHub Desktop.
Save goatslacker/da0377e1413a526aa5ce to your computer and use it in GitHub Desktop.
A stateless store version of alt
import { Dispatcher } from 'flux'
function isPromise(obj) {
return obj && (typeof obj === 'object' || typeof obj === 'function') &&
typeof obj.then === 'function'
}
const inject = Math.random().toString(16).substr(2, 7)
class Alt {
constructor(options = {}) {
this.dispatcher = new options.Dispatcher()
this.serialize = options.serialize || JSON.stringify
this.deserialize = options.deserialize || JSON.parse
this.actions = {}
this.stores = {}
this.history = options.history || 2
this.payloads = []
this.state = this.serialize({})
}
createActions(namespace, model) {
this.actions[namespace] = Object.keys(model).reduce((actions, name) => {
const action = (...args) => {
const data = model[name](...args)
const meta = { namespace, name, args }
if (isPromise(data)) {
data
.then(x => this.dispatcher.dispatch({ action, data: x, meta }))
.catch(e => this.dispatcher.dispatch({ action: null, data: e, meta }))
} else {
this.dispatcher.dispatch({ action, data, meta })
}
}
actions[name] = action
return actions
}, {})
return this.actions[namespace]
}
createStore(namespace, reducer, initialState, ...args) {
const subscriptions = []
let state = initialState
this.payloads[namespace] = []
// save the state to app level state
const saveState = state => {
const appState = this.deserialize(this.state)
appState[namespace] = state
this.state = this.serialize(appState)
if (this.payloads.length - 1 < this.history) {
this.payloads[namespace].push(state)
}
}
// dispatch event
this.dispatcher.register((payload) => {
state = reducer(state, payload, ...args)
subscriptions.forEach(f => f(state))
})
// our store
const store = {
name: namespace,
subscribe: (onChange) => {
const id = subscriptions.push(onChange)
return () => subscriptions.splice(id, 1)
},
peek: (n = 1) => {
const payloads = this.payloads[namespace]
return payloads[payloads.length - n - 1]
},
[inject]: newState => saveState(state = newState)
}
// save the initial state
saveState(initialState)
// make it available
this.stores[namespace] = store
// listener to save state locally for history
store.subscribe(saveState)
return store
}
takeSnapshot() {
return this.state
}
bootstrap(serializedData) {
const data = this.deserialize(serializedData)
Object.keys(data).forEach((name) => {
this.stores[name][inject](data[name])
})
}
prepare(store, payload) {
return this.serialize({
[store.name]: payload
})
}
recycle() {
Object.keys(this.stores).forEach((name) => {
this.stores[name][inject](this.payloads[name][0])
})
}
flush() {
const snapshot = this.takeSnapshot()
this.recycle()
return snapshot
}
}
import assign from 'object-assign'
const alt = new Alt({ Dispatcher, history: 300 })
// The name is for
// A) to reference this later
// B) DispatcherRecorder and logging
const actions = alt.createActions('Actions', {
fire(x) {
return x
},
blah() { }
})
console.log('Actions =>', actions)
const store = alt.createStore('MyStore', (state, { action, data, meta }) => {
return assign({}, state, {
b: data
})
}, {
a: 0,
b: 0
})
console.log('Store =>', store)
console.log('Snapshot 1, 3', alt.takeSnapshot())
store.subscribe((state) => {
console.log('* Change Event:', state)
})
actions.fire(1)
actions.fire(2)
actions.fire(3)
actions.fire(4)
console.log('PEEKING 0, 1', store.peek(3))
const newState = store.peek(3) // 0, 1
const serialized = alt.prepare(store, newState) // MyStore: { a: 0, b: 1 }
alt.bootstrap(serialized)
console.log('Snapshot 0, 1', alt.takeSnapshot())
@threepointone
Copy link

neat!

@goatslacker
Copy link
Author

The only thing missing from this is how to deal with derived data.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment