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

interactor-organizer

v1.0.15

Published

Interactor pattern implementation

Downloads

22

Readme

interactor-organizer

CircleCI Coverage Status npm

Interactor pattern implementation, inspired by Ruby gem interactor.


Getting started

npm i interactor-organizer
import { Interactor } from 'interactor-organizer';

class DoSomething extends Interactor {
  async after() {
    console.log('after');
  }

  async before() {
    console.log('before');
  }

  // Your business logic goes here
  async perform() {
    console.log('perform', this.context);

    try {
      this.context.bar = 'baz';
    } catch (error) {
      this.fail({ error });
    }
  }
}

async function main() {
  // Perform the interactor
  const interactor = await DoSomething.perform({ foo: 'bar' });

  console.log(interactor.failure, interactor.success, interactor.context);
}

main();

// output
/**
before
perform { foo: 'bar' }
after
false true { foo: 'bar', bar: 'baz' }
*/

Interactors

Every interactor has after, before, fail, perform and rollback methods, they are very similar to the Ruby gem methods, the only "new" method is perform (which is used here instead of call).

There are two classes of interactors:

  • Interactor
  • SafeInteractor

The only difference between them is that SafeInteractor will never reject, instead, it calls fail({ error }), while Interactor will reject unless you catch and handle errors yourself.

constructor

constructor(context?: any)

Anything you want to pass to the interactor or return from it should be stored in context. Expected an object, default {}.

after

after(): Promise<any>

Is called after perform only if the interactor didn't fail.

before

before(): Promise<any>

Is always called before perform.

fail

fail(context?: any): void

If something went wrong use this method. It sets the interactor's property failure to true (which is also used by Organizers).

context is appended to the current context. Expected an object.

perform

perform(): Promise<any>

Your business logic goes here. Under the hood, this method is modified so that it calls the after and before hooks.

rollback

rollback(): Promise<any>

This method is only used by Organizers if the interactor failed, to undo changes made by perform.

static perform

static perform(context?: any): Promise<Interactor>

A shortcut to the instance method.

context

context: any

Current context. An object.

failure

failure: boolean

Indicates if the interactor failed.

success

success: boolean

The opposite of failure.

Organizers

Organizers sequentially perform interactors, if any interactor in the chain fails all the previous interactors will rollback (from the last resolved to the first). If any rollback rejects the organizer will reject as well (any further interactors won't rollback)!

Usage

Interactors example:

import { Interactor } from "interactor-organizer";

class PlaceOrder extends Interactor {
  get order() {
    return this.context.order;
  }

  get user() {
    return this.context.user;
  }

  async perform() {
    this.order.user = { _id: this.user._id };

    return client.db().collection('orders').insertOne(this.order)
      .then((result) => {
        this.order._id = result.insertedId;
      })
      // We could inherit PlaceOrder from SafeInteractor to let it catch errors for us
      .catch((error) => {
        this.fail({ error });
      });
  }

  async rollback() {
    // Delete the order if ChargeCard fails
    return client.db().collection('orders').deleteOne({ _id: this.order._id })
  }
}

class ChargeCard extends Interactor {
  async perform() {
    // API call to the payment system
  }
}

There are helper functions to create an Interactor class runtime:

import { createInteractor } from "interactor-organizer";

// Do not use arrow/anonymous functions if you want to access `this`
const FirstInteractor = createInteractor(function perform() { console.log('first'); });
const SecondInteractor = createInteractor(function perform() { console.log('second'); });

Organizers example:

// The easiest way is to use the `organize` function
import { organize } from "interactor-organizer";

organize({}, [FirstInteractor, SecondInteractor]).then(console.log);
// A more elegant way is to create an Organizer
import { Organizer } from "interactor-organizer";

class CreateOrder extends Organizer {
  static organize() {
    return [PlaceOrder, ChargeCard];
  }
}
// orders.controller.ts

function createOrder(req, res, next) {
  CreateOrder.perform({ order: ...req.body, user: req.user })
    .then((result) => {
      if (result.failure) {
        throw result.context.error;
      }

      res.status(201).json({ _id: result.context.order._id });
    })
    .catch(next);
}

Checking for failure every time may not always can be convenient, instead, you can throw errors from the organizer:

class StrictOrganizer extends Organizer {
  static async perform(context: any = {}) {
    return super.perform(context)
      .then((result) => {
        if (result.failure) {
          throw result.context.error || new Error(`${this.name} failed`);
        }
        return result;
      });
  }
}

// Inherit your organizers from StrictOrganizer