Created
October 12, 2018 22:17
-
-
Save rjhilgefort/5eb25dad86fe76092d0ca5ce28314f0b to your computer and use it in GitHub Desktop.
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 R = require('ramda') | |
const { Logger } = require('../logger') | |
// higher order "plugin" factories; these functions accept | |
// a factory, add functionality to it, and then return it | |
const locksHof = require('./locksHof') | |
const loggerHof = require('./methodLoggerHof') | |
const speakHof = require('./speakHof') | |
// create a "base" higher-order factory that has adds the methods | |
// from all 3 higher order factories (locks, log, speak) | |
const baseFactoryHof = R.compose( | |
// locksHof adds lock flags to all instance methods so that the same | |
// method cannot run twice concurrently | |
// we did not make this configurable, so we don't pass anything in | |
locksHof, | |
// loggerHof will cause every method to log its params and results | |
// this hof is configurable; we tell it what level to log at | |
loggerHof({ level: 'info' }), | |
// speakHof will add a .speak() method | |
speakHof, | |
) | |
// create a factory | |
const MathFactory = services => ({ someOption } = { someOption: 3 }) => ({ | |
getValue: (val = 0) => new Promise(resolve => setTimeout(resolve, 10, val + someOption)) | |
}) | |
// create a logger dependency | |
const logger = new Logger('factories', { level: 'info' }) | |
;(async () => { | |
// extend the factory with the baseFactoryHof | |
const MathFactoryExtended = baseFactoryHof(MathFactory) | |
// call the factory to get the interface | |
// with a class, we would do new MyService(services, options) | |
// since factories are functions, we do: MyFactory(services)(options) | |
const services = { logger } | |
const options = { someOption: 1 } | |
const math = MathFactoryExtended(services)(options) | |
// now we have the extended factory | |
const result = await math.getValue(1) | |
// using functionality from speakHof | |
math.speak(result) | |
// using functionality from locksHof | |
// math.lock('getValue') | |
await math.getValue() | |
})() |
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
module.exports = () => ({ | |
_locks: {}, | |
_set (key, value) { | |
this._locks[key] = value | |
}, | |
lock (key) { | |
this.assert(key, false) | |
this._set(key, true) | |
}, | |
unlock (key) { | |
this.assert(key, true) | |
this._set(key, false) | |
}, | |
assert (key, value) { | |
const state = this._locks[key] || false | |
if (state !== value) { | |
const verb = value ? 'add' : 'remove' | |
const status = value ? 'already' : 'not yet' | |
const message = `cannot ${verb} lock on method '${key}'; method is ${status} locked` | |
throw new Error(message) | |
} | |
}, | |
}) |
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 Locks = require('./locks') | |
const wrapMethods = require('./wrapMethods') | |
module.exports = factory => services => (...params) => { | |
const parent = factory(services)(...params) | |
const locks = Locks() | |
const methods = wrapMethods(parent, { | |
before ({ method }) { | |
locks.lock(method) | |
}, | |
after ({ method }) { | |
locks.unlock(method) | |
}, | |
}) | |
return { | |
...methods, | |
...locks | |
} | |
} |
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 assert = require('assert') | |
const wrapMethods = require('./wrapMethods') | |
module.exports = ({ level } = { level: 'info' }) => factory => services => (...params) => { | |
const parent = factory(services)(...params) | |
const { logger } = services | |
assert(logger, 'services.logger is not defined') | |
const methods = wrapMethods(parent, { | |
before ({ method, params }) { | |
logger[level]({ method, params }, 'calling method') | |
}, | |
after ({ method, params, result }) { | |
logger[level]({ method, params, result }, 'method result') | |
}, | |
}) | |
return { | |
...methods | |
} | |
} |
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
module.exports = factory => services => (...params) => ({ | |
// pass through all parent methods | |
...factory(services)(...params), | |
// add our own methods | |
speak (msg) { | |
console.log(msg) | |
}, | |
}) |
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 R = require('ramda') | |
module.exports = (obj, { before, after } = { before: R.always(), after: R.always() }) => { | |
const methods = R.mapObjIndexed((x, method) => { | |
if (typeof x !== 'function') { | |
return x | |
} | |
return (...params) => { | |
before({ method, params }) | |
const result = x(...params) | |
// async | |
if (result && result.constructor === Promise) { | |
return result.then(y => { after({ method, params, result: y }); return y }) | |
} | |
// sync | |
after({ method, params, result }) | |
return result | |
} | |
}, obj) | |
return { ...methods } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment