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

superglue

v0.1.3

Published

Sticky mediator that promotes loose-coupling and makes app-flow clearer.

Downloads

12

Readme

Superglue

License Build Dependency Status NPM version

No dependencies.

Description

Superglue is interested in the organization of your application by promoting loosely coupled components and defining the order of their execution—or, application flow. It is inspired by a few different design patterns and attempts to consolidate some of their benefits. To understand better, see the examples below :)

Installation

Install superglue:

npm install superglue

Documentation

Superglue Publishers are for triggering a sequence of tasks, while Subscribers are for registering logic to handle that task.

superglue.subscribe("task1").then(function () { /* fired 1st */ });
superglue.subscribe("task2").then(function () { /* fired 2nd */ });

superglue.publish("task1", "task2");

As usual, multiple subscribers can be listening to the same task, which are fired one at a time (due to context, explained shortly), in no particular order.

// both subscribers listening to the same task,
// so both will be invoked when task is triggered
superglue.subscribe("task").then(...);
superglue.subscribe("task").then(...);

Publishers can also fire their own logic at any time during the sequence of tasks.

superglue.publish()
  .events("task1", "task2")
  .then(function () { ... })
  .event("task3");

Superglue also allows Publishers to define the context, which is then accessible to every Subscriber via this. This means updating the context will update the context for subsequent tasks.

superglue.subscribe("build-name").then(function () {
  this.name = this.first + " " + this.last;
});

superglue.publish()
  .context({ first: "Julie", last: "Chubbs" })
  .event("build-name")
  .then(function () {
    console.log(this.name);
  });
//=> "Julie Chubbs"

Sometimes you will only want to trigger subscriber logic if the context has a certain state. You can do this using the .filter() method. If any functions passed into .filter() evaluate to false, the subscriber will be skipped (but the task will continue).

superglue.subscribe("build-name")
  .filter(function () {
    return typeof this.first === "string" && typeof this.last === "string";
  })
  .then( /* only triggered if .filter returns true */ );

Other times, invoking the subscriber logic will be mandatory but the logic may still depend on the context having a certain state. To require a particular state, use the .require() method. If any functions passed into .require() evaluate to false, a ContextError will be generated and the sequence of tasks will be stopped.

superglue.subscribe("build-name")
  .require(function () {
    return typeof this.first === "string" && typeof this.last === "string";
  })
  .then( /* if .require returns false, a ContextError is generated and this function is not invoked */ );

Superglue halts the sequence of tasks once an error occurs. Besides the ContextErrors generated by .require() methods, you can also set errors yourself by returning errors (instanceof Error) from your Subscriber or Publisher .then() methods, at which point, superglue will skip all remaining tasks.

If you would like to catch and handle errors, pass in a function with the signature function (err) into your publisher's .then() method; currently, only Publishers can catch and handle errors.

superglue.subscribe("task1").then(function () {
  return Error("here's what happened...");
});
superglue.subscribe("task2").then(function () {
  // never invoked
});

superglue.publish("task1", "task2")
  .then(function () { /* this one is skipped, since missing `err` argument */ })
  .then(function (err) {
    console.log(err.message);
  });
//=> here's what happened...

See Advanced Error Handling for greater error handling control.

Express Example

Lets look at a more useful example, building on top of Express. Notice the convention of namespacing tasks with the : character, which by doing so, allows you to use the .tasks(namespace, [task1, ...]) method.

superglue.subscribe("user:save:validate").then(function () {
  if (!this.body.first || !this.body.last) {
    return Error("First & last names are required");
  }
});
superglue.subscribe("user:save:db").then(function () {
  // add to db
});
superglue.subscribe("user:save:feedback").then(function () {
  this.feedback = this.body.name + " was created successfully!";
});

...

app.post("/user", function (req, res) {

  superglue.publish()
    .context(req)
    .tasks("user:save", ["validate", "db", "feedback"])
    .then(function (err) {
      if (err) {
        res.send(422, err.message);
      } else {
        res.send(this.feedback);
      }
    });

});

By triggering task names, it becomes clearer what your application is actually doing by firing tasks with semantical names; for instance, in the example above we can see that we are using Express' req object as the context, then validating & saving the incoming data, and then providing feedback to the user. Furthermore, the pub/sub influence of Superglue keeps your application decoupled and your code DRY. In essence, Superglue glues together your application components while providing a high-level view of how your application works.

While publishers define the flow of tasks fired, it can be useful to group tasks together on the subscriber side as well.

