Comparing version 0.1.0 to 0.2.0
@@ -1,121 +0,168 @@ | ||
var arrayize, getHook, isFunction; | ||
(function() { | ||
var arrayize, defaultPosition, isArray, isFunction, isString, positions, setupAction; | ||
isFunction = (fn) => { | ||
return typeof fn === "function"; | ||
}; | ||
isFunction = function(fn) { | ||
return typeof fn === "function"; | ||
}; | ||
arrayize = (obj) => { | ||
if (Array.isArray(obj)) { | ||
return obj; | ||
} else if (obj != null) { | ||
return [obj]; | ||
} | ||
return []; | ||
}; | ||
isArray = Array.isArray.bind(Array); | ||
getHook = (obj, options) => { | ||
var arr, exec, execute, getExec, hook, register; | ||
arr = []; | ||
exec = null; | ||
execute = async(o) => { | ||
if (exec == null) { | ||
exec = getExec(); | ||
arrayize = function(obj) { | ||
if (isArray(obj)) { | ||
return obj; | ||
} else if (obj != null) { | ||
return [obj]; | ||
} | ||
return (await exec(o)); | ||
return []; | ||
}; | ||
register = (o) => { | ||
if (isFunction(o)) { | ||
o = { | ||
hook: o, | ||
prio: 0 | ||
}; | ||
isString = function(str) { | ||
return typeof str === "string" || str instanceof String; | ||
}; | ||
positions = ["init", "before", "during", "after", "end"]; | ||
defaultPosition = "during"; | ||
setupAction = function(actionName, obj, arg) { | ||
var Promise, action, call, catcher, def, hookIn, names; | ||
catcher = arg["catch"], names = arg.names, def = arg["default"], Promise = arg.Promise; | ||
if ((catcher != null) && !isFunction(catcher)) { | ||
catcher = catcher[actionName]; | ||
} | ||
if (!(((o != null ? o.hook : void 0) != null) && (o.prio != null))) { | ||
throw new Error("a hook needs a 'hook' and a 'prio' property"); | ||
} | ||
arr.push(o); | ||
return exec = null; | ||
}; | ||
getExec = () => { | ||
var reduced; | ||
reduced = (arr.reduce(((acc, current) => { | ||
var name1, tmp; | ||
tmp = acc[name1 = current.prio] != null ? acc[name1] : acc[name1] = []; | ||
tmp.push(current.hook.bind(obj)); | ||
return acc; | ||
}), [])).map((hooks) => { | ||
if (hooks.length === 1) { | ||
return hooks[0]; | ||
} else { | ||
return (o) => { | ||
return Promise.all(hooks.map((hook) => { | ||
return hook(o); | ||
})); | ||
}; | ||
call = function(o) { | ||
var promise; | ||
if (o == null) { | ||
o = obj; | ||
} | ||
}); | ||
return exec = async(o) => { | ||
var callHooks, i, len, results; | ||
results = []; | ||
for (i = 0, len = reduced.length; i < len; i++) { | ||
callHooks = reduced[i]; | ||
results.push((await callHooks(o))); | ||
promise = (action._chain.reduce((function(lastPromise, hooks) { | ||
if (hooks.length === 1) { | ||
return lastPromise.then(hooks[0].bind(obj, o, obj)); | ||
} else { | ||
return lastPromise.then(function() { | ||
return Promise.all(hooks.map(function(hook) { | ||
return hook.call(obj, o, obj); | ||
})); | ||
}); | ||
} | ||
}), Promise.resolve())).then(function() { | ||
return o; | ||
}); | ||
if (catcher != null) { | ||
return promise["catch"](catcher); | ||
} | ||
return results; | ||
return promise; | ||
}; | ||
hookIn = function(index, cb) { | ||
var base, hookInName, ref, tmp; | ||
if (isFunction(index)) { | ||
cb = index; | ||
index = def; | ||
} else if (!isFunction(cb)) { | ||
ref = index, cb = ref.cb, index = ref.index; | ||
if (index == null) { | ||
index = def; | ||
} | ||
} | ||
if (!((cb != null) && (index != null))) { | ||
hookInName = names.hookIn || "hooking-in"; | ||
throw new Error(hookInName + " needs a 'cb' and a 'index'"); | ||
} | ||
tmp = (base = action._chain)[index] != null ? base[index] : base[index] = []; | ||
tmp.push(cb); | ||
return function() { | ||
var i; | ||
if (~(i = tmp.indexOf(cb))) { | ||
return tmp = tmp.splice(i, 1); | ||
} | ||
}; | ||
}; | ||
if (names.hookIn === "") { | ||
action = hookIn; | ||
action[names.call] = call; | ||
} else if (names.call === "") { | ||
action = call; | ||
action[names.hookIn] = hookIn; | ||
} else { | ||
action = {}; | ||
action[names.call] = call; | ||
action[names.hookIn] = hookIn; | ||
} | ||
action._chain = []; | ||
action[names.reset] = function() { | ||
return action._chain = []; | ||
}; | ||
return action; | ||
}; | ||
if (options.register === "") { | ||
hook = register; | ||
hook[options.execute] = execute; | ||
} else if (options.execute === "") { | ||
hook = execute; | ||
hook[options.register] = register; | ||
} else { | ||
hook = {}; | ||
hook[options.execute] = execute; | ||
hook[options.register] = register; | ||
} | ||
hook[options.clear] = () => { | ||
hook._hooks = arr = []; | ||
return exec = null; | ||
}; | ||
hook._hooks = arr; | ||
return hook; | ||
}; | ||
module.exports = (obj, options) => { | ||
var prefix; | ||
if (options == null) { | ||
options = {}; | ||
} | ||
if (options.prefix != null) { | ||
prefix = arrayize(options.prefix); | ||
} | ||
if (options.register == null) { | ||
options.register = "register"; | ||
} | ||
if (options.execute == null) { | ||
options.execute = "execute"; | ||
} | ||
if (options.clear == null) { | ||
options.clear = "clear"; | ||
} | ||
return obj.registerHooks = (names) => { | ||
var i, j, k, len, len1, len2, name, prop, results, results1, tmp; | ||
names = arrayize(names); | ||
if (prefix) { | ||
for (i = 0, len = prefix.length; i < len; i++) { | ||
prop = prefix[i]; | ||
obj[prop] = {}; | ||
module.exports = function(obj, options) { | ||
var action, actionName, actions, def, i, j, k, len, name, names, position, ref, results, spread, tmp, v; | ||
if (options != null) { | ||
if (isString(options) || isArray(options)) { | ||
options = { | ||
actions: options | ||
}; | ||
} | ||
if (options.Promise == null) { | ||
options.Promise = Promise; | ||
} | ||
names = options.names != null ? options.names : options.names = {}; | ||
if (names.hookIn == null) { | ||
names.hookIn = "hookIn"; | ||
} | ||
if (names.call == null) { | ||
names.call = ""; | ||
} | ||
if (names.reset == null) { | ||
names.reset = "reset"; | ||
} | ||
if (names.position == null) { | ||
names.position = "position"; | ||
} | ||
if (options.position != null) { | ||
position = options.position; | ||
} else { | ||
spread = options.spread || 8; | ||
position = {}; | ||
ref = options.positions || positions; | ||
for (i = j = 0, len = ref.length; j < len; i = ++j) { | ||
name = ref[i]; | ||
position[name] = i * spread; | ||
} | ||
} | ||
if ((def = options["default"]) != null) { | ||
if (isString(def)) { | ||
options["default"] = position[def]; | ||
} else { | ||
options["default"] = def; | ||
} | ||
} else { | ||
options["default"] = position[defaultPosition]; | ||
} | ||
obj[names.position] = position; | ||
actions = options.actions; | ||
if (isString(actions)) { | ||
actions = [actions]; | ||
} | ||
if (isArray(actions)) { | ||
actions = { | ||
"": actions | ||
}; | ||
} | ||
results = []; | ||
for (j = 0, len1 = prefix.length; j < len1; j++) { | ||
prop = prefix[j]; | ||
tmp = obj[prop]; | ||
for (k in actions) { | ||
v = actions[k]; | ||
if (k) { | ||
tmp = obj[k] = {}; | ||
} else { | ||
tmp = obj; | ||
} | ||
results.push((function() { | ||
var k, len2, results1; | ||
var l, len1, ref1, results1; | ||
ref1 = arrayize(v); | ||
results1 = []; | ||
for (k = 0, len2 = names.length; k < len2; k++) { | ||
name = names[k]; | ||
results1.push(tmp[name] = getHook(obj, options)); | ||
for (l = 0, len1 = ref1.length; l < len1; l++) { | ||
action = ref1[l]; | ||
actionName = k ? k + "." + action : action; | ||
results1.push(tmp[action] = setupAction(actionName, obj, options)); | ||
} | ||
@@ -126,11 +173,5 @@ return results1; | ||
return results; | ||
} else { | ||
results1 = []; | ||
for (k = 0, len2 = names.length; k < len2; k++) { | ||
name = names[k]; | ||
results1.push(obj[name] = getHook(obj, options)); | ||
} | ||
return results1; | ||
} | ||
}; | ||
}; | ||
}).call(this); |
{ | ||
"name": "hook-up", | ||
"description": "Create your own hook api with 2-dimensional hooks - better than events or simple hooks", | ||
"version": "0.1.0", | ||
"version": "0.2.0", | ||
"homepage": "https://github.com/paulpflug/", | ||
@@ -22,8 +22,8 @@ "author": { | ||
], | ||
"dependencies": {}, | ||
"devDependencies": { | ||
"coffeescript": "^2.0.2", | ||
"script-runner": "^0.1.7", | ||
"mocha": "^4.0.1", | ||
"chai": "^4.1.2" | ||
"chai": "^4.1.2", | ||
"coffeescript": "^2.2.1", | ||
"coffee-cli": "^0.2.1", | ||
"mocha": "^5.0.0", | ||
"script-runner": "^0.1.7" | ||
}, | ||
@@ -33,3 +33,4 @@ "keywords": [], | ||
"scripts": { | ||
"build": "coffee --bare --no-header --compile --output lib/ src/*.coffee", | ||
"build": "run-npm build:*", | ||
"build:node": "coffee-cli --no-header --compile --output lib/ src/*.coffee", | ||
"test": "mocha", | ||
@@ -36,0 +37,0 @@ "preversion": "npm run test", |
153
README.md
# hook-up | ||
Create your own hook api with 2-dimensional hooks - better than events or simple hooks. | ||
**Speed, functionality, simplicity - choose two.** | ||
This fundamental tradeoff in programming is as true as always. | ||
We are just better at hiding complexity - regular severe security flaws are a reminder of what we already hid away. | ||
The only way we can improve this tradeoff is *clever program design*. | ||
Over the last few years of programming I produced two very helpful guidelines | ||
- aim for declarative programming | ||
- separate by functionality | ||
(please note the absence of object orientated programming - which is *often* increasing complexity without any gain) | ||
## Aim for declarative programming | ||
Make your programs work with configuration files - the most common type of declarative programming. | ||
They can be easily read, merged, diffed and shared. | ||
Common settings of different projects can be easily extracted and maintained in one place. | ||
I created [read-conf](https://github.com/paulpflug/read-conf) as a powerful configuration reader with watching and plugin functionality. | ||
## Separation by functionality | ||
Separation by functionality greatly improves extendability and understandability - thus maintainability. | ||
You probably experienced the need for a major refactoring or even a complete rewrite at least once. And you will remember the large impact this had on your project - This happens when functionality isn't separated properly. | ||
I'm using two design pattern for different types of programms: | ||
- user interface: mixins (used in [cerijs](https://github.com/cerijs/ceri)) | ||
Each functionality is encapsulated in one mixin. | ||
A user interface component and each mixin can depend on other mixins. | ||
You need one main merging algorithm to resolve the dependency tree and merge all functionality into your ui-component. | ||
- processing programs: plugins and actions (used in [leajs](https://github.com/leajs/leajs) or [snapy](https://github.com/snapy/snapy)) | ||
Think of an action as an 2d array of callbacks where a state can progress through. | ||
Each callback only interacts with the current (action and/or program) state. | ||
``` | ||
# cb2 and cb3 will be called simultaneously but only after cb1 is finished | ||
# there is empty space where plugins could hook in more cbs | ||
{actionState, programState} -> [cb1, , , , [cb2, cb3], , ,] -> {actionState} | ||
``` | ||
A plugin can hook in in those actions on any position. | ||
This package is an action builder: | ||
### Install | ||
@@ -13,24 +61,45 @@ ```sh | ||
hookUp = require("hook-up") | ||
program = { | ||
config: {} | ||
} | ||
// hookUp(obj:Object, options:Object) | ||
someObj = {} | ||
hookUp(someObj) | ||
// adds a function: | ||
// registerHooks(names:String | Array of Strings) | ||
someObj.registerHooks("start") | ||
// adds functions | ||
// to add a hook | ||
// [name].register({prio: (optional) Number, cb: Function}) | ||
// to call all hooks | ||
// [name].execute(obj) | ||
// to delete all hooks | ||
// [name].clear() | ||
someArg = {} | ||
someHook = async (arg) => | ||
// this == someObj | ||
// arg == someArg | ||
// do something async | ||
someObj.start.register(someHook) | ||
someObj.start.register({prio: 2, cb: someHook}) // bigger prio gets called first | ||
// hooks with the same prio get called simultaneously | ||
await someObj.start.execute(someArg) | ||
hookUp(program,{ | ||
actions: { | ||
{"": "run"}, | ||
{"cache": ["get", "set"]} | ||
}, | ||
catch: { | ||
"cache.get": (e) => { console.error(e) } | ||
} | ||
}) | ||
// hookIn([position:Number], cb:Function) | ||
// position defaults to program.position.during | ||
// (see below) | ||
program.run.hookIn((state,{config}) => { | ||
// config equals program.config | ||
// it is recommend to test the state if you depend on it | ||
// as you have no idea what the previous cbs did | ||
if (// is in correct state) { | ||
// doSomething with state | ||
} | ||
// you don't need to return anything, each cb will be | ||
// called with the current action and program state | ||
// if you return a promise or the cb is async | ||
// the next cb will only get called afterwards | ||
}) | ||
result = await program.run({}) | ||
// remove all cbs | ||
program.run.reset() | ||
// program.position | ||
// with a default spread of 8 | ||
// contains the predefined positions: | ||
// {init: 0,before: 8, during: 16, after: 24, end: 32} | ||
program.run.hookIn(program.position.init, (state) => { | ||
// init state somehow | ||
}) | ||
``` | ||
@@ -40,25 +109,31 @@ #### Options | ||
---:| --- | ---| --- | ||
prefix | Array or String | - | to add a namespace for all hooks | ||
register | String | `register` | name of the register prop, can be empty | ||
execute | String | `execute` | name of the execute prop, can be empty | ||
actions | String or Array or Object | - | name of the actions - maximum 1 depth | ||
catch | Object | - | lookup object to apply default catch functions to actions | ||
spread | Number | 8 | distance between the predefined positions | ||
position | Object | - | lookup object to use as predefined positions | ||
Promise | Object | native Promise | Promise lib to use | ||
names | Object | - | see below | ||
Note, that only one of register or execute can be empty | ||
you can change the default names: | ||
```js | ||
// available: "hookIn", "reset", "position", "call" | ||
hookUp(program = {},{ | ||
actions: "run", | ||
names:{ | ||
hookIn: "", // only one of hookIn or call can be empty | ||
reset: "clear", | ||
position: "pos", | ||
call: "call" | ||
} | ||
}) | ||
### Example for custom API | ||
```js | ||
hookUp = require("hook-up") | ||
someObj = {} | ||
hookUp(someObj,{prefix:["before","after"], register: "call", execute:""}) | ||
someObj.registerHooks("start") | ||
// will result in the following api for hooks: | ||
someObj.before.start.call(someHook) | ||
someObj.after.start.call(someOtherHook) | ||
// to execute | ||
await someObj.before.start(someArg) | ||
program.run(program.pos.init,(state)=>{ | ||
// do something | ||
}) | ||
result = await program.run.call({}) | ||
program.run.clear() | ||
``` | ||
## License | ||
Copyright (c) 2017 Paul Pflugradt | ||
Licensed under the MIT license. |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
10298
167
138
5
1