npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@poppinss/hooks

v7.2.4

Published

A no brainer hooks module for execute before/after lifecycle hooks

Downloads

231,971

Readme

@poppinss/hooks

A simple yet effective implementation for executing hooks around an event.

gh-workflow-image typescript-image npm-image license-image

This package is a zero-dependency implementation for running lifecycle hooks around an event. Following are some of the notable features.

  • Register and run lifecycle hooks.
  • Hooks can return cleanup functions that are executed to perform the cleanup.
  • Super lightweight

Setup

Install the package from the npm packages registry.

npm i @poppinss/hooks

# yarn lovers
yarn add @poppinss/hooks

And import the Hooks class as follows.

import Hooks from '@poppinss/hooks'

const hooks = new Hooks()

hooks.add('saving', function () {
  console.log('called')
})

// Execute hooks using runner
await hooks.runner('saving').run()

Defining hooks

The hooks are defined using the hooks.add method. The method accepts the event name and a callback function to execute.

const hooks = new Hooks()

hooks.add('saving', function () {
  console.log('called')
})

You can also define hook as an object with the name and the handle method property. This is usually helpful when you want to specify a custom name for the hook, or re-use the same handle method multiple times.

const hooks = new Hooks()

function handleSave() {}

hooks.add('saving', { name: 'beforeSave', handle: handleSave })
hooks.add('creating', { name: 'beforeCreate', handle: handleSave })

The handle method receives the first argument as the event name, followed by the rest of the arguments supplied during runtime.

Running hooks

You can execute hooks using the Hooks Runner. You can create a new runner instance by calling the hooks.runner method and passing the event name for which you want to execute hooks.

const hooks = new Hooks()

const runner = hooks.runner('saving')
await runner.run()

To run hooks in the reverse order, you can use the runner.runReverse method.

const hooks = new Hooks()

const runner = hooks.runner('saving')
await runner.runReverse()

Passing data to hooks

You can pass one or more arguments to the runner.run method, which the runner will share with the hook callbacks. For example:

const hooks = new Hooks()

hooks.add('saving', function (model, transaction) {})

const runner = hooks.runner('saving')
await runner.run(model, transaction)

Cleanup functions

Cleanup functions allow hooks to clean up after themselves after the main action finishes successfully or with an error. Let's consider a real-world example of saving a model to the database.

  • You will first run the saving hooks.
  • Assume one of the saving hooks writes some files to the disk.
  • Next, you issue the insert query to the database, and the query fails.
  • The hook that has written files to the disk would want to remove those files as the main operation got canceled with an error.

Following is how you can express that with cleanup functions.

hooks.add('saving', function () {
  await fs.writeFile()

  // Return the cleanup function
  return (error) => {
    // In case of an error, remove the file
    if (error) {
      await fs.unlink()
    }
  }
})

The code responsible for issuing the insert query should run hooks as follows.

const runner = hooks.runner('saving')

try {
  await runner.run(model)
  await model.save()
} catch (error) {
  // Perform cleanup and pass error
  await runner.cleanup(error)
  throw error
}

// Perform cleanup in case of success as well
await runner.cleanup()

Note: The runner.cleanup method is idempotent. Therefore you can call it multiple times, yet it will run the underlying cleanup methods only once.

Run without hook handlers

You can exclude certain hook handlers from executing using the without method.

In the following example, we run hooks without executing the generateDefaultAvatar hook handler. As you can notice, you can specify the function name as a string.

hooks.add('saving', function hashPassword() {})
hooks.add('saving', function generateDefaultAvatar() {})

await hooks.runner('saving').without(['generateDefaultAvatar']).run()

Event types

You can also specify the types of supported events and their arguments well in advance as follows.

The first step is to define a type for all the events.

type Events = {
  saving: [
    [BaseModel], // for hook handler
    [error: Error | null, BaseModel], // for cleanup function
  ]
  finding: [
    [QueryBuilder], // for hook handler
    [error: Error | null, QueryBuilder], // for cleanup function
  ]
}

And then pass it as a generic to the Hooks class.

const hooks = new Hooks<Events>()