From 837088b0bbec5a44a34e2f979249fc76d17188c1 Mon Sep 17 00:00:00 2001 From: "harmvdwerf@gmail.com" Date: Thu, 21 Feb 2019 14:42:19 +0100 Subject: [PATCH 01/36] Added "unnamed hook" feature --- index.d.ts | 57 +++++++++++++++++++--- index.js | 31 +++++++++--- test/integration/index.js | 1 + test/integration/unnamed-hook-test.js | 70 +++++++++++++++++++++++++++ 4 files changed, 146 insertions(+), 13 deletions(-) create mode 100644 test/integration/unnamed-hook-test.js diff --git a/index.d.ts b/index.d.ts index 0f408b1..36d9f57 100644 --- a/index.d.ts +++ b/index.d.ts @@ -10,28 +10,71 @@ interface HookInstance { /** * Add before hook for given name. Returns `hook` instance for chaining. */ - before (name: string, method: (options: any) => any): HookInstance + before (name: string, method: (options: any) => Promise | any): HookInstance /** * Add error hook for given name. Returns `hook` instance for chaining. */ - error (name: string, method: (options: any) => any): HookInstance + error (name: string, method: (options: any) => Promise | any): HookInstance /** * Add after hook for given name. Returns `hook` instance for chaining. */ - after (name: string, method: (options: any) => any): HookInstance + after (name: string, method: (options: any) => Promise | any): HookInstance /** * Add wrap hook for given name. Returns `hook` instance for chaining. */ - wrap (name: string, method: (options: any) => any): HookInstance + wrap (name: string, method: (options: any) => Promise | any): HookInstance /** * Removes hook for given name. Returns `hook` instance for chaining. */ - remove (name: string, beforeHookMethod: (options: any) => any): HookInstance + remove (name: string, beforeHookMethod: (options: any) => Promise | any): HookInstance + + /** + * Creates a nameless hook instance that allows passing down typings for the options + */ + unnamed (): UnnamedHook +} + +interface UnnamedHook { + /** + * Invoke before and after hooks. + */ + (method: (options: T) => Promise | T | null | void): Promise + /** + * Invoke before and after hooks. + */ + (options: T, method: (options: Y) => Promise | T | null | void): Promise + + before( + beforeFn: (options: T) => Promise | T | null | void + ): UnnamedHook; + /** + * Add error hook for given name. Returns `hook` instance for chaining. + */ + error( + errorFn: (options: T) => Promise | T | null | void + ): UnnamedHook; + /** + * Add after hook for given name. Returns `hook` instance for chaining. + */ + after( + afterFn: (options: T) => Promise | T | null | void + ): UnnamedHook; + /** + * Add wrap hook for given name. Returns `hook` instance for chaining. + */ + wrap( + wrapFn: (options: T) => Promise | T | null | void + ): UnnamedHook; + /** + * Removes hook for given name. Returns `hook` instance for chaining. + */ + remove( + beforeHookMethod: (options: T) => Promise | T | null | void + ): UnnamedHook; } interface HookType { new (): HookInstance } -declare const Hook: HookType -export = Hook +export declare const Hook: HookType diff --git a/index.js b/index.js index be349d8..c79e3f4 100644 --- a/index.js +++ b/index.js @@ -4,19 +4,38 @@ var register = require('./lib/register') var addHook = require('./lib/add') var removeHook = require('./lib/remove') +var bind = Function.bind +var bindable = bind.bind(bind) + +function bindApi (hook, state, name) { + const removeHookRef = bindable(removeHook, null).apply(null, name ? [state, null, name] : [state, null]) + hook.api = { remove: removeHookRef } + hook.remove = removeHookRef + + ;['before', 'error', 'after', 'wrap'].forEach(function (kind) { + const args = name ? [state, kind, name] : [state, kind] + hook[kind] = hook.api[kind] = bindable(addHook, null).apply(null, args) + hook.remove[kind] = hook.api.remove[kind] = bindable(removeHook, null).apply(null, args) + }) +} + +function unnamedHook (state) { + var unnamedHookIterator = 0 + var unnamedHookName = 'unnamedHook' + unnamedHookIterator++ + var unnamedHook = register.bind(null, state, unnamedHookName) + bindApi(unnamedHook, state, unnamedHookName) + return unnamedHook +} + function Hook () { var state = { registry: {} } var hook = register.bind(null, state) - hook.api = { remove: removeHook.bind(null, state, null) } - hook.remove = removeHook.bind(null, state, null) + bindApi(hook, state) - ;['before', 'error', 'after', 'wrap'].forEach(function (kind) { - hook[kind] = hook.api[kind] = addHook.bind(null, state, kind) - hook.remove[kind] = hook.api.remove[kind] = removeHook.bind(null, state, kind) - }) + hook.unnamed = unnamedHook.bind(null, state) return hook } diff --git a/test/integration/index.js b/test/integration/index.js index 94e6a76..564fdc9 100644 --- a/test/integration/index.js +++ b/test/integration/index.js @@ -3,6 +3,7 @@ require('./after-test') require('./before-test') require('./error-test') require('./hook-test') +require('./unnamed-hook-test') require('./remove-after-test') require('./remove-before-test') require('./remove-error-test') diff --git a/test/integration/unnamed-hook-test.js b/test/integration/unnamed-hook-test.js new file mode 100644 index 0000000..2689b8e --- /dev/null +++ b/test/integration/unnamed-hook-test.js @@ -0,0 +1,70 @@ +var test = require('tape') + +var Hook = require('../../') + +test('hook.unnamed(options, method)', function (group) { + group.test('multiple names', function (t) { + var hook = new Hook() + var calls = [] + + var unnamedHook = hook.unnamed() + unnamedHook.before(function () { calls.push('beforeSecond') }) + unnamedHook.before(function () { calls.push('beforeFirst') }) + unnamedHook.after(function () { calls.push('afterFirst') }) + unnamedHook.after(function () { calls.push('afterSecond') }) + + unnamedHook(function () { calls.push('method') }) + + .then(function () { + t.deepEqual(calls, [ + 'beforeFirst', + 'beforeSecond', + 'method', + 'afterFirst', + 'afterSecond' + ]) + t.end() + }) + + .catch(t.error) + }) + + group.test('order', function (t) { + var hook = new Hook() + var calls = [] + + var unnamedHook = hook.unnamed() + unnamedHook.before(function () { calls.push('before 1') }) + unnamedHook.after(function () { calls.push('after 1') }) + unnamedHook.before(function () { calls.push('before 2') }) + unnamedHook.after(function () { calls.push('after 2') }) + + unnamedHook(function () { calls.push('method') }) + + .then(function () { + t.deepEqual(calls, [ + 'before 2', + 'before 1', + 'method', + 'after 1', + 'after 2' + ]) + t.end() + }) + + .catch(t.error) + }) + + group.test('no handlers defined (#51)', function (t) { + var hook = new Hook() + var options = { foo: 'bar' } + + var unnamedHook = hook.unnamed() + unnamedHook(options, function (_options) { + t.deepLooseEqual(options, _options) + t.end() + }) + }) + + group.end() +}) From 79565f2439607fb6c65039915e6aa4261817384d Mon Sep 17 00:00:00 2001 From: "harmvdwerf@gmail.com" Date: Thu, 21 Feb 2019 15:56:47 +0100 Subject: [PATCH 02/36] Made it work better in combination with TypeScript --- index.d.ts | 22 +++++++++------------- index.js | 4 +--- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/index.d.ts b/index.d.ts index 36d9f57..7ac48ae 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,32 +1,32 @@ -interface HookInstance { +interface Hook { /** * Invoke before and after hooks. */ - (name: string | string[], method: (options: any) => any): Promise + (name: string | string[], method: (options: any) => Promise | any): Promise /** * Invoke before and after hooks. */ - (name: string | string[], options: any, method: (options: any) => any): Promise + (name: string | string[], options: any, method: (options: any) => Promise | any): Promise /** * Add before hook for given name. Returns `hook` instance for chaining. */ - before (name: string, method: (options: any) => Promise | any): HookInstance + before (name: string, method: (options: any) => Promise | any): Hook /** * Add error hook for given name. Returns `hook` instance for chaining. */ - error (name: string, method: (options: any) => Promise | any): HookInstance + error (name: string, method: (options: any) => Promise | any): Hook /** * Add after hook for given name. Returns `hook` instance for chaining. */ - after (name: string, method: (options: any) => Promise | any): HookInstance + after (name: string, method: (options: any) => Promise | any): Hook /** * Add wrap hook for given name. Returns `hook` instance for chaining. */ - wrap (name: string, method: (options: any) => Promise | any): HookInstance + wrap (name: string, method: (options: any) => Promise | any): Hook /** * Removes hook for given name. Returns `hook` instance for chaining. */ - remove (name: string, beforeHookMethod: (options: any) => Promise | any): HookInstance + remove (name: string, beforeHookMethod: (options: any) => Promise | any): Hook /** * Creates a nameless hook instance that allows passing down typings for the options @@ -73,8 +73,4 @@ interface UnnamedHook { ): UnnamedHook; } -interface HookType { - new (): HookInstance -} - -export declare const Hook: HookType +export declare const Hook: {new (): Hook} diff --git a/index.js b/index.js index c79e3f4..39aba29 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,3 @@ -module.exports = Hook - var register = require('./lib/register') var addHook = require('./lib/add') var removeHook = require('./lib/remove') @@ -27,7 +25,7 @@ function unnamedHook (state) { return unnamedHook } -function Hook () { +module.Hook = function Hook () { var state = { registry: {} } From dd3b2d7d9f6aaf40a25af5c132b884946b2931d1 Mon Sep 17 00:00:00 2001 From: "harmvdwerf@gmail.com" Date: Thu, 21 Feb 2019 16:28:29 +0100 Subject: [PATCH 03/36] Reverted some code and fixed the export in a different way --- index.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 39aba29..4aa37fe 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,7 @@ +module.exports = Hook +// expose constructor as a named property for Typescript +module.exports.Hook = Hook + var register = require('./lib/register') var addHook = require('./lib/add') var removeHook = require('./lib/remove') @@ -25,7 +29,7 @@ function unnamedHook (state) { return unnamedHook } -module.Hook = function Hook () { +function Hook () { var state = { registry: {} } From c0dfc33d9579c78e6240bfe66fad1b4b188e2727 Mon Sep 17 00:00:00 2001 From: "harmvdwerf@gmail.com" Date: Thu, 21 Feb 2019 16:34:56 +0100 Subject: [PATCH 04/36] Moved the exports to the end of the file as I keep getting an undefined --- index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 4aa37fe..07b5df9 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,3 @@ -module.exports = Hook -// expose constructor as a named property for Typescript -module.exports.Hook = Hook - var register = require('./lib/register') var addHook = require('./lib/add') var removeHook = require('./lib/remove') @@ -41,3 +37,7 @@ function Hook () { return hook } + +module.exports = Hook +// expose constructor as a named property for Typescript +module.exports.Hook = Hook From 7b261f940494f73650835938aed4914af209f952 Mon Sep 17 00:00:00 2001 From: "harmvdwerf@gmail.com" Date: Thu, 21 Feb 2019 17:01:34 +0100 Subject: [PATCH 05/36] Fixed the export and improved docs --- index.d.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/index.d.ts b/index.d.ts index 7ac48ae..80a4f1e 100644 --- a/index.d.ts +++ b/index.d.ts @@ -29,18 +29,18 @@ interface Hook { remove (name: string, beforeHookMethod: (options: any) => Promise | any): Hook /** - * Creates a nameless hook instance that allows passing down typings for the options + * Creates a nameless hook instance that allows passing down typings of the options */ unnamed (): UnnamedHook } interface UnnamedHook { /** - * Invoke before and after hooks. + * Invoke before and after hooks without options */ (method: (options: T) => Promise | T | null | void): Promise /** - * Invoke before and after hooks. + * Invoke before and after hooks with options */ (options: T, method: (options: Y) => Promise | T | null | void): Promise @@ -48,29 +48,30 @@ interface UnnamedHook { beforeFn: (options: T) => Promise | T | null | void ): UnnamedHook; /** - * Add error hook for given name. Returns `hook` instance for chaining. + * Add error hook. Returns `UnnamedHook` instance for chaining. */ error( errorFn: (options: T) => Promise | T | null | void ): UnnamedHook; /** - * Add after hook for given name. Returns `hook` instance for chaining. + * Add after hook. Returns `UnnamedHook` instance for chaining. */ after( afterFn: (options: T) => Promise | T | null | void ): UnnamedHook; /** - * Add wrap hook for given name. Returns `hook` instance for chaining. + * Add wrap hook. Returns `UnnamedHook` instance for chaining. */ wrap( wrapFn: (options: T) => Promise | T | null | void ): UnnamedHook; /** - * Removes hook for given name. Returns `hook` instance for chaining. + * Removes hook. Returns `UnnamedHook` instance for chaining. */ remove( beforeHookMethod: (options: T) => Promise | T | null | void ): UnnamedHook; } -export declare const Hook: {new (): Hook} +const Hook: {new (): Hook} +export default Hook; From 31681ac0418bb64ba361144494b14c01ba83563f Mon Sep 17 00:00:00 2001 From: "harmvdwerf@gmail.com" Date: Thu, 21 Feb 2019 17:11:32 +0100 Subject: [PATCH 06/36] Reverted export --- index.d.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/index.d.ts b/index.d.ts index 80a4f1e..e3d1acd 100644 --- a/index.d.ts +++ b/index.d.ts @@ -73,5 +73,9 @@ interface UnnamedHook { ): UnnamedHook; } -const Hook: {new (): Hook} -export default Hook; +interface HookType { + new (): HookInstance +} + +declare const Hook: HookType +export = Hook From d5c825e3ea98120fa84148ec8585f860a22eb5a8 Mon Sep 17 00:00:00 2001 From: "harmvdwerf@gmail.com" Date: Thu, 21 Feb 2019 17:26:47 +0100 Subject: [PATCH 07/36] Renamed the interface back to the original --- index.d.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/index.d.ts b/index.d.ts index e3d1acd..96faea5 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,4 +1,4 @@ -interface Hook { +interface HookInstance { /** * Invoke before and after hooks. */ @@ -10,23 +10,23 @@ interface Hook { /** * Add before hook for given name. Returns `hook` instance for chaining. */ - before (name: string, method: (options: any) => Promise | any): Hook + before (name: string, method: (options: any) => Promise | any): HookInstance /** * Add error hook for given name. Returns `hook` instance for chaining. */ - error (name: string, method: (options: any) => Promise | any): Hook + error (name: string, method: (options: any) => Promise | any): HookInstance /** * Add after hook for given name. Returns `hook` instance for chaining. */ - after (name: string, method: (options: any) => Promise | any): Hook + after (name: string, method: (options: any) => Promise | any): HookInstance /** * Add wrap hook for given name. Returns `hook` instance for chaining. */ - wrap (name: string, method: (options: any) => Promise | any): Hook + wrap (name: string, method: (options: any) => Promise | any): HookInstance /** * Removes hook for given name. Returns `hook` instance for chaining. */ - remove (name: string, beforeHookMethod: (options: any) => Promise | any): Hook + remove (name: string, beforeHookMethod: (options: any) => Promise | any): HookInstance /** * Creates a nameless hook instance that allows passing down typings of the options From f6c53d91447a5a4cff1252d216d39ca2a3acddb5 Mon Sep 17 00:00:00 2001 From: "harmvdwerf@gmail.com" Date: Fri, 22 Feb 2019 09:59:26 +0100 Subject: [PATCH 08/36] It's not really a new instance --- index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index 96faea5..bd39534 100644 --- a/index.d.ts +++ b/index.d.ts @@ -74,7 +74,7 @@ interface UnnamedHook { } interface HookType { - new (): HookInstance + (): HookInstance } declare const Hook: HookType From f505eb41161c32a01a3bb94975942259888fbfe4 Mon Sep 17 00:00:00 2001 From: "harmvdwerf@gmail.com" Date: Fri, 22 Feb 2019 11:02:43 +0100 Subject: [PATCH 09/36] Another attempt --- index.d.ts | 151 +++++++++++++++++++++++++++-------------------------- 1 file changed, 77 insertions(+), 74 deletions(-) diff --git a/index.d.ts b/index.d.ts index bd39534..3c3bc4b 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,81 +1,84 @@ -interface HookInstance { - /** - * Invoke before and after hooks. - */ - (name: string | string[], method: (options: any) => Promise | any): Promise - /** - * Invoke before and after hooks. - */ - (name: string | string[], options: any, method: (options: any) => Promise | any): Promise - /** - * Add before hook for given name. Returns `hook` instance for chaining. - */ - before (name: string, method: (options: any) => Promise | any): HookInstance - /** - * Add error hook for given name. Returns `hook` instance for chaining. - */ - error (name: string, method: (options: any) => Promise | any): HookInstance - /** - * Add after hook for given name. Returns `hook` instance for chaining. - */ - after (name: string, method: (options: any) => Promise | any): HookInstance - /** - * Add wrap hook for given name. Returns `hook` instance for chaining. - */ - wrap (name: string, method: (options: any) => Promise | any): HookInstance - /** - * Removes hook for given name. Returns `hook` instance for chaining. - */ - remove (name: string, beforeHookMethod: (options: any) => Promise | any): HookInstance +declare namespace Hook { + interface HookInstance { + /** + * Invoke before and after hooks. + */ + (name: string | string[], method: (options: any) => Promise | any): Promise + /** + * Invoke before and after hooks. + */ + (name: string | string[], options: any, method: (options: any) => Promise | any): Promise + /** + * Add before hook for given name. Returns `hook` instance for chaining. + */ + before (name: string, method: (options: any) => Promise | any): HookInstance + /** + * Add error hook for given name. Returns `hook` instance for chaining. + */ + error (name: string, method: (options: any) => Promise | any): HookInstance + /** + * Add after hook for given name. Returns `hook` instance for chaining. + */ + after (name: string, method: (options: any) => Promise | any): HookInstance + /** + * Add wrap hook for given name. Returns `hook` instance for chaining. + */ + wrap (name: string, method: (options: any) => Promise | any): HookInstance + /** + * Removes hook for given name. Returns `hook` instance for chaining. + */ + remove (name: string, beforeHookMethod: (options: any) => Promise | any): HookInstance - /** - * Creates a nameless hook instance that allows passing down typings of the options - */ - unnamed (): UnnamedHook -} + /** + * Creates a nameless hook instance that allows passing down typings of the options + */ + unnamed (): UnnamedHookInstance + } -interface UnnamedHook { - /** - * Invoke before and after hooks without options - */ - (method: (options: T) => Promise | T | null | void): Promise - /** - * Invoke before and after hooks with options - */ - (options: T, method: (options: Y) => Promise | T | null | void): Promise + interface UnnamedHookInstance { + /** + * Invoke before and after hooks without options + */ + (method: (options: T) => Promise | T | null | void): Promise + /** + * Invoke before and after hooks with options + */ + (options: T, method: (options: T) => Promise | T | null | void): Promise - before( - beforeFn: (options: T) => Promise | T | null | void - ): UnnamedHook; - /** - * Add error hook. Returns `UnnamedHook` instance for chaining. - */ - error( - errorFn: (options: T) => Promise | T | null | void - ): UnnamedHook; - /** - * Add after hook. Returns `UnnamedHook` instance for chaining. - */ - after( - afterFn: (options: T) => Promise | T | null | void - ): UnnamedHook; - /** - * Add wrap hook. Returns `UnnamedHook` instance for chaining. - */ - wrap( - wrapFn: (options: T) => Promise | T | null | void - ): UnnamedHook; - /** - * Removes hook. Returns `UnnamedHook` instance for chaining. - */ - remove( - beforeHookMethod: (options: T) => Promise | T | null | void - ): UnnamedHook; -} + before( + beforeFn: (options: T) => Promise | T | null | void + ): UnnamedHookInstance; + /** + * Add error hook. Returns `UnnamedHook` instance for chaining. + */ + error( + errorFn: (options: T) => Promise | T | null | void + ): UnnamedHookInstance; + /** + * Add after hook. Returns `UnnamedHook` instance for chaining. + */ + after( + afterFn: (options: T) => Promise | T | null | void + ): UnnamedHookInstance; + /** + * Add wrap hook. Returns `UnnamedHook` instance for chaining. + */ + wrap( + wrapFn: (options: T) => Promise | T | null | void + ): UnnamedHookInstance; + /** + * Removes hook. Returns `UnnamedHook` instance for chaining. + */ + remove( + beforeHookMethod: (options: T) => Promise | T | null | void + ): UnnamedHookInstance; + } + + interface HookType { + new (): Hook.HookInstance + } -interface HookType { - (): HookInstance + declare const Hook: HookType } -declare const Hook: HookType export = Hook From 8717c1e5bee60f877b79a48d07816b55f5b67ed7 Mon Sep 17 00:00:00 2001 From: "harmvdwerf@gmail.com" Date: Fri, 22 Feb 2019 11:12:45 +0100 Subject: [PATCH 10/36] Default? --- index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index 3c3bc4b..2bf6253 100644 --- a/index.d.ts +++ b/index.d.ts @@ -81,4 +81,4 @@ declare namespace Hook { declare const Hook: HookType } -export = Hook +export default Hook From 640fe2140173f4e575e631a0ffa71036ba542010 Mon Sep 17 00:00:00 2001 From: "harmvdwerf@gmail.com" Date: Fri, 22 Feb 2019 11:17:47 +0100 Subject: [PATCH 11/36] Reverted the namespace --- index.d.ts | 154 ++++++++++++++++++++++++++--------------------------- 1 file changed, 76 insertions(+), 78 deletions(-) diff --git a/index.d.ts b/index.d.ts index 2bf6253..eecc3b3 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,84 +1,82 @@ -declare namespace Hook { - interface HookInstance { - /** - * Invoke before and after hooks. - */ - (name: string | string[], method: (options: any) => Promise | any): Promise - /** - * Invoke before and after hooks. - */ - (name: string | string[], options: any, method: (options: any) => Promise | any): Promise - /** - * Add before hook for given name. Returns `hook` instance for chaining. - */ - before (name: string, method: (options: any) => Promise | any): HookInstance - /** - * Add error hook for given name. Returns `hook` instance for chaining. - */ - error (name: string, method: (options: any) => Promise | any): HookInstance - /** - * Add after hook for given name. Returns `hook` instance for chaining. - */ - after (name: string, method: (options: any) => Promise | any): HookInstance - /** - * Add wrap hook for given name. Returns `hook` instance for chaining. - */ - wrap (name: string, method: (options: any) => Promise | any): HookInstance - /** - * Removes hook for given name. Returns `hook` instance for chaining. - */ - remove (name: string, beforeHookMethod: (options: any) => Promise | any): HookInstance +interface HookInstance { + /** + * Invoke before and after hooks. + */ + (name: string | string[], method: (options: any) => Promise | any): Promise + /** + * Invoke before and after hooks. + */ + (name: string | string[], options: any, method: (options: any) => Promise | any): Promise + /** + * Add before hook for given name. Returns `hook` instance for chaining. + */ + before (name: string, method: (options: any) => Promise | any): HookInstance + /** + * Add error hook for given name. Returns `hook` instance for chaining. + */ + error (name: string, method: (options: any) => Promise | any): HookInstance + /** + * Add after hook for given name. Returns `hook` instance for chaining. + */ + after (name: string, method: (options: any) => Promise | any): HookInstance + /** + * Add wrap hook for given name. Returns `hook` instance for chaining. + */ + wrap (name: string, method: (options: any) => Promise | any): HookInstance + /** + * Removes hook for given name. Returns `hook` instance for chaining. + */ + remove (name: string, beforeHookMethod: (options: any) => Promise | any): HookInstance - /** - * Creates a nameless hook instance that allows passing down typings of the options - */ - unnamed (): UnnamedHookInstance - } - - interface UnnamedHookInstance { - /** - * Invoke before and after hooks without options - */ - (method: (options: T) => Promise | T | null | void): Promise - /** - * Invoke before and after hooks with options - */ - (options: T, method: (options: T) => Promise | T | null | void): Promise + /** + * Creates a nameless hook instance that allows passing down typings of the options + */ + unnamed (): UnnamedHookInstance +} - before( - beforeFn: (options: T) => Promise | T | null | void - ): UnnamedHookInstance; - /** - * Add error hook. Returns `UnnamedHook` instance for chaining. - */ - error( - errorFn: (options: T) => Promise | T | null | void - ): UnnamedHookInstance; - /** - * Add after hook. Returns `UnnamedHook` instance for chaining. - */ - after( - afterFn: (options: T) => Promise | T | null | void - ): UnnamedHookInstance; - /** - * Add wrap hook. Returns `UnnamedHook` instance for chaining. - */ - wrap( - wrapFn: (options: T) => Promise | T | null | void - ): UnnamedHookInstance; - /** - * Removes hook. Returns `UnnamedHook` instance for chaining. - */ - remove( - beforeHookMethod: (options: T) => Promise | T | null | void - ): UnnamedHookInstance; - } +interface UnnamedHookInstance { + /** + * Invoke before and after hooks without options + */ + (method: (options: T) => Promise | T | null | void): Promise + /** + * Invoke before and after hooks with options + */ + (options: T, method: (options: T) => Promise | T | null | void): Promise - interface HookType { - new (): Hook.HookInstance - } + before( + beforeFn: (options: T) => Promise | T | null | void + ): UnnamedHookInstance; + /** + * Add error hook. Returns `UnnamedHook` instance for chaining. + */ + error( + errorFn: (options: T) => Promise | T | null | void + ): UnnamedHookInstance; + /** + * Add after hook. Returns `UnnamedHook` instance for chaining. + */ + after( + afterFn: (options: T) => Promise | T | null | void + ): UnnamedHookInstance; + /** + * Add wrap hook. Returns `UnnamedHook` instance for chaining. + */ + wrap( + wrapFn: (options: T) => Promise | T | null | void + ): UnnamedHookInstance; + /** + * Removes hook. Returns `UnnamedHook` instance for chaining. + */ + remove( + beforeHookMethod: (options: T) => Promise | T | null | void + ): UnnamedHookInstance; +} - declare const Hook: HookType +interface HookType { + new (): HookInstance } -export default Hook +declare const Hook: HookType + +export = Hook From 6ef591aeddf6e2736e7359ad9a095cca18e70f7c Mon Sep 17 00:00:00 2001 From: "harmvdwerf@gmail.com" Date: Fri, 22 Feb 2019 11:42:57 +0100 Subject: [PATCH 12/36] Another try --- index.d.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/index.d.ts b/index.d.ts index eecc3b3..9fdb0cd 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,4 +1,4 @@ -interface HookInstance { +interface Hook { /** * Invoke before and after hooks. */ @@ -73,10 +73,6 @@ interface UnnamedHookInstance { ): UnnamedHookInstance; } -interface HookType { - new (): HookInstance -} - -declare const Hook: HookType +declare const Hook: {new (): Hook} export = Hook From 4b6fa3a81911acdab05eafc48c60cdcfa3e96cd2 Mon Sep 17 00:00:00 2001 From: "harmvdwerf@gmail.com" Date: Fri, 22 Feb 2019 11:59:36 +0100 Subject: [PATCH 13/36] Turning it into a module --- index.d.ts | 152 ++++++++++++++++++++++++++++------------------------- 1 file changed, 80 insertions(+), 72 deletions(-) diff --git a/index.d.ts b/index.d.ts index 9fdb0cd..ec30225 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,78 +1,86 @@ -interface Hook { - /** - * Invoke before and after hooks. - */ - (name: string | string[], method: (options: any) => Promise | any): Promise - /** - * Invoke before and after hooks. - */ - (name: string | string[], options: any, method: (options: any) => Promise | any): Promise - /** - * Add before hook for given name. Returns `hook` instance for chaining. - */ - before (name: string, method: (options: any) => Promise | any): HookInstance - /** - * Add error hook for given name. Returns `hook` instance for chaining. - */ - error (name: string, method: (options: any) => Promise | any): HookInstance - /** - * Add after hook for given name. Returns `hook` instance for chaining. - */ - after (name: string, method: (options: any) => Promise | any): HookInstance - /** - * Add wrap hook for given name. Returns `hook` instance for chaining. - */ - wrap (name: string, method: (options: any) => Promise | any): HookInstance - /** - * Removes hook for given name. Returns `hook` instance for chaining. - */ - remove (name: string, beforeHookMethod: (options: any) => Promise | any): HookInstance +declare namespace __Hook { + interface Hook { + /** + * Invoke before and after hooks. + */ + (name: string | string[], method: (options: any) => Promise | any): Promise + /** + * Invoke before and after hooks. + */ + (name: string | string[], options: any, method: (options: any) => Promise | any): Promise + /** + * Add before hook for given name. Returns `hook` instance for chaining. + */ + before (name: string, method: (options: any) => Promise | any): HookInstance + /** + * Add error hook for given name. Returns `hook` instance for chaining. + */ + error (name: string, method: (options: any) => Promise | any): HookInstance + /** + * Add after hook for given name. Returns `hook` instance for chaining. + */ + after (name: string, method: (options: any) => Promise | any): HookInstance + /** + * Add wrap hook for given name. Returns `hook` instance for chaining. + */ + wrap (name: string, method: (options: any) => Promise | any): HookInstance + /** + * Removes hook for given name. Returns `hook` instance for chaining. + */ + remove (name: string, beforeHookMethod: (options: any) => Promise | any): HookInstance - /** - * Creates a nameless hook instance that allows passing down typings of the options - */ - unnamed (): UnnamedHookInstance -} + /** + * Creates a nameless hook instance that allows passing down typings of the options + */ + unnamed (): UnnamedHook + } + + interface UnnamedHook { + /** + * Invoke before and after hooks without options + */ + (method: (options: T) => Promise | T | null | void): Promise + /** + * Invoke before and after hooks with options + */ + (options: T, method: (options: T) => Promise | T | null | void): Promise -interface UnnamedHookInstance { - /** - * Invoke before and after hooks without options - */ - (method: (options: T) => Promise | T | null | void): Promise - /** - * Invoke before and after hooks with options - */ - (options: T, method: (options: T) => Promise | T | null | void): Promise + before( + beforeFn: (options: T) => Promise | T | null | void + ): UnnamedHook; + /** + * Add error hook. Returns `UnnamedHook` instance for chaining. + */ + error( + errorFn: (options: T) => Promise | T | null | void + ): UnnamedHook; + /** + * Add after hook. Returns `UnnamedHook` instance for chaining. + */ + after( + afterFn: (options: T) => Promise | T | null | void + ): UnnamedHook; + /** + * Add wrap hook. Returns `UnnamedHook` instance for chaining. + */ + wrap( + wrapFn: (options: T) => Promise | T | null | void + ): UnnamedHook; + /** + * Removes hook. Returns `UnnamedHook` instance for chaining. + */ + remove( + beforeHookMethod: (options: T) => Promise | T | null | void + ): UnnamedHook; + } - before( - beforeFn: (options: T) => Promise | T | null | void - ): UnnamedHookInstance; - /** - * Add error hook. Returns `UnnamedHook` instance for chaining. - */ - error( - errorFn: (options: T) => Promise | T | null | void - ): UnnamedHookInstance; - /** - * Add after hook. Returns `UnnamedHook` instance for chaining. - */ - after( - afterFn: (options: T) => Promise | T | null | void - ): UnnamedHookInstance; - /** - * Add wrap hook. Returns `UnnamedHook` instance for chaining. - */ - wrap( - wrapFn: (options: T) => Promise | T | null | void - ): UnnamedHookInstance; - /** - * Removes hook. Returns `UnnamedHook` instance for chaining. - */ - remove( - beforeHookMethod: (options: T) => Promise | T | null | void - ): UnnamedHookInstance; + interface Static { + new(): Hook; + } } -declare const Hook: {new (): Hook} +declare var Hook: __Hook.Static; -export = Hook +declare module 'hook' { + export = Hook; +} From 7f6d2e22fb079b4a3e65efac3e2727d10114e768 Mon Sep 17 00:00:00 2001 From: "harmvdwerf@gmail.com" Date: Fri, 22 Feb 2019 12:01:58 +0100 Subject: [PATCH 14/36] Wrong name --- index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index ec30225..add7f7b 100644 --- a/index.d.ts +++ b/index.d.ts @@ -81,6 +81,6 @@ declare namespace __Hook { declare var Hook: __Hook.Static; -declare module 'hook' { +declare module 'before-after-hook' { export = Hook; } From 008ecaf978044b4b5f7002561842e4cb58e3ccf9 Mon Sep 17 00:00:00 2001 From: "harmvdwerf@gmail.com" Date: Fri, 22 Feb 2019 12:23:18 +0100 Subject: [PATCH 15/36] No module... --- index.d.ts | 152 +++++++++++++++++++++++++---------------------------- 1 file changed, 72 insertions(+), 80 deletions(-) diff --git a/index.d.ts b/index.d.ts index add7f7b..9fdb0cd 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,86 +1,78 @@ -declare namespace __Hook { - interface Hook { - /** - * Invoke before and after hooks. - */ - (name: string | string[], method: (options: any) => Promise | any): Promise - /** - * Invoke before and after hooks. - */ - (name: string | string[], options: any, method: (options: any) => Promise | any): Promise - /** - * Add before hook for given name. Returns `hook` instance for chaining. - */ - before (name: string, method: (options: any) => Promise | any): HookInstance - /** - * Add error hook for given name. Returns `hook` instance for chaining. - */ - error (name: string, method: (options: any) => Promise | any): HookInstance - /** - * Add after hook for given name. Returns `hook` instance for chaining. - */ - after (name: string, method: (options: any) => Promise | any): HookInstance - /** - * Add wrap hook for given name. Returns `hook` instance for chaining. - */ - wrap (name: string, method: (options: any) => Promise | any): HookInstance - /** - * Removes hook for given name. Returns `hook` instance for chaining. - */ - remove (name: string, beforeHookMethod: (options: any) => Promise | any): HookInstance +interface Hook { + /** + * Invoke before and after hooks. + */ + (name: string | string[], method: (options: any) => Promise | any): Promise + /** + * Invoke before and after hooks. + */ + (name: string | string[], options: any, method: (options: any) => Promise | any): Promise + /** + * Add before hook for given name. Returns `hook` instance for chaining. + */ + before (name: string, method: (options: any) => Promise | any): HookInstance + /** + * Add error hook for given name. Returns `hook` instance for chaining. + */ + error (name: string, method: (options: any) => Promise | any): HookInstance + /** + * Add after hook for given name. Returns `hook` instance for chaining. + */ + after (name: string, method: (options: any) => Promise | any): HookInstance + /** + * Add wrap hook for given name. Returns `hook` instance for chaining. + */ + wrap (name: string, method: (options: any) => Promise | any): HookInstance + /** + * Removes hook for given name. Returns `hook` instance for chaining. + */ + remove (name: string, beforeHookMethod: (options: any) => Promise | any): HookInstance - /** - * Creates a nameless hook instance that allows passing down typings of the options - */ - unnamed (): UnnamedHook - } - - interface UnnamedHook { - /** - * Invoke before and after hooks without options - */ - (method: (options: T) => Promise | T | null | void): Promise - /** - * Invoke before and after hooks with options - */ - (options: T, method: (options: T) => Promise | T | null | void): Promise + /** + * Creates a nameless hook instance that allows passing down typings of the options + */ + unnamed (): UnnamedHookInstance +} - before( - beforeFn: (options: T) => Promise | T | null | void - ): UnnamedHook; - /** - * Add error hook. Returns `UnnamedHook` instance for chaining. - */ - error( - errorFn: (options: T) => Promise | T | null | void - ): UnnamedHook; - /** - * Add after hook. Returns `UnnamedHook` instance for chaining. - */ - after( - afterFn: (options: T) => Promise | T | null | void - ): UnnamedHook; - /** - * Add wrap hook. Returns `UnnamedHook` instance for chaining. - */ - wrap( - wrapFn: (options: T) => Promise | T | null | void - ): UnnamedHook; - /** - * Removes hook. Returns `UnnamedHook` instance for chaining. - */ - remove( - beforeHookMethod: (options: T) => Promise | T | null | void - ): UnnamedHook; - } +interface UnnamedHookInstance { + /** + * Invoke before and after hooks without options + */ + (method: (options: T) => Promise | T | null | void): Promise + /** + * Invoke before and after hooks with options + */ + (options: T, method: (options: T) => Promise | T | null | void): Promise - interface Static { - new(): Hook; - } + before( + beforeFn: (options: T) => Promise | T | null | void + ): UnnamedHookInstance; + /** + * Add error hook. Returns `UnnamedHook` instance for chaining. + */ + error( + errorFn: (options: T) => Promise | T | null | void + ): UnnamedHookInstance; + /** + * Add after hook. Returns `UnnamedHook` instance for chaining. + */ + after( + afterFn: (options: T) => Promise | T | null | void + ): UnnamedHookInstance; + /** + * Add wrap hook. Returns `UnnamedHook` instance for chaining. + */ + wrap( + wrapFn: (options: T) => Promise | T | null | void + ): UnnamedHookInstance; + /** + * Removes hook. Returns `UnnamedHook` instance for chaining. + */ + remove( + beforeHookMethod: (options: T) => Promise | T | null | void + ): UnnamedHookInstance; } -declare var Hook: __Hook.Static; +declare const Hook: {new (): Hook} -declare module 'before-after-hook' { - export = Hook; -} +export = Hook From 05ab684b26e7f2fd456bf04fa629f2d31e0a44f2 Mon Sep 17 00:00:00 2001 From: "harmvdwerf@gmail.com" Date: Mon, 25 Feb 2019 11:02:21 +0100 Subject: [PATCH 16/36] Improved the types due to overloading --- index.d.ts | 59 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 7 deletions(-) diff --git a/index.d.ts b/index.d.ts index 9fdb0cd..e9f6b59 100644 --- a/index.d.ts +++ b/index.d.ts @@ -38,38 +38,83 @@ interface UnnamedHookInstance { /** * Invoke before and after hooks without options */ - (method: (options: T) => Promise | T | null | void): Promise + (method: (options: T) => T | null | void): Promise + /** + * Invoke before and after hooks without options + */ + (method: (options: T) => Promise): Promise /** * Invoke before and after hooks with options */ - (options: T, method: (options: T) => Promise | T | null | void): Promise + (options: T, method: (options: T) => T | null | void): Promise + /** + * Invoke before and after hooks with options + */ + (options: T, method: (options: T) => Promise): Promise + /** + * Add before hook. Returns `UnnamedHook` instance for chaining. + */ + before( + beforeFn: (options: T) => T | null | void + ): UnnamedHookInstance; + /** + * Add before hook. Returns `UnnamedHook` instance for chaining. + */ before( - beforeFn: (options: T) => Promise | T | null | void + beforeFn: (options: T) => Promise + ): UnnamedHookInstance; + + /** + * Add error hook. Returns `UnnamedHook` instance for chaining. + */ + error( + errorFn: (options: T) => T | null | void ): UnnamedHookInstance; /** * Add error hook. Returns `UnnamedHook` instance for chaining. */ error( - errorFn: (options: T) => Promise | T | null | void + errorFn: (options: T) => Promise ): UnnamedHookInstance; + /** * Add after hook. Returns `UnnamedHook` instance for chaining. */ after( - afterFn: (options: T) => Promise | T | null | void + afterFn: (options: T) => T | null | void + ): UnnamedHookInstance; + /** + * Add after hook. Returns `UnnamedHook` instance for chaining. + */ + after( + afterFn: (options: T) => Promise + ): UnnamedHookInstance; + + /** + * Add wrap hook. Returns `UnnamedHook` instance for chaining. + */ + wrap( + wrapFn: (options: T) => T | null | void ): UnnamedHookInstance; /** * Add wrap hook. Returns `UnnamedHook` instance for chaining. */ wrap( - wrapFn: (options: T) => Promise | T | null | void + wrapFn: (options: T) => Promise + ): UnnamedHookInstance; + + /** + * Removes hook. Returns `UnnamedHook` instance for chaining. + */ + remove( + beforeHookMethod: (options: T) => T | null | void ): UnnamedHookInstance; /** * Removes hook. Returns `UnnamedHook` instance for chaining. */ remove( - beforeHookMethod: (options: T) => Promise | T | null | void + beforeHookMethod: (options: T) => Promise ): UnnamedHookInstance; } From b544c5f86691dd8e656b8a7fe03765c506c22864 Mon Sep 17 00:00:00 2001 From: "harmvdwerf@gmail.com" Date: Tue, 26 Feb 2019 13:20:46 +0100 Subject: [PATCH 17/36] The iteration variable wasn't in the correct place --- index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 07b5df9..08884ea 100644 --- a/index.js +++ b/index.js @@ -17,9 +17,8 @@ function bindApi (hook, state, name) { }) } -function unnamedHook (state) { - var unnamedHookIterator = 0 - var unnamedHookName = 'unnamedHook' + unnamedHookIterator++ +function unnamedHook (state, hookIteration) { + var unnamedHookName = 'unnamedHook' + hookIteration var unnamedHook = register.bind(null, state, unnamedHookName) bindApi(unnamedHook, state, unnamedHookName) return unnamedHook @@ -33,7 +32,8 @@ function Hook () { var hook = register.bind(null, state) bindApi(hook, state) - hook.unnamed = unnamedHook.bind(null, state) + var unnamedHookIterator = 0; + hook.unnamed = unnamedHook.bind(null, state, unnamedHookIterator++) return hook } From e6ccc5752fa455132eeb4b5e6c3a9a36eb3404b9 Mon Sep 17 00:00:00 2001 From: "harmvdwerf@gmail.com" Date: Tue, 26 Feb 2019 13:53:42 +0100 Subject: [PATCH 18/36] Fixed the iteration and added a unit test --- index.js | 6 +++--- test/integration/unnamed-hook-test.js | 31 ++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 08884ea..5d515ff 100644 --- a/index.js +++ b/index.js @@ -18,7 +18,7 @@ function bindApi (hook, state, name) { } function unnamedHook (state, hookIteration) { - var unnamedHookName = 'unnamedHook' + hookIteration + var unnamedHookName = 'unnamedHook' + hookIteration() var unnamedHook = register.bind(null, state, unnamedHookName) bindApi(unnamedHook, state, unnamedHookName) return unnamedHook @@ -32,8 +32,8 @@ function Hook () { var hook = register.bind(null, state) bindApi(hook, state) - var unnamedHookIterator = 0; - hook.unnamed = unnamedHook.bind(null, state, unnamedHookIterator++) + var unnamedHookIterator = 0 + hook.unnamed = unnamedHook.bind(null, state, () => unnamedHookIterator++) return hook } diff --git a/test/integration/unnamed-hook-test.js b/test/integration/unnamed-hook-test.js index 2689b8e..2ac0402 100644 --- a/test/integration/unnamed-hook-test.js +++ b/test/integration/unnamed-hook-test.js @@ -2,7 +2,7 @@ var test = require('tape') var Hook = require('../../') -test('hook.unnamed(options, method)', function (group) { +test.only('hook.unnamed(options, method)', function (group) { group.test('multiple names', function (t) { var hook = new Hook() var calls = [] @@ -55,6 +55,35 @@ test('hook.unnamed(options, method)', function (group) { .catch(t.error) }) + group.test('multiple unnamed hooks', function (t) { + var hook = new Hook() + var calls = [] + + var unnamedHook1 = hook.unnamed() + unnamedHook1.before(function () { calls.push('before 1') }) + unnamedHook1.after(function () { calls.push('after 1') }) + var unnamedHook2 = hook.unnamed() + unnamedHook2.before(function () { calls.push('before 2') }) + unnamedHook2.after(function () { calls.push('after 2') }) + + unnamedHook1(function () { calls.push('method 1') }) + .then(function () { + unnamedHook2(function () { calls.push('method 2') }) + .then(function () { + t.deepEqual(calls, [ + 'before 1', + 'method 1', + 'after 1', + 'before 2', + 'method 2', + 'after 2' + ]) + t.end() + }) + .catch(t.error) + }) + }) + group.test('no handlers defined (#51)', function (t) { var hook = new Hook() var options = { foo: 'bar' } From c84d08009fb4c2ee130783cfc976207a1182e90b Mon Sep 17 00:00:00 2001 From: "harmvdwerf@gmail.com" Date: Tue, 26 Feb 2019 14:07:31 +0100 Subject: [PATCH 19/36] Removed an "only" in the unit tests --- test/integration/unnamed-hook-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/unnamed-hook-test.js b/test/integration/unnamed-hook-test.js index 2ac0402..d60ac8e 100644 --- a/test/integration/unnamed-hook-test.js +++ b/test/integration/unnamed-hook-test.js @@ -2,7 +2,7 @@ var test = require('tape') var Hook = require('../../') -test.only('hook.unnamed(options, method)', function (group) { +test('hook.unnamed(options, method)', function (group) { group.test('multiple names', function (t) { var hook = new Hook() var calls = [] From f9d05effd1843189ff254d71541ec49e6de8b7e9 Mon Sep 17 00:00:00 2001 From: "harmvdwerf@gmail.com" Date: Wed, 27 Feb 2019 15:26:45 +0100 Subject: [PATCH 20/36] Updated the API to fit the suggestions from @gr2m --- index.js | 33 ++++++--- test/integration/index.js | 2 +- test/integration/singular-hook-test.js | 95 ++++++++++++++++++++++++ test/integration/unnamed-hook-test.js | 99 -------------------------- 4 files changed, 121 insertions(+), 108 deletions(-) create mode 100644 test/integration/singular-hook-test.js delete mode 100644 test/integration/unnamed-hook-test.js diff --git a/index.js b/index.js index 5d515ff..cdb89a5 100644 --- a/index.js +++ b/index.js @@ -17,14 +17,29 @@ function bindApi (hook, state, name) { }) } -function unnamedHook (state, hookIteration) { - var unnamedHookName = 'unnamedHook' + hookIteration() - var unnamedHook = register.bind(null, state, unnamedHookName) - bindApi(unnamedHook, state, unnamedHookName) +var singularHookState = { + registry: {} +} +var singularHookIterator = 0 +var singularHookDeprecationMessageDisplayed = false +function HookSingular () { + if (!singularHookDeprecationMessageDisplayed) { + console.warn('[before-after-hook]: "Hook.Singular()" deprecation warning. Will be renamed to "Hook()" in the next major release.') + singularHookDeprecationMessageDisplayed = true + } + var unnamedHookName = 'unnamedHook' + singularHookIterator++ + var unnamedHook = register.bind(null, singularHookState, unnamedHookName) + bindApi(unnamedHook, singularHookState, unnamedHookName) return unnamedHook } -function Hook () { +var collectionHookDeprecationMessageDisplayed = false +function HookCollection (displayDeprecationWarning) { + if (!collectionHookDeprecationMessageDisplayed && displayDeprecationWarning) { + console.warn('[before-after-hook]: "Hook()" deprecation/repurpose warning. In the next major release "Hook()" will become a singleton. To continue using hook collections, use "Hook.Collection()".') + collectionHookDeprecationMessageDisplayed = true + } + var state = { registry: {} } @@ -32,12 +47,14 @@ function Hook () { var hook = register.bind(null, state) bindApi(hook, state) - var unnamedHookIterator = 0 - hook.unnamed = unnamedHook.bind(null, state, () => unnamedHookIterator++) - return hook } +HookCollection.Singular = HookSingular.bind(null) +HookCollection.Collection = HookCollection.bind(null, false) + +var Hook = HookCollection // temporary, can be removed when Hook becomes singular + module.exports = Hook // expose constructor as a named property for Typescript module.exports.Hook = Hook diff --git a/test/integration/index.js b/test/integration/index.js index 564fdc9..8e9cf6f 100644 --- a/test/integration/index.js +++ b/test/integration/index.js @@ -3,7 +3,7 @@ require('./after-test') require('./before-test') require('./error-test') require('./hook-test') -require('./unnamed-hook-test') +require('./singular-hook-test') require('./remove-after-test') require('./remove-before-test') require('./remove-error-test') diff --git a/test/integration/singular-hook-test.js b/test/integration/singular-hook-test.js new file mode 100644 index 0000000..f2bbbaf --- /dev/null +++ b/test/integration/singular-hook-test.js @@ -0,0 +1,95 @@ +var test = require('tape') + +var Hook = require('../../') + +test('hook.Singular(options, method)', function (group) { + group.test('multiple names', function (t) { + var hook = new Hook.Singular() + var calls = [] + + hook.before(function () { calls.push('beforeSecond') }) + hook.before(function () { calls.push('beforeFirst') }) + hook.after(function () { calls.push('afterFirst') }) + hook.after(function () { calls.push('afterSecond') }) + + hook(function () { calls.push('method') }) + + .then(function () { + t.deepEqual(calls, [ + 'beforeFirst', + 'beforeSecond', + 'method', + 'afterFirst', + 'afterSecond' + ]) + t.end() + }) + + .catch(t.error) + }) + + group.test('order', function (t) { + var hook = new Hook.Singular() + var calls = [] + + hook.before(function () { calls.push('before 1') }) + hook.after(function () { calls.push('after 1') }) + hook.before(function () { calls.push('before 2') }) + hook.after(function () { calls.push('after 2') }) + + hook(function () { calls.push('method') }) + + .then(function () { + t.deepEqual(calls, [ + 'before 2', + 'before 1', + 'method', + 'after 1', + 'after 2' + ]) + t.end() + }) + + .catch(t.error) + }) + + group.test('multiple unnamed hooks', function (t) { + var calls = [] + + var hook1 = new Hook.Singular() + hook1.before(function () { calls.push('before 1') }) + hook1.after(function () { calls.push('after 1') }) + var hook2 = new Hook.Singular() + hook2.before(function () { calls.push('before 2') }) + hook2.after(function () { calls.push('after 2') }) + + hook1(function () { calls.push('method 1') }) + .then(function () { + hook2(function () { calls.push('method 2') }) + .then(function () { + t.deepEqual(calls, [ + 'before 1', + 'method 1', + 'after 1', + 'before 2', + 'method 2', + 'after 2' + ]) + t.end() + }) + .catch(t.error) + }) + }) + + group.test('no handlers defined (#51)', function (t) { + var hook = new Hook.Singular() + var options = { foo: 'bar' } + + hook(options, function (_options) { + t.deepLooseEqual(options, _options) + t.end() + }) + }) + + group.end() +}) diff --git a/test/integration/unnamed-hook-test.js b/test/integration/unnamed-hook-test.js deleted file mode 100644 index d60ac8e..0000000 --- a/test/integration/unnamed-hook-test.js +++ /dev/null @@ -1,99 +0,0 @@ -var test = require('tape') - -var Hook = require('../../') - -test('hook.unnamed(options, method)', function (group) { - group.test('multiple names', function (t) { - var hook = new Hook() - var calls = [] - - var unnamedHook = hook.unnamed() - unnamedHook.before(function () { calls.push('beforeSecond') }) - unnamedHook.before(function () { calls.push('beforeFirst') }) - unnamedHook.after(function () { calls.push('afterFirst') }) - unnamedHook.after(function () { calls.push('afterSecond') }) - - unnamedHook(function () { calls.push('method') }) - - .then(function () { - t.deepEqual(calls, [ - 'beforeFirst', - 'beforeSecond', - 'method', - 'afterFirst', - 'afterSecond' - ]) - t.end() - }) - - .catch(t.error) - }) - - group.test('order', function (t) { - var hook = new Hook() - var calls = [] - - var unnamedHook = hook.unnamed() - unnamedHook.before(function () { calls.push('before 1') }) - unnamedHook.after(function () { calls.push('after 1') }) - unnamedHook.before(function () { calls.push('before 2') }) - unnamedHook.after(function () { calls.push('after 2') }) - - unnamedHook(function () { calls.push('method') }) - - .then(function () { - t.deepEqual(calls, [ - 'before 2', - 'before 1', - 'method', - 'after 1', - 'after 2' - ]) - t.end() - }) - - .catch(t.error) - }) - - group.test('multiple unnamed hooks', function (t) { - var hook = new Hook() - var calls = [] - - var unnamedHook1 = hook.unnamed() - unnamedHook1.before(function () { calls.push('before 1') }) - unnamedHook1.after(function () { calls.push('after 1') }) - var unnamedHook2 = hook.unnamed() - unnamedHook2.before(function () { calls.push('before 2') }) - unnamedHook2.after(function () { calls.push('after 2') }) - - unnamedHook1(function () { calls.push('method 1') }) - .then(function () { - unnamedHook2(function () { calls.push('method 2') }) - .then(function () { - t.deepEqual(calls, [ - 'before 1', - 'method 1', - 'after 1', - 'before 2', - 'method 2', - 'after 2' - ]) - t.end() - }) - .catch(t.error) - }) - }) - - group.test('no handlers defined (#51)', function (t) { - var hook = new Hook() - var options = { foo: 'bar' } - - var unnamedHook = hook.unnamed() - unnamedHook(options, function (_options) { - t.deepLooseEqual(options, _options) - t.end() - }) - }) - - group.end() -}) From 7b852cf2242d85a081f31b9310ecc0850107845b Mon Sep 17 00:00:00 2001 From: "harmvdwerf@gmail.com" Date: Wed, 27 Feb 2019 15:38:17 +0100 Subject: [PATCH 21/36] The deprecation warning didn't show for the deprecated method.. --- index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index cdb89a5..f212710 100644 --- a/index.js +++ b/index.js @@ -34,8 +34,8 @@ function HookSingular () { } var collectionHookDeprecationMessageDisplayed = false -function HookCollection (displayDeprecationWarning) { - if (!collectionHookDeprecationMessageDisplayed && displayDeprecationWarning) { +function HookCollection (ignoreDeprecationWarning) { + if (!collectionHookDeprecationMessageDisplayed && !ignoreDeprecationWarning) { console.warn('[before-after-hook]: "Hook()" deprecation/repurpose warning. In the next major release "Hook()" will become a singleton. To continue using hook collections, use "Hook.Collection()".') collectionHookDeprecationMessageDisplayed = true } @@ -51,7 +51,7 @@ function HookCollection (displayDeprecationWarning) { } HookCollection.Singular = HookSingular.bind(null) -HookCollection.Collection = HookCollection.bind(null, false) +HookCollection.Collection = HookCollection.bind(null, true) var Hook = HookCollection // temporary, can be removed when Hook becomes singular From 90a8aed37b4c90c92068df475160c25f5dd717b8 Mon Sep 17 00:00:00 2001 From: "harmvdwerf@gmail.com" Date: Wed, 27 Feb 2019 17:55:46 +0100 Subject: [PATCH 22/36] Got rid of the "ignoreDeprecationWarning" --- index.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/index.js b/index.js index f212710..6c053c0 100644 --- a/index.js +++ b/index.js @@ -33,13 +33,7 @@ function HookSingular () { return unnamedHook } -var collectionHookDeprecationMessageDisplayed = false -function HookCollection (ignoreDeprecationWarning) { - if (!collectionHookDeprecationMessageDisplayed && !ignoreDeprecationWarning) { - console.warn('[before-after-hook]: "Hook()" deprecation/repurpose warning. In the next major release "Hook()" will become a singleton. To continue using hook collections, use "Hook.Collection()".') - collectionHookDeprecationMessageDisplayed = true - } - +function HookCollection () { var state = { registry: {} } @@ -50,10 +44,17 @@ function HookCollection (ignoreDeprecationWarning) { return hook } -HookCollection.Singular = HookSingular.bind(null) -HookCollection.Collection = HookCollection.bind(null, true) +var collectionHookDeprecationMessageDisplayed = false +function Hook () { + if (!collectionHookDeprecationMessageDisplayed) { + console.warn('[before-after-hook]: "Hook()" deprecation/repurpose warning. In the next major release "Hook()" will become a singleton. To continue using hook collections, use "Hook.Collection()".') + collectionHookDeprecationMessageDisplayed = true + } + return HookCollection() +} -var Hook = HookCollection // temporary, can be removed when Hook becomes singular +Hook.Singular = HookSingular.bind(null) +Hook.Collection = HookCollection.bind(null) module.exports = Hook // expose constructor as a named property for Typescript From 60314bafd18f25a62352689b13aa6ac77b564e89 Mon Sep 17 00:00:00 2001 From: "harmvdwerf@gmail.com" Date: Thu, 28 Feb 2019 09:18:43 +0100 Subject: [PATCH 23/36] Added a comment to explain why the bind hacks are there --- index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/index.js b/index.js index 6c053c0..4f138cd 100644 --- a/index.js +++ b/index.js @@ -2,6 +2,7 @@ var register = require('./lib/register') var addHook = require('./lib/add') var removeHook = require('./lib/remove') +// https://stackoverflow.com/a/21792913 var bind = Function.bind var bindable = bind.bind(bind) From e38d5902866c59d122bad2586ccb6aea45c79058 Mon Sep 17 00:00:00 2001 From: "harmvdwerf@gmail.com" Date: Thu, 28 Feb 2019 09:34:26 +0100 Subject: [PATCH 24/36] Moved the state inside the SingularHook constructor --- index.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/index.js b/index.js index 4f138cd..096eccf 100644 --- a/index.js +++ b/index.js @@ -18,20 +18,19 @@ function bindApi (hook, state, name) { }) } -var singularHookState = { - registry: {} -} -var singularHookIterator = 0 var singularHookDeprecationMessageDisplayed = false function HookSingular () { if (!singularHookDeprecationMessageDisplayed) { console.warn('[before-after-hook]: "Hook.Singular()" deprecation warning. Will be renamed to "Hook()" in the next major release.') singularHookDeprecationMessageDisplayed = true } - var unnamedHookName = 'unnamedHook' + singularHookIterator++ - var unnamedHook = register.bind(null, singularHookState, unnamedHookName) - bindApi(unnamedHook, singularHookState, unnamedHookName) - return unnamedHook + var singularHookName = 'hook' + var singularHookState = { + registry: {} + } + var singularHook = register.bind(null, singularHookState, singularHookName) + bindApi(singularHook, singularHookState, singularHookName) + return singularHook } function HookCollection () { From 56aeb68facb41558c60c64a5e3f7f5fb095f2c4b Mon Sep 17 00:00:00 2001 From: "harmvdwerf@gmail.com" Date: Thu, 28 Feb 2019 09:38:15 +0100 Subject: [PATCH 25/36] Removed an unnecessary deprecation warning --- index.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/index.js b/index.js index 096eccf..75ca2f0 100644 --- a/index.js +++ b/index.js @@ -18,13 +18,8 @@ function bindApi (hook, state, name) { }) } -var singularHookDeprecationMessageDisplayed = false function HookSingular () { - if (!singularHookDeprecationMessageDisplayed) { - console.warn('[before-after-hook]: "Hook.Singular()" deprecation warning. Will be renamed to "Hook()" in the next major release.') - singularHookDeprecationMessageDisplayed = true - } - var singularHookName = 'hook' + var singularHookName = 'h' var singularHookState = { registry: {} } From ed8eaedc8fab1ed5b1b011f33230c7a27daf9ba3 Mon Sep 17 00:00:00 2001 From: "harmvdwerf@gmail.com" Date: Thu, 28 Feb 2019 11:25:37 +0100 Subject: [PATCH 26/36] The types are compatible with the new API --- index.d.ts | 53 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/index.d.ts b/index.d.ts index e9f6b59..40338eb 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,4 +1,4 @@ -interface Hook { +export interface HookCollection { /** * Invoke before and after hooks. */ @@ -10,31 +10,26 @@ interface Hook { /** * Add before hook for given name. Returns `hook` instance for chaining. */ - before (name: string, method: (options: any) => Promise | any): HookInstance + before (name: string, method: (options: any) => Promise | any): HookCollection /** * Add error hook for given name. Returns `hook` instance for chaining. */ - error (name: string, method: (options: any) => Promise | any): HookInstance + error (name: string, method: (options: any) => Promise | any): HookCollection /** * Add after hook for given name. Returns `hook` instance for chaining. */ - after (name: string, method: (options: any) => Promise | any): HookInstance + after (name: string, method: (options: any) => Promise | any): HookCollection /** * Add wrap hook for given name. Returns `hook` instance for chaining. */ - wrap (name: string, method: (options: any) => Promise | any): HookInstance + wrap (name: string, method: (options: any) => Promise | any): HookCollection /** * Removes hook for given name. Returns `hook` instance for chaining. */ - remove (name: string, beforeHookMethod: (options: any) => Promise | any): HookInstance - - /** - * Creates a nameless hook instance that allows passing down typings of the options - */ - unnamed (): UnnamedHookInstance + remove (name: string, beforeHookMethod: (options: any) => Promise | any): HookCollection } -interface UnnamedHookInstance { +export interface HookSingular { /** * Invoke before and after hooks without options */ @@ -57,67 +52,79 @@ interface UnnamedHookInstance { */ before( beforeFn: (options: T) => T | null | void - ): UnnamedHookInstance; + ): HookSingular; /** * Add before hook. Returns `UnnamedHook` instance for chaining. */ before( beforeFn: (options: T) => Promise - ): UnnamedHookInstance; + ): HookSingular; /** * Add error hook. Returns `UnnamedHook` instance for chaining. */ error( errorFn: (options: T) => T | null | void - ): UnnamedHookInstance; + ): HookSingular; /** * Add error hook. Returns `UnnamedHook` instance for chaining. */ error( errorFn: (options: T) => Promise - ): UnnamedHookInstance; + ): HookSingular; /** * Add after hook. Returns `UnnamedHook` instance for chaining. */ after( afterFn: (options: T) => T | null | void - ): UnnamedHookInstance; + ): HookSingular; /** * Add after hook. Returns `UnnamedHook` instance for chaining. */ after( afterFn: (options: T) => Promise - ): UnnamedHookInstance; + ): HookSingular; /** * Add wrap hook. Returns `UnnamedHook` instance for chaining. */ wrap( wrapFn: (options: T) => T | null | void - ): UnnamedHookInstance; + ): HookSingular; /** * Add wrap hook. Returns `UnnamedHook` instance for chaining. */ wrap( wrapFn: (options: T) => Promise - ): UnnamedHookInstance; + ): HookSingular; /** * Removes hook. Returns `UnnamedHook` instance for chaining. */ remove( beforeHookMethod: (options: T) => T | null | void - ): UnnamedHookInstance; + ): HookSingular; /** * Removes hook. Returns `UnnamedHook` instance for chaining. */ remove( beforeHookMethod: (options: T) => Promise - ): UnnamedHookInstance; + ): HookSingular; } -declare const Hook: {new (): Hook} +declare const Hook: { + new (): HookCollection + + /** + * Creates a nameless hook that allows passing down typings for the options + */ + Singular: {new (): HookSingular} + + /** + * Creates a hook collection + */ + Collection: {new (): HookCollection} +} export = Hook From 36228ae6b5a87a6caee00b09aaffa25f2904624c Mon Sep 17 00:00:00 2001 From: "harmvdwerf@gmail.com" Date: Thu, 28 Feb 2019 11:26:38 +0100 Subject: [PATCH 27/36] The readme is now compatible with the API --- README.md | 240 +++++++++++++++++++++++++++++++++++++++++------------- index.js | 2 +- 2 files changed, 186 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 349012a..ef10f1a 100644 --- a/README.md +++ b/README.md @@ -9,26 +9,54 @@ ## Usage +### Singular hook + +Recommended for [TypeScript](#typescript) ```js -// instantiate hook API -const hook = new Hook() +// instantiate singular hook API +const hook = new Hook.Singular() // Create a hook function getData (options) { - return hook('get', options, fetchFromDatabase) + return hook(options, fetchFromDatabase) .then(handleData) .catch(handleGetError) } // register before/error/after hooks. // The methods can be async or return a promise -hook.before('get', beforeHook) -hook.error('get', errorHook) -hook.after('get', afterHook) +hook.before(beforeHook) +hook.error(errorHook) +hook.after(afterHook) + +getData({id: 123}) +``` + +### Hook collection +```js +// instantiate hook collection API +const hookCollection = new Hook.Collection() + +// Create a hook +function getData (options) { + return hookCollection('get', options, fetchFromDatabase) + .then(handleData) + .catch(handleGetError) +} + +// register before/error/after hooks. +// The methods can be async or return a promise +hookCollection.before('get', beforeHook) +hookCollection.error('get', errorHook) +hookCollection.after('get', afterHook) getData({id: 123}) ``` +### Behind the scenes + +There's no fundamental difference between the `Hook.Singular` and `Hook.Collection` hooks except for the fact that a hook from a collection requires you to pass along the name. Therefore the following explanation applies to both code snippets as described above. + The methods are executed in the following order 1. `beforeHook` @@ -47,10 +75,10 @@ of `getData`. If `errorHook` throws an error then `handleGetError` is called next, otherwise `afterHook` and `getData`. -You can also use `hook.wrap` to achieve the same thing as shown above: +You can also use `hook.wrap` to achieve the same thing as shown above (collection example): ```js -hook.wrap('get', async (getData, options) => { +hookCollection.wrap('get', async (getData, options) => { await beforeHook(options) try { @@ -73,42 +101,88 @@ Or download [the latest `before-after-hook.min.js`](https://github.com/gr2m/befo ## API -- [Constructor](#constructor) -- [hook.api](#hookapi) -- [hook()](#hook) -- [hook.before()](#hookbefore) -- [hook.error()](#hookerror) -- [hook.after()](#hookafter) -- [hook.wrap()](#hookwrap) -- [hook.remove()](#hookremove) +- [Singular Hook Constructor](#singular-hook-api) +- [Hook Collection Constructor](#hook-collection-api) + +## Singular hook API + +- [Singular constructor](#singular-constructor) +- [hook.api](#singular-api) +- [hook()](#singular-api) +- [hook.before()](#singular-api) +- [hook.error()](#singular-api) +- [hook.after()](#singular-api) +- [hook.wrap()](#singular-api) +- [hook.remove()](#singular-api) + +### Singular constructor + +The `Hook.Singular` constructor has no options and returns a `hook` instance with the +methods below: + +```js +const hook = new Hook.Singular() +``` +Using the singular hook is recommended for [TypeScript](#typescript) + +### Singular API + +The singular hook is a reference to a single hook. This means that there's no need to pass along any identifier (such as a `name` as can be seen in the [Hook.Collection API](#collectionapi)). -### Constructor +The API of a singular hook is exactly the same as a collection hook and we therefore suggest you read the [Hook.Collection API](#collectionapi) and leave out any use of the `name` argument. Just skip it like described in this example: +```js +const hook = new Hook.Singular() + +// good +hook.before(beforeHook) +hook.after(afterHook) +hook(options, fetchFromDatabase) -The `Hook` constructor has no options and returns a `hook` instance with the +// bad +hook.before('get', beforeHook) +hook.after('get', afterHook) +hook('get', options, fetchFromDatabase) +} +``` + +## Hook collection API + +- [Collection constructor](#collection-constructor) +- [collection.api](#collectionapi) +- [collection()](#collection) +- [collection.before()](#collectionbefore) +- [collection.error()](#collectionerror) +- [collection.after()](#collectionafter) +- [collection.wrap()](#collectionwrap) +- [collection.remove()](#collectionremove) + +### Collection constructor + +The `Hook.Collection` constructor has no options and returns a `hookCollection` instance with the methods below ```js -const hook = new Hook() +const collection = new Hook.Collection() ``` -### hook.api +### collection.api Use the `api` property to return the public API: -- [hook.before()](#hookbefore) -- [hook.after()](#hookafter) -- [hook.error()](#hookerror) -- [hook.wrap()](#hookwrap) -- [hook.remove()](#hookremove) +- [collection.before()](#collectionbefore) +- [collection.after()](#collectionafter) +- [collection.error()](#collectionerror) +- [collection.wrap()](#collectionwrap) +- [collection.remove()](#collectionremove) -That way you don’t need to expose the [hook()](#hook) method to consumers of your library +That way you don’t need to expose the [collection()](#collection) method to consumers of your library -### hook() +### collection() Invoke before and after hooks. Returns a promise. ```js -hook(nameOrNames, [options,] method) +collection(nameOrNames, [options,] method) ``` @@ -150,12 +224,12 @@ Rejects with error that is thrown or rejected with by Simple Example ```js -hook('save', record, function (record) { +collection('save', record, function (record) { return store.save(record) }) -// shorter: hook('save', record, store.save) +// shorter: collection('save', record, store.save) -hook.before('save', function addTimestamps (record) { +collection.before('save', function addTimestamps (record) { const now = new Date().toISOString() if (record.createdAt) { record.updatedAt = now @@ -168,17 +242,17 @@ hook.before('save', function addTimestamps (record) { Example defining multiple hooks at once. ```js -hook(['add', 'save'], record, function (record) { +collection(['add', 'save'], record, function (record) { return store.save(record) }) -hook.before('add', function addTimestamps (record) { +collection.before('add', function addTimestamps (record) { if (!record.type) { throw new Error('type property is required') } }) -hook.before('save', function addTimestamps (record) { +collection.before('save', function addTimestamps (record) { if (!record.type) { throw new Error('type property is required') } @@ -188,19 +262,19 @@ hook.before('save', function addTimestamps (record) { Defining multiple hooks is helpful if you have similar methods for which you want to define separate hooks, but also an additional hook that gets called for all at once. The example above is equal to this: ```js -hook('add', record, function (record) { - return hook('save', record, function (record) { +collection('add', record, function (record) { + return collection('save', record, function (record) { return store.save(record) }) }) ``` -### hook.before() +### collection.before() -Add before hook for given name. Returns `hook` instance for chaining. +Add before hook for given name. Returns `collection` instance for chaining. ```js -hook.before(name, method) +collection.before(name, method) ```
@@ -233,19 +307,19 @@ hook.before(name, method) Example ```js -hook.before('save', function validate (record) { +collection.before('save', function validate (record) { if (!record.name) { throw new Error('name property is required') } }) ``` -### hook.error() +### collection.error() -Add error hook for given name. Returns `hook` instance for chaining. +Add error hook for given name. Returns `collection` instance for chaining. ```js -hook.error(name, method) +collection.error(name, method) ```
@@ -280,18 +354,18 @@ hook.error(name, method) Example ```js -hook.error('save', function (error, options) { +collection.error('save', function (error, options) { if (error.ignore) return throw error }) ``` -### hook.after() +### collection.after() Add after hook for given name. Returns `hook` instance for chaining. ```js -hook.after(name, method) +collection.after(name, method) ```
@@ -323,7 +397,7 @@ hook.after(name, method) Example ```js -hook.after('save', function (result, options) { +collection.after('save', function (result, options) { if (result.updatedAt) { app.emit('update', result) } else { @@ -332,12 +406,12 @@ hook.after('save', function (result, options) { }) ``` -### hook.wrap() +### collection.wrap() -Add wrap hook for given name. Returns `hook` instance for chaining. +Add wrap hook for given name. Returns `collection` instance for chaining. ```js -hook.wrap(name, method) +collection.wrap(name, method) ```
@@ -368,7 +442,7 @@ hook.wrap(name, method) Example ```js -hook.wrap('save', async function (saveInDatabase, options) { +collection.wrap('save', async function (saveInDatabase, options) { if (!record.name) { throw new Error('name property is required') } @@ -392,12 +466,12 @@ hook.wrap('save', async function (saveInDatabase, options) { See also: [Test mock example](examples/test-mock-example.md) -### hook.remove() +### collection.remove() Removes hook for given name. Returns `hook` instance for chaining. ```js -hook.remove(name, hookMethod) +collection.remove(name, hookMethod) ```
@@ -419,7 +493,7 @@ hook.remove(name, hookMethod) @@ -428,9 +502,65 @@ hook.remove(name, hookMethod) Example ```js -hook.remove('save', validateRecord) +collection.remove('save', validateRecord) ``` +## TypeScript + +This library contains type definitions for TypeScript. When you use TypeScript we highly recommend using the `Hook.Singular` constructor for your hooks as this allows you to pass along type information for the options object. For example: + +```ts + +import * as Hook from 'before-after-hook' + +interface Foo { + bar: string + num: number; +} + +const hook = new Hook.Singular(); + +hook.before(function (foo) { + + // typescript will complain about the following mutation attempts + foo.hello = 'world' + foo.bar = 123 + + // yet this is valid + foo.bar = 'other-string' + foo.num = 123 +}) + +const foo = hook({bar: 'random-string'}, function(foo) { + // handle `foo` + foo.bar = 'another-string' +}) + +// foo outputs +{ + bar: 'another-string', + num: 123 +} +``` + +An alternative import: +```ts +import {Singular} from 'before-after-hook' + +const hook = new Singular<{foo: string}>(); +``` + + +[gg](#upgrading-to-1.4) + +## Upgrading to 1.4 + +Since version 1.4 the `Hook` constructor has been deprecated in favor of becoming a singleton in the next major release. + +Version 1.4 is still 100% backwards-compatible, but if you want to continue using hook collections, we recommend using the `Hook.Collection` constructor instead before the next release. + +For even more details, check out [the PR](https://github.com/gr2m/before-after-hook/pull/52). + ## See also If `before-after-hook` is not for you, have a look at one of these alternatives: diff --git a/index.js b/index.js index 75ca2f0..9a21ef6 100644 --- a/index.js +++ b/index.js @@ -42,7 +42,7 @@ function HookCollection () { var collectionHookDeprecationMessageDisplayed = false function Hook () { if (!collectionHookDeprecationMessageDisplayed) { - console.warn('[before-after-hook]: "Hook()" deprecation/repurpose warning. In the next major release "Hook()" will become a singleton. To continue using hook collections, use "Hook.Collection()".') + console.warn('[before-after-hook]: "Hook()" repurposing warning. Read more: https://github.com/gr2m/before-after-hook#upgrading-to-1.4 ') collectionHookDeprecationMessageDisplayed = true } return HookCollection() From 149ec380fd53d92e680c3b45858f2ec6649f3759 Mon Sep 17 00:00:00 2001 From: "harmvdwerf@gmail.com" Date: Thu, 28 Feb 2019 11:35:06 +0100 Subject: [PATCH 28/36] Removed unnecessary bracket --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index ef10f1a..7e44591 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,6 @@ hook(options, fetchFromDatabase) hook.before('get', beforeHook) hook.after('get', afterHook) hook('get', options, fetchFromDatabase) -} ``` ## Hook collection API From f28a7b533ea1bd07d3af7c6601f14a6bf23021f0 Mon Sep 17 00:00:00 2001 From: "harmvdwerf@gmail.com" Date: Fri, 1 Mar 2019 10:21:10 +0100 Subject: [PATCH 29/36] Removed debug statement --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 7e44591..0bb3fa2 100644 --- a/README.md +++ b/README.md @@ -549,9 +549,6 @@ import {Singular} from 'before-after-hook' const hook = new Singular<{foo: string}>(); ``` - -[gg](#upgrading-to-1.4) - ## Upgrading to 1.4 Since version 1.4 the `Hook` constructor has been deprecated in favor of becoming a singleton in the next major release. From a0b730e5d811913f694fac970654d5500c3202cd Mon Sep 17 00:00:00 2001 From: "harmvdwerf@gmail.com" Date: Fri, 1 Mar 2019 10:21:16 +0100 Subject: [PATCH 30/36] Renamed a title --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0bb3fa2..43bda91 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ hookCollection.after('get', afterHook) getData({id: 123}) ``` -### Behind the scenes +### Hook.Singular vs Hook.Collection There's no fundamental difference between the `Hook.Singular` and `Hook.Collection` hooks except for the fact that a hook from a collection requires you to pass along the name. Therefore the following explanation applies to both code snippets as described above. From 2b631ec471777a662a3e26a32a22cb4a71addc68 Mon Sep 17 00:00:00 2001 From: "harmvdwerf@gmail.com" Date: Fri, 1 Mar 2019 10:21:34 +0100 Subject: [PATCH 31/36] Comment made more descriptive --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 9a21ef6..d7046a5 100644 --- a/index.js +++ b/index.js @@ -2,7 +2,7 @@ var register = require('./lib/register') var addHook = require('./lib/add') var removeHook = require('./lib/remove') -// https://stackoverflow.com/a/21792913 +// bind with array of arguments: https://stackoverflow.com/a/21792913 var bind = Function.bind var bindable = bind.bind(bind) From a584b6441e6ce35b42f6493f6d7f28efb65a0ab2 Mon Sep 17 00:00:00 2001 From: "harmvdwerf@gmail.com" Date: Fri, 1 Mar 2019 10:21:45 +0100 Subject: [PATCH 32/36] The null was not required --- index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index d7046a5..1c8dc0e 100644 --- a/index.js +++ b/index.js @@ -48,8 +48,8 @@ function Hook () { return HookCollection() } -Hook.Singular = HookSingular.bind(null) -Hook.Collection = HookCollection.bind(null) +Hook.Singular = HookSingular.bind() +Hook.Collection = HookCollection.bind() module.exports = Hook // expose constructor as a named property for Typescript From dc40ac7e772bdb11ecf2f6f92a7678ddad2eb85f Mon Sep 17 00:00:00 2001 From: "harmvdwerf@gmail.com" Date: Fri, 1 Mar 2019 10:28:39 +0100 Subject: [PATCH 33/36] Renamed "collection" to "hookCollection" for improved descriptiveness --- README.md | 80 +++++++++++++++++++++++++++---------------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 43bda91..9f53c0f 100644 --- a/README.md +++ b/README.md @@ -127,9 +127,9 @@ Using the singular hook is recommended for [TypeScript](#typescript) ### Singular API -The singular hook is a reference to a single hook. This means that there's no need to pass along any identifier (such as a `name` as can be seen in the [Hook.Collection API](#collectionapi)). +The singular hook is a reference to a single hook. This means that there's no need to pass along any identifier (such as a `name` as can be seen in the [Hook.Collection API](#hookcollectionapi)). -The API of a singular hook is exactly the same as a collection hook and we therefore suggest you read the [Hook.Collection API](#collectionapi) and leave out any use of the `name` argument. Just skip it like described in this example: +The API of a singular hook is exactly the same as a collection hook and we therefore suggest you read the [Hook.Collection API](#hookcollectionapi) and leave out any use of the `name` argument. Just skip it like described in this example: ```js const hook = new Hook.Singular() @@ -161,27 +161,27 @@ The `Hook.Collection` constructor has no options and returns a `hookCollection` methods below ```js -const collection = new Hook.Collection() +const hookCollection = new Hook.Collection() ``` -### collection.api +### hookCollection.api Use the `api` property to return the public API: -- [collection.before()](#collectionbefore) -- [collection.after()](#collectionafter) -- [collection.error()](#collectionerror) -- [collection.wrap()](#collectionwrap) -- [collection.remove()](#collectionremove) +- [hookCollection.before()](#hookcollectionbefore) +- [hookCollection.after()](#hookcollectionafter) +- [hookCollection.error()](#hookcollectionerror) +- [hookCollection.wrap()](#hookcollectionwrap) +- [hookCollection.remove()](#hookcollectionremove) -That way you don’t need to expose the [collection()](#collection) method to consumers of your library +That way you don’t need to expose the [hookCollection()](#hookcollection) method to consumers of your library -### collection() +### hookCollection() Invoke before and after hooks. Returns a promise. ```js -collection(nameOrNames, [options,] method) +hookCollection(nameOrNames, [options,] method) ```
beforeHookMethod Function - Same function that was previously passed to hook.before(), hook.error(), hook.after() or hook.wrap() + Same function that was previously passed to collection.before(), collection.error(), collection.after() or collection.wrap() Yes
@@ -223,12 +223,12 @@ Rejects with error that is thrown or rejected with by Simple Example ```js -collection('save', record, function (record) { +hookCollection('save', record, function (record) { return store.save(record) }) -// shorter: collection('save', record, store.save) +// shorter: hookCollection('save', record, store.save) -collection.before('save', function addTimestamps (record) { +hookCollection.before('save', function addTimestamps (record) { const now = new Date().toISOString() if (record.createdAt) { record.updatedAt = now @@ -241,17 +241,17 @@ collection.before('save', function addTimestamps (record) { Example defining multiple hooks at once. ```js -collection(['add', 'save'], record, function (record) { +hookCollection(['add', 'save'], record, function (record) { return store.save(record) }) -collection.before('add', function addTimestamps (record) { +hookCollection.before('add', function addTimestamps (record) { if (!record.type) { throw new Error('type property is required') } }) -collection.before('save', function addTimestamps (record) { +hookCollection.before('save', function addTimestamps (record) { if (!record.type) { throw new Error('type property is required') } @@ -261,19 +261,19 @@ collection.before('save', function addTimestamps (record) { Defining multiple hooks is helpful if you have similar methods for which you want to define separate hooks, but also an additional hook that gets called for all at once. The example above is equal to this: ```js -collection('add', record, function (record) { - return collection('save', record, function (record) { +hookCollection('add', record, function (record) { + return hookCollection('save', record, function (record) { return store.save(record) }) }) ``` -### collection.before() +### hookCollection.before() -Add before hook for given name. Returns `collection` instance for chaining. +Add before hook for given name. Returns `hookCollection` instance for chaining. ```js -collection.before(name, method) +hookCollection.before(name, method) ```
@@ -306,19 +306,19 @@ collection.before(name, method) Example ```js -collection.before('save', function validate (record) { +hookCollection.before('save', function validate (record) { if (!record.name) { throw new Error('name property is required') } }) ``` -### collection.error() +### hookCollection.error() -Add error hook for given name. Returns `collection` instance for chaining. +Add error hook for given name. Returns `hookCollection` instance for chaining. ```js -collection.error(name, method) +hookCollection.error(name, method) ```
@@ -353,18 +353,18 @@ collection.error(name, method) Example ```js -collection.error('save', function (error, options) { +hookCollection.error('save', function (error, options) { if (error.ignore) return throw error }) ``` -### collection.after() +### hookCollection.after() Add after hook for given name. Returns `hook` instance for chaining. ```js -collection.after(name, method) +hookCollection.after(name, method) ```
@@ -396,7 +396,7 @@ collection.after(name, method) Example ```js -collection.after('save', function (result, options) { +hookCollection.after('save', function (result, options) { if (result.updatedAt) { app.emit('update', result) } else { @@ -405,12 +405,12 @@ collection.after('save', function (result, options) { }) ``` -### collection.wrap() +### hookCollection.wrap() -Add wrap hook for given name. Returns `collection` instance for chaining. +Add wrap hook for given name. Returns `hookCollection` instance for chaining. ```js -collection.wrap(name, method) +hookCollection.wrap(name, method) ```
@@ -441,7 +441,7 @@ collection.wrap(name, method) Example ```js -collection.wrap('save', async function (saveInDatabase, options) { +hookCollection.wrap('save', async function (saveInDatabase, options) { if (!record.name) { throw new Error('name property is required') } @@ -465,12 +465,12 @@ collection.wrap('save', async function (saveInDatabase, options) { See also: [Test mock example](examples/test-mock-example.md) -### collection.remove() +### hookCollection.remove() -Removes hook for given name. Returns `hook` instance for chaining. +Removes hook for given name. Returns `hookCollection` instance for chaining. ```js -collection.remove(name, hookMethod) +hookCollection.remove(name, hookMethod) ```
@@ -492,7 +492,7 @@ collection.remove(name, hookMethod) @@ -501,7 +501,7 @@ collection.remove(name, hookMethod) Example ```js -collection.remove('save', validateRecord) +hookCollection.remove('save', validateRecord) ``` ## TypeScript From 40dc3a487dc75a7d7aeae37e1c4940ef29f6b696 Mon Sep 17 00:00:00 2001 From: "harmvdwerf@gmail.com" Date: Fri, 1 Mar 2019 11:10:17 +0100 Subject: [PATCH 34/36] Improved TypeScript usage --- README.md | 5 +- index.d.ts | 243 +++++++++++++++++++++++++++-------------------------- index.js | 4 +- 3 files changed, 129 insertions(+), 123 deletions(-) diff --git a/README.md b/README.md index 9f53c0f..f460020 100644 --- a/README.md +++ b/README.md @@ -510,7 +510,7 @@ This library contains type definitions for TypeScript. When you use TypeScript w ```ts -import * as Hook from 'before-after-hook' +import {Hook} from 'before-after-hook' interface Foo { bar: string @@ -544,9 +544,10 @@ const foo = hook({bar: 'random-string'}, function(foo) { An alternative import: ```ts -import {Singular} from 'before-after-hook' +import {Singular, Collection} from 'before-after-hook' const hook = new Singular<{foo: string}>(); +const hookCollection = new Collection(); ``` ## Upgrading to 1.4 diff --git a/index.d.ts b/index.d.ts index 40338eb..8b36b63 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,130 +1,133 @@ -export interface HookCollection { - /** - * Invoke before and after hooks. - */ - (name: string | string[], method: (options: any) => Promise | any): Promise - /** - * Invoke before and after hooks. - */ - (name: string | string[], options: any, method: (options: any) => Promise | any): Promise - /** - * Add before hook for given name. Returns `hook` instance for chaining. - */ - before (name: string, method: (options: any) => Promise | any): HookCollection - /** - * Add error hook for given name. Returns `hook` instance for chaining. - */ - error (name: string, method: (options: any) => Promise | any): HookCollection - /** - * Add after hook for given name. Returns `hook` instance for chaining. - */ - after (name: string, method: (options: any) => Promise | any): HookCollection - /** - * Add wrap hook for given name. Returns `hook` instance for chaining. - */ - wrap (name: string, method: (options: any) => Promise | any): HookCollection - /** - * Removes hook for given name. Returns `hook` instance for chaining. - */ - remove (name: string, beforeHookMethod: (options: any) => Promise | any): HookCollection -} +declare module "before-after-hook" { + export interface HookCollection { + /** + * Invoke before and after hooks. + */ + (name: string | string[], method: (options: any) => Promise | any): Promise + /** + * Invoke before and after hooks. + */ + (name: string | string[], options: any, method: (options: any) => Promise | any): Promise + /** + * Add before hook for given name. Returns `hook` instance for chaining. + */ + before (name: string, method: (options: any) => Promise | any): HookCollection + /** + * Add error hook for given name. Returns `hook` instance for chaining. + */ + error (name: string, method: (options: any) => Promise | any): HookCollection + /** + * Add after hook for given name. Returns `hook` instance for chaining. + */ + after (name: string, method: (options: any) => Promise | any): HookCollection + /** + * Add wrap hook for given name. Returns `hook` instance for chaining. + */ + wrap (name: string, method: (options: any) => Promise | any): HookCollection + /** + * Removes hook for given name. Returns `hook` instance for chaining. + */ + remove (name: string, beforeHookMethod: (options: any) => Promise | any): HookCollection + } -export interface HookSingular { - /** - * Invoke before and after hooks without options - */ - (method: (options: T) => T | null | void): Promise - /** - * Invoke before and after hooks without options - */ - (method: (options: T) => Promise): Promise - /** - * Invoke before and after hooks with options - */ - (options: T, method: (options: T) => T | null | void): Promise - /** - * Invoke before and after hooks with options - */ - (options: T, method: (options: T) => Promise): Promise + export interface HookSingular { + /** + * Invoke before and after hooks without options + */ + (method: (options: T) => T | null | void): Promise + /** + * Invoke before and after hooks without options + */ + (method: (options: T) => Promise): Promise + /** + * Invoke before and after hooks with options + */ + (options: T, method: (options: T) => T | null | void): Promise + /** + * Invoke before and after hooks with options + */ + (options: T, method: (options: T) => Promise): Promise - /** - * Add before hook. Returns `UnnamedHook` instance for chaining. - */ - before( - beforeFn: (options: T) => T | null | void - ): HookSingular; - /** - * Add before hook. Returns `UnnamedHook` instance for chaining. - */ - before( - beforeFn: (options: T) => Promise - ): HookSingular; + /** + * Add before hook. Returns `UnnamedHook` instance for chaining. + */ + before( + beforeFn: (options: T) => T | null | void + ): HookSingular; + /** + * Add before hook. Returns `UnnamedHook` instance for chaining. + */ + before( + beforeFn: (options: T) => Promise + ): HookSingular; - /** - * Add error hook. Returns `UnnamedHook` instance for chaining. - */ - error( - errorFn: (options: T) => T | null | void - ): HookSingular; - /** - * Add error hook. Returns `UnnamedHook` instance for chaining. - */ - error( - errorFn: (options: T) => Promise - ): HookSingular; + /** + * Add error hook. Returns `UnnamedHook` instance for chaining. + */ + error( + errorFn: (options: T) => T | null | void + ): HookSingular; + /** + * Add error hook. Returns `UnnamedHook` instance for chaining. + */ + error( + errorFn: (options: T) => Promise + ): HookSingular; - /** - * Add after hook. Returns `UnnamedHook` instance for chaining. - */ - after( - afterFn: (options: T) => T | null | void - ): HookSingular; - /** - * Add after hook. Returns `UnnamedHook` instance for chaining. - */ - after( - afterFn: (options: T) => Promise - ): HookSingular; + /** + * Add after hook. Returns `UnnamedHook` instance for chaining. + */ + after( + afterFn: (options: T) => T | null | void + ): HookSingular; + /** + * Add after hook. Returns `UnnamedHook` instance for chaining. + */ + after( + afterFn: (options: T) => Promise + ): HookSingular; - /** - * Add wrap hook. Returns `UnnamedHook` instance for chaining. - */ - wrap( - wrapFn: (options: T) => T | null | void - ): HookSingular; - /** - * Add wrap hook. Returns `UnnamedHook` instance for chaining. - */ - wrap( - wrapFn: (options: T) => Promise - ): HookSingular; + /** + * Add wrap hook. Returns `UnnamedHook` instance for chaining. + */ + wrap( + wrapFn: (options: T) => T | null | void + ): HookSingular; + /** + * Add wrap hook. Returns `UnnamedHook` instance for chaining. + */ + wrap( + wrapFn: (options: T) => Promise + ): HookSingular; - /** - * Removes hook. Returns `UnnamedHook` instance for chaining. - */ - remove( - beforeHookMethod: (options: T) => T | null | void - ): HookSingular; - /** - * Removes hook. Returns `UnnamedHook` instance for chaining. - */ - remove( - beforeHookMethod: (options: T) => Promise - ): HookSingular; -} + /** + * Removes hook. Returns `UnnamedHook` instance for chaining. + */ + remove( + beforeHookMethod: (options: T) => T | null | void + ): HookSingular; + /** + * Removes hook. Returns `UnnamedHook` instance for chaining. + */ + remove( + beforeHookMethod: (options: T) => Promise + ): HookSingular; + } -declare const Hook: { - new (): HookCollection + export const Hook: { + new (): HookCollection - /** - * Creates a nameless hook that allows passing down typings for the options - */ - Singular: {new (): HookSingular} + /** + * Creates a nameless hook that allows passing down typings for the options + */ + Singular: {new (): HookSingular} - /** - * Creates a hook collection - */ - Collection: {new (): HookCollection} -} + /** + * Creates a hook collection + */ + Collection: {new (): HookCollection} + } -export = Hook + export const Singular: {new (): HookSingular} + export const Collection: {new (): HookCollection} +} diff --git a/index.js b/index.js index 1c8dc0e..1384d58 100644 --- a/index.js +++ b/index.js @@ -52,5 +52,7 @@ Hook.Singular = HookSingular.bind() Hook.Collection = HookCollection.bind() module.exports = Hook -// expose constructor as a named property for Typescript +// expose constructors as a named property for TypeScript module.exports.Hook = Hook +module.exports.Singular = Hook.Singular +module.exports.Collection = Hook.Collection From ad0c1e1312fc747ffaf40ad039b243bf968e1a09 Mon Sep 17 00:00:00 2001 From: "harmvdwerf@gmail.com" Date: Fri, 1 Mar 2019 11:35:52 +0100 Subject: [PATCH 35/36] Reverted for backwards compatibility --- index.d.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/index.d.ts b/index.d.ts index 8b36b63..a822b70 100644 --- a/index.d.ts +++ b/index.d.ts @@ -130,4 +130,6 @@ declare module "before-after-hook" { export const Singular: {new (): HookSingular} export const Collection: {new (): HookCollection} + + export = Hook } From a73b730aa3a8e810ad3896868d960137acb4914c Mon Sep 17 00:00:00 2001 From: Gregor Martynus Date: Mon, 4 Mar 2019 10:13:15 +0100 Subject: [PATCH 36/36] Update README.md Co-Authored-By: harm-less --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f460020..6c952ba 100644 --- a/README.md +++ b/README.md @@ -552,7 +552,7 @@ const hookCollection = new Collection(); ## Upgrading to 1.4 -Since version 1.4 the `Hook` constructor has been deprecated in favor of becoming a singleton in the next major release. +Since version 1.4 the `Hook` constructor has been deprecated in favor of returning `Hook.Singular` in the next major release. Version 1.4 is still 100% backwards-compatible, but if you want to continue using hook collections, we recommend using the `Hook.Collection` constructor instead before the next release.
beforeHookMethod Function - Same function that was previously passed to collection.before(), collection.error(), collection.after() or collection.wrap() + Same function that was previously passed to hookCollection.before(), hookCollection.error(), hookCollection.after() or hookCollection.wrap() Yes