superglue.group("user:save").tasks("user:save", ["validate", "db", "feedback"]);

...

app.post("/user", function (req, res) {

  superglue.publish()
    .context(req)
    // this fires "user:save:validate", "user:save:db", "user:save:feedback" for you
    .event("user:save")
    .then(...);

});

Advanced Error Handling

Superglue stores error constructors in the superglue.errors object. You can add your own error constructors to the superglue.errors object directly, or you can use the convenience method .addError(constructor, [name]), which adds your error constructor to superglue.errors.

When an error occurs, you can check what task created the error using the error's .failedOn property.

// SpecialError constructor, extending Error
function SpecialError (msg) {
  Error.call(this, msg);
  ...
}
SpecialError.prototype = Object.create(Error.prototype);

// add SpecialError to superglue.errors
superglue.addError(SpecialError);

superglue.subscribe("build-name")
  .then(function () {
    var SpecialError = superglue.errors.SpecialError;
    if (!this.first || !this.last) {
      return new SpecialError("missing name");
    }
  });

superglue.publish("build-name")
  .then(function (err) {

    // .name - if SpecialError was a "defined function"
    // .failedOn - task name that created the error
    if (err.name === "SpecialError" && err.failedOn === "build-name") {
      // now we have a pretty good idea about what happened...
    }

  });

Please Note: If your constructor does not extend an Error (either as a parent, grandparent, or somewhere along your prototype chain), then it will be ignored when a task returns it.

API

This section will be fixed soon, as right now it's not very useful.

Subscriber API

superglue.subscribe(task1 [, task2, ...]) creates a fresh subscriber instance listening to whatever task names passed in.

superglue.subscribe(...).replace flushes out any susbcribers & groups with the same task names, allowing this subscriber to replace the existing subscribers/groups. .replace must be called immediately after subscribe() in order to work properly.

superglue.subscribe(...).filter(filterFn, ...) invokes the filterFn and if the returned value is truthy, the subscriber's logic is also invoked.

superglue.subscribe(...).require(reqFn, ...) invokes the reqFn and if the returned value is truthy, the subscriber's logic is invoked; if the returned value is falsy, a ContextError is generated and subsequent tasks are not triggered.

superglue.subscribe(...).then(fn, ...) invokes the fn. If errors are returned, subsequent tasks are not triggered.

.filter(), .require(), and .then() can all be given multiple functions as their arguments.

Even though it is not required (except for .replace), it is useful to order method invocation in the same order it is processed internally, which is:

superglue.subscribe("namespace:task")
  .replace  // if needed
  .filter(function () {})
  .require(function () {})
  .then(function () {})

Group-Subscriber API

superglue.group(groupName1 [, groupName2, ...]) creates a fresh subscriber-group instance listening to whatever group-names are passed in.

superglue.group(...).replace is identical to .subscribe().replace.

superglue.group(...).events(name1 [, name2, ...]) will group all event/task names passed in.

superglue.group(...).event alias for .events.

superglue.group(...).tasks(namespace, tasks) will prepend tasks with the namespace and then fire them when the group name is triggered. tasks can either be a string if it is a single task name, or an array of task names.

superglue.group(...).task alias for .tasks.

Publisher API

superglue.publish([context, taskName1, ...]) creates a fresh publisher instance. If you pass in an object, it will be set as the context. If you pass in a string, it will be fired as a task name. You can pass in as many contexts and task names as you would like. However, the order is significant—if you invoke superglue.publish("task1", {name:"Julie"}, "task2"), only task2 had the context with this.name, since the context was set after task1 was triggered.

superglue.publish().context(cxt) sets the cxt object as the context for subsequent tasks fired. The context can be fired more than once if you desire to change the context for subsequent tasks.

superglue.publish().events(task1 [, task2, ...]) fires the tasks in the order provided.

superglue.publish().event alias for .events.

superglue.publish().tasks(namespace, tasks) will prepend tasks with the namespace and then fire them. tasks can either be a string if it is a single task name, or an array of task names.

superglue.publish().task alias for .tasks.

Remember that order is important. Tasks are triggered the moment their task-name is received, which means that tasks are fired in the order they are received, and contexts are only available to tasks fired after the context is set.

Road Map

Adding test coverage stats soon. Let me know what else you'd like to see!

Feedback

I would love feedback! Let me know what parts are confusing or what could be improved. Easiest way to discuss it is to submit an issue :)

License

The MIT License

Copyright (c) 2014 David Chubbuck