Last active
February 3, 2016 15:39
-
-
Save yelouafi/b8605c7291fdfc5ec19d to your computer and use it in GitHub Desktop.
redux actions specs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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