Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@poppinss/hooks

Package Overview
Dependencies
Maintainers
1
Versions
40
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@poppinss/hooks - npm Package Compare versions

Comparing version 5.0.0 to 6.0.0-0

build/src/Contracts/index.d.ts

1

build/index.d.ts

@@ -0,1 +1,2 @@

export * from './src/Contracts';
export { Hooks } from './src/Hooks';

@@ -10,5 +10,16 @@ "use strict";

*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Hooks = void 0;
__exportStar(require("./src/Contracts"), exports);
var Hooks_1 = require("./src/Hooks");
Object.defineProperty(exports, "Hooks", { enumerable: true, get: function () { return Hooks_1.Hooks; } });

60

build/src/Hooks/index.d.ts

@@ -1,59 +0,51 @@

/// <reference types="@adonisjs/application/build/adonis-typings/application" />
declare type HooksHandler = (...args: any[]) => void | Promise<void>;
import { Runner } from '../Runner';
import { HooksHandler } from '../Contracts';
/**
* Exposes the API to register before/after lifecycle hooks for a given action
* with option to resolve handlers from the IoC container.
* Exposes the API for registering hooks for a given lifecycle action.
*
* The hooks class doesn't provide autocomplete for actions and the arguments
* the handler will receive, since we expect this class to be used internally
* for user facing objects.
* @example
* const hooks = new Hooks()
* hooks.add('onCreate', function () {
* doSomething
* })
*/
export declare class Hooks {
private resolver?;
private hooks;
constructor(resolver?: import("@ioc:Adonis/Core/Application").IocResolverContract<import("@ioc:Adonis/Core/Application").ContainerBindings> | undefined);
/**
* Raise exceptins when resolver is not defined
* Pre-resolved registered hooks
*/
private ensureResolver;
private hooks;
/**
* Resolves the hook handler using the resolver when it is defined as string
* or returns the function reference back
* Add handler to the actions map
*/
private resolveHandler;
private addHandler;
/**
* Returns handlers set for a given action or undefined
* Returns a map of registered hooks
*/
private getActionHandlers;
all(): Map<string, Set<HooksHandler>>;
/**
* Adds the resolved handler to the actions set
* Returns true when a handler has already been registered
*/
private addResolvedHandler;
has(action: string, handler: HooksHandler): boolean;
/**
* Returns a boolean whether a handler has been already registered or not
* Register handler for a given action
*/
has(lifecycle: 'before' | 'after', action: string, handler: HooksHandler | string): boolean;
add(action: string, handler: HooksHandler): this;
/**
* Register hook handler for a given event and lifecycle
*/
add(lifecycle: 'before' | 'after', action: string, handler: HooksHandler | string): this;
/**
* Remove a pre-registered handler
*/
remove(lifecycle: 'before' | 'after', action: string, handler: HooksHandler | string): void;
remove(action: string, handler: HooksHandler): void;
/**
* Remove all handlers for a given action or lifecycle. If action is not
* defined, then all actions for that given lifecycle are removed
* Remove all handlers for a given action. If action is not
* defined, then all actions are removed.
*/
clear(lifecycle: 'before' | 'after', action?: string): void;
clear(action?: string): void;
/**
* Merges hooks of a given hook instance. To merge from more than
* one instance, you can call the merge method for multiple times
* Merge pre-resolved hooks from an existing
* hooks instance
*/
merge(hooks: Hooks): void;
/**
* Executes the hook handler for a given action and lifecycle
* Returns an instance of hooks runner. Optionally, a few hooks can be disabled.
*/
exec(lifecycle: 'before' | 'after', action: string, ...data: any[]): Promise<void>;
runner(action: string, withoutHooks?: string[]): Runner;
}
export {};

@@ -12,70 +12,50 @@ "use strict";

exports.Hooks = void 0;
const Runner_1 = require("../Runner");
/**
* Exposes the API to register before/after lifecycle hooks for a given action
* with option to resolve handlers from the IoC container.
* Exposes the API for registering hooks for a given lifecycle action.
*
* The hooks class doesn't provide autocomplete for actions and the arguments
* the handler will receive, since we expect this class to be used internally
* for user facing objects.
* @example
* const hooks = new Hooks()
* hooks.add('onCreate', function () {
* doSomething
* })
*/
class Hooks {
constructor(resolver) {
this.resolver = resolver;
this.hooks = {
before: new Map(),
after: new Map(),
};
constructor() {
/**
* Pre-resolved registered hooks
*/
this.hooks = new Map();
}
/**
* Raise exceptins when resolver is not defined
* Add handler to the actions map
*/
ensureResolver() {
if (!this.resolver) {
throw new Error('IoC container resolver is required to register string based hooks handlers');
addHandler(action, handler) {
const handlers = this.hooks.get(action);
if (!handlers) {
this.hooks.set(action, new Set());
}
this.hooks.get(action).add(handler);
}
/**
* Resolves the hook handler using the resolver when it is defined as string
* or returns the function reference back
* Returns a map of registered hooks
*/
resolveHandler(handler) {
if (typeof handler === 'string') {
this.ensureResolver();
return this.resolver.resolve(handler);
}
return handler;
all() {
return this.hooks;
}
/**
* Returns handlers set for a given action or undefined
* Returns true when a handler has already been registered
*/
getActionHandlers(lifecycle, action) {
return this.hooks[lifecycle].get(action);
}
/**
* Adds the resolved handler to the actions set
*/
addResolvedHandler(lifecycle, action, handler) {
const handlers = this.getActionHandlers(lifecycle, action);
if (handlers) {
handlers.add(handler);
}
else {
this.hooks[lifecycle].set(action, new Set([handler]));
}
}
/**
* Returns a boolean whether a handler has been already registered or not
*/
has(lifecycle, action, handler) {
const handlers = this.getActionHandlers(lifecycle, action);
has(action, handler) {
const handlers = this.hooks.get(action);
if (!handlers) {
return false;
}
return handlers.has(this.resolveHandler(handler));
return handlers.has(handler);
}
/**
* Register hook handler for a given event and lifecycle
* Register handler for a given action
*/
add(lifecycle, action, handler) {
this.addResolvedHandler(lifecycle, action, this.resolveHandler(handler));
add(action, handler) {
this.addHandler(action, handler);
return this;

@@ -86,54 +66,38 @@ }

*/
remove(lifecycle, action, handler) {
const handlers = this.getActionHandlers(lifecycle, action);
remove(action, handler) {
const handlers = this.hooks.get(action);
if (!handlers) {
return;
}
handlers.delete(this.resolveHandler(handler));
handlers.delete(handler);
}
/**
* Remove all handlers for a given action or lifecycle. If action is not
* defined, then all actions for that given lifecycle are removed
* Remove all handlers for a given action. If action is not
* defined, then all actions are removed.
*/
clear(lifecycle, action) {
clear(action) {
if (!action) {
this.hooks[lifecycle].clear();
this.hooks.clear();
return;
}
this.hooks[lifecycle].delete(action);
this.hooks.delete(action);
}
/**
* Merges hooks of a given hook instance. To merge from more than
* one instance, you can call the merge method for multiple times
* Merge pre-resolved hooks from an existing
* hooks instance
*/
merge(hooks) {
hooks.hooks.before.forEach((actionHooks, action) => {
hooks.all().forEach((actionHooks, action) => {
actionHooks.forEach((handler) => {
this.addResolvedHandler('before', action, handler);
this.add(action, handler);
});
});
hooks.hooks.after.forEach((actionHooks, action) => {
actionHooks.forEach((handler) => {
this.addResolvedHandler('after', action, handler);
});
});
}
/**
* Executes the hook handler for a given action and lifecycle
* Returns an instance of hooks runner. Optionally, a few hooks can be disabled.
*/
async exec(lifecycle, action, ...data) {
const handlers = this.getActionHandlers(lifecycle, action);
if (!handlers) {
return;
}
for (let handler of handlers) {
if (typeof handler === 'function') {
await handler(...data);
}
else {
await this.resolver.call(handler, undefined, data);
}
}
runner(action, withoutHooks) {
return new Runner_1.Runner(this.hooks.get(action), withoutHooks);
}
}
exports.Hooks = Hooks;
{
"name": "@poppinss/hooks",
"version": "5.0.0",
"version": "6.0.0-0",
"description": "A no brainer hooks module for execute before/after lifecycle hooks",

@@ -11,2 +11,5 @@ "main": "build/index.js",

],
"exports": {
".": "./build/index.js"
},
"scripts": {

@@ -27,12 +30,3 @@ "mrm": "mrm --preset=@adonisjs/mrm-preset",

},
"peerDependencies": {
"@adonisjs/application": ">=4.0.0"
},
"peerDependenciesMeta": {
"@adonisjs/application": {
"optional": true
}
},
"devDependencies": {
"@adonisjs/application": "^5.1.8",
"@adonisjs/mrm-preset": "^5.0.2",

@@ -79,7 +73,2 @@ "@adonisjs/require-ts": "^2.0.7",

},
"husky": {
"hooks": {
"commit-msg": "node ./node_modules/@adonisjs/mrm-preset/validateCommit/conventional/validate.js"
}
},
"config": {

@@ -136,3 +125,7 @@ "commitizen": {

"printWidth": 100
},
"publishConfig": {
"access": "public",
"tag": "next"
}
}

@@ -18,8 +18,6 @@ <div align="center"><img src="https://res.cloudinary.com/adonisjs/image/upload/q_100/v1557762307/poppinss_iftxlt.jpg" width="600px"></div>

- [Usage](#usage)
- [API](#api)
- [add(lifecycle: 'before' | 'after', action: string, handler: Function | string)](#addlifecycle-before--after-action-string-handler-function--string)
- [exec(lifecycle: 'before' | 'after', action: string, ...data: any[])](#execlifecycle-before--after-action-string-data-any)
- [remove (lifecycle: 'before' | 'after', action: string, handler: HooksHandler | string)](#remove-lifecycle-before--after-action-string-handler-hookshandler--string)
- [clear(lifecycle: 'before' | 'after', action?: string)](#clearlifecycle-before--after-action-string)
- [merge (hooks: Hooks): void](#merge-hooks-hooks-void)
- [Removing `before` and `after` calls with cleanup functions](#removing-before-and-after-calls-with-cleanup-functions)
- [Scanerio with hooks](#scanerio-with-hooks)
- [Scanerio with cleanup functions](#scanerio-with-cleanup-functions)
- [Passing data to hooks](#passing-data-to-hooks)

@@ -32,6 +30,4 @@ <!-- END doctoc generated TOC please keep comment here to allow auto update -->

For example: The Lucid models uses this class internally and expose `before` and `after` methods on the model itself. Doing this, Lucid can control the autocomplete, type checking for the `before` and `after` methods itself, without relying on this package to expose the generics API.
For example: The Luciod models uses this internally and exposes the API to register lifecycle hooks around model actions like `creating`, `created`, `deleting`, `deleted` and so on.
> Also generics increases the number of types Typescript has to generate and it's better to avoid them whenever possible.
## Installation

@@ -42,6 +38,6 @@

```sh
npm i @poppinss/hooks
npm i @poppinss/hooks@next
# yarn
yarn add @poppinss/hooks
yarn add @poppinss/hooks@next
```

@@ -57,80 +53,119 @@

hooks.add('before', 'save', function () {})
hooks.add('creating', function () {})
hooks.add('created', function () {})
// Later invoke before save hooks
await hooks.exec('before', 'save', { id: 1 })
const runner = hooks.runner('creating')
try {
await runner.run() // pass data here
} finally {
await runner.cleaup() // pass data here
}
```
If you want the end user to define IoC container bindings as the hook handler, then you need to pass the `IoC` container resolver to the Hooks constructor. Following is the snippet from Lucid models.
## Removing `before` and `after` calls with cleanup functions
Usually hooks are modeled around `before` and `after` events. Also, the previous versions of this package also allowed registering before and after lifecycle hooks. However, I have recently removed support for that because of the following reasons.
The `before` and `after` lifecycle hooks will always be prone to errors. Let's consider the following scanerio.
#### Scanerio with hooks
We have the following `before` hooks.
```ts
import { Ioc } from '@adonisjs/fold'
const ioc = new Ioc()
const resolver = ioc.getResolver(undefined, 'modelHooks', 'App/Models/Hooks')
hooks.add('before', 'save', function () {
createTemporaryResource()
})
const hooks = new Hooks(resolver)
```
hooks.add('before', 'save', function () {
createAnotherTemporaryResource()
})
The resolver allows the end user to pass the hook reference as string and hooks must live inside `App/Models/Hooks` folder.
hooks.add('after', 'save', function () {
removeFirstTemporaryResource()
})
```ts
hooks.add('before', 'save', 'User.encryptPassword')
hooks.add('after', 'save', function () {
removeSecondTemporaryResource()
})
```
## API
**Now let's save the operation around which the lifecycle hooks were registered fails. Should we fire the `after` hooks?**
#### add(lifecycle: 'before' | 'after', action: string, handler: Function | string)
- If no, then temporary resources will be never be removed
- If yes, then we will end up in the stable state.
Add a new hook handler.
**However, what happens if the second before hook fails?** Now should we call all the `after` hooks or not?
```ts
hooks.add('before', 'save', (data) => {
console.log(data)
})
```
- If yes, then the `removeSecondTemporaryResource` may error out since its `before` action was never completed.
- If not, then again we will end up in a dirty state from the first `before` hook.
#### exec(lifecycle: 'before' | 'after', action: string, ...data: any[])
As I mentioned earlier, the `before` and `after` hooks have no direct relationship and hence there is no correct way to call only the `after` hooks for which the `before` hooks completed successfully.
Execute a given hook for a selected lifecycle.
Therefore, by removing the concept of `before` and `after` and using cleanup functions, we will be able to design a more robust hooks system.
### Scanerio with cleanup functions
Now, in the following API, the hooks themselves are responsible for returning the cleanup functions.
If the second hook fails, then we will only call the cleanup function for the first hook.
```ts
hooks.exec('before', 'save', { username: 'virk' })
hooks.add('save', function () {
createTemporaryResource()
return removeFirstTemporaryResource
})
hooks.add('save', function () {
createAnotherTemporaryResource()
return removeSecondTemporaryResource
})
```
#### remove (lifecycle: 'before' | 'after', action: string, handler: HooksHandler | string)
## Passing data to hooks
You can pass data to hooks at the time of running the `run` and the `cleanup` functions. For example.
Remove an earlier registered hook. If you are using the IoC container bindings, then passing the binding string is enough, otherwise you need to store the reference of the function.
```ts
function onSave() {}
import { Hooks } from '@poppinss/hooks'
const hooks = new Hooks()
hooks.add('before', 'save', onSave)
hooks.add('creating', function (arg1, arg2, arg3) {})
hooks.add('creating', function (arg1, arg2, arg3) {})
hooks.add('creating', function (arg1, arg2, arg3) {})
// Later remove it
hooks.remove('before', 'save', onSave)
const runner = hooks.runner('creating')
await runner.run('arg1', 'arg2', 'arg3')
```
#### clear(lifecycle: 'before' | 'after', action?: string)
It is usually helpful to inform the cleanup functions if there was an error or not. Maybe some cleanup functions may not to run only in case of errors.
Clear all hooks for a given lifecycle and optionally an action.
```ts
hooks.clear('before')
const hooks = new Hooks()
// Clear just for the save action
hooks.clear('before', 'save')
```
hooks.add('creating', function (model) {
const file = await saveFileToDisk()
model.filePath = file
#### merge (hooks: Hooks): void
return (error, model) => {
if (error) {
await removeFileFromDisk(model.filePath)
}
}
})
Merge hooks from an existing hooks instance. Useful during class inheritance.
const runner = hooks.runner('creating')
try {
// Run hooks
await runner.run(model)
```ts
const hooks = new Hooks()
hooks.add('before', 'save', function () {})
// Run the actual action
await model.save()
} catch (error) {
// During error
await runner.cleaup(error, model)
}
const hooks1 = new Hooks()
hooks1.merge(hooks)
await hooks1.exec('before', 'save', [])
/**
* During success
*/
if (runner.isCleanupPending) {
await runner.cleaup(null, model)
}
```

@@ -137,0 +172,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc