Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@yelouafi
Last active February 3, 2016 15:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yelouafi/b8605c7291fdfc5ec19d to your computer and use it in GitHub Desktop.
Save yelouafi/b8605c7291fdfc5ec19d to your computer and use it in GitHub Desktop.
redux actions specs
const empty = {}
const ktrue = () => true
const contract = p => (v, field) => {
if(p(v, field))
return v
throw `Invalid value ${v} for field ${field}`
}
// builtin validators
const any = contract(ktrue)
const number = contract(v => typeof v === 'number')
const string = contract(v => typeof v === 'string')
const boolean = contract(v => typeof v === 'boolean')
// higher order validors
const maybe = vtype => contract((v, field) => v === undefined || v === null || vtype(v, field))
const array = vtype => contract(arr => arr.every(vtype))
const tuple = (...vtypes) => contract(arr => arr.every((val, idx) => vtypes[idx](val)))
const shape = spec => (obj, field) => {
const keys = Object.keys(spec)
for(var i=0; i<keys.length; i++) {
const key = keys[i]
const c = spec[key]
const val = obj[key]
const path = `${field}.${key}`
c(val, path)
}
return obj
}
const ACName = str => str.toLowerCase().replace(/\_([a-z])/g, g => g[1].toUpperCase())
function makeAC(acType, acName, spec) {
return (...args) => {
const res = {type: acType}
const isArray = Array.isArray(spec)
const fields = isArray ? spec : Object.keys(spec)
fields.forEach((field, idx) => {
const arg = args[idx]
const c = isArray ? any : spec[field]
try {
res[field] = c(arg, field)
} catch(e) {
throw new TypeError(`invalid argument passed to action '${acName}'; ${e}`)
}
})
return res
}
}
function actionsSpec(specs) {
const res = {}
Object.keys(specs).forEach(key => {
const spec = specs[key]
res[key] = key // constant
const acName = ACName(key)
if(typeof spec === 'string')
res[acName] = () => ({type: key})
else if(typeof spec === 'function')
res[acName] = (...args) => Object.assign({type: key}, spec(...args))
else
res[acName] = makeAC(key, acName, spec)
})
return res
}
// Example
const actions = actionsSpec({
START: empty, // actions.start = () => ({type: 'START')
REQUEST : {id: number}, // actions.request = id => ({type: 'REQUEST', id})
RESPONSE: { // actions.response = response => ({type: 'RESPONSE', response})
response: shape({
id: number,
name: maybe(string) // optional
})
}
})
// should contain action types
assert.equal(actions.REQUEST, 'REQUEST')
assert.equal(actions.RESPONSE, 'RESPONSE')
// should contain action creators
assert.equal(typeof actions.request, 'function')
assert.equal(typeof actions.response, 'function')
// action creators should build and return action ojects
assert.deepEqual(actions.request(1), { type: 'REQUEST', id: 1 })
assert.equal(actions.response({id: 1, name: 'joe'}), { type: 'RESPONSE', id: 1, name: 'joe' })
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment