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

composie

v1.1.0

Published

Compose middleware with router and run it like a charm. Also serves as an advanced event bus with on, off, and emit methods.

Downloads

215

Readme

Features

  • Middleware Composition: Supports chaining and complex middleware compositions.
  • Routing: Based on path routing for middleware.
  • Event Bus: Easily handle and trigger events using on, off, and emit.

Installation

Install via npm:

npm install composie

Or via yarn:

yarn add composie

Usage

Basic Usage

import Composie from 'composie'; // or const Composie = require('composie');
const composie = new Composie();
// Add global middleware
composie.use((ctx, next) => {
  console.log('Global middleware');
  return next();
});

// Add route middleware
composie.route('api/user-info', (ctx, next) => {
  ctx.response = 'User info';
  return next();
});

// Run middleware
composie.run('api/user-info').then(response => {
  console.log(response); // 'User info'
});

// remove route
composie.remove('api/user-info');

Using as an Event Bus

const Composie = require('composie');
const composie = new Composie();

// add general middleware for all events
composie.use(async (ctx, next) => {
  // simulate async operation
  await Promise(resolve => setTimeout(resolve, 1000));
  console.log(`Handling event: ${ctx.channel}`);
  return next();
});

// Listen for events
composie.on('user-registered', (ctx, next) => {
  console.log(`Handling event for user: ${ctx.request.user}`);
  // to continue to the next callback
  return next();
});
// chain another callback
composie.on('user-registered', (ctx, next) => {
  if (ctx.request.user === 'Alice') {
    ctx.response = 'Alice is registered';
  } else {
    // to continue to the next callback
    return next()
  }
});
// even chain more callbacks
composie.on('user-registered', (ctx, next) => {
  ctx.response = `User ${ctx.request.user} is not registered`;
});

// Emit events
composie.emit('user-registered', { user: 'Alice' }).then((response) => {
  console.log(response); // 'Alice is registered'
});

composie.emit('user-registered', { user: 'Bob' }).then((response) => {
  console.log(response); // 'User Bob is not registered'
});

// Remove event listener
composie.off('user-registered');

API Documentation

Before you get started, there are some basic concepts of this library.

  1. Every middleware(callback) is a function that receive two parameters ctx and next:
function (ctx, next) {
  // your logic here
}
  1. ctx is an object contains request info:
{
  // channel name, required
  channel: 'channel name',
  // data you passed by run or emit, optional
  request: 'any request data'
  // response data, you should save your result in it
  response: 'any response data'
  // you can add your own property to ctx to share information between middlewares
  ...
}
  1. You should save your result in ctx.response, you will get it in .run(or .emit) promise resolve
  2. and you can add your own property to ctx to share information between middlewares
  3. use return next() or await next()(in async function) if you want ctx be processed by the next middleware, or just return to terminate it
  4. use throw to throw an error if an error occurred, you will catch it in .run(or .emit) promise reject

Composie

Create an instance

import Composie from 'composie'
// no arguments is needed
const composie = new Composie()

This libaray is writen in class, you should create an instance with new before use it

composie.use(middleware)

Add global middleware, all invoking will proccessed by global middleware

// basic usage
composie.use(function(ctx, next) {
  // your logic here
  return next();
});

// advanced usage
composie.use(async function(ctx, next) {
  try {
    // your logic here
    await next();
  } catch (err) {
    // handle error, you can throw it again or just log it
    console.error(err);
    // set a default response, if you want
    ctx.response = '404 Not Found';
    // or just throw it again, then you can catch it in `.run`(or `.emit`) promise reject
    // throw err;
  }
})
// chain more than one middleware
.use(function(ctx, next) {
  // your logic here
  return next();
});

You can add more than more than one global middleware, they will run by the order you adding.

composie.use(channelPrefix, middleware)

Add a middleware for channel has specific prefix, when run the middleware, if the channel match the prefix, then will be processed by the that middleware.

composie.use('api', function(ctx, next) {
  // your logic here
  return next()
});

You can also add as many middlewares as you want for one prefix, they will be called in the order they added.

composie.route(channel, middleware, [middleware...])

Add middlewares for a specific channel, you can add more than one at a time.

composie.on is an alias of composie.route, use it as you like.

compose.route(
  "api/user",
  function(ctx, next) {
    // your logic here
  },
  function(ctx, next) {
    // another middleware
  }
);

composie.route({channel: [middleware...]})

Add middlewares for more than one channel

composie.on is an alias of composie.route, use it as you like.

compose.route({
  "api/user": function(ctx, next) {
    // your logic here
  },
  "api/detail": [
    function(ctx, next) {
      // your logic here
    },
    function(ctx, next) {
      // another middleware
    }
  ]
});

chain use, route together

You can chain use and route together

compose
  .use(function(ctx, next) {
    // your logic here
    // ...
    return next();
  })
  .use('api', function(ctx, nexdt) {
    // your logic here
    // ...
    return next();
  })
  .route('api/user', function(ctx, next) {
    // your logic here
  })
  // `on` is an alias of `route`
  .on('api/detail', function(ctx, next) {
    // your logic here
  });

composie.alias(existingChannel, aliasName)

add an alias for an existing channel, when you run the alias, it will be processed by the middleware of the existing channel.

composie.route('api/user', function(ctx, next) {
  ctx.response = 'User info';
});
composie.alias('api/user', 'user');

composie.run('user').then(response => {
  console.log(response); // 'User info'
});

composie.removeRoute(channel, middleware?)

Remove a middleware for a specific channel, if middleware is not provided, then all middlewares for that channel will be removed.

composie.off is an alias of composie.removeRoute, use it as you like.

composie.removeRoute('api/user');

compose.off('api/user', middleware);

When you remove a alias, the alias will be removed, but the existing channel will be kept.

composie.run(channel, request?)

run middleware for channel, it will return a promise

composie.emit and composie.call are aliases of composie.run, use it as you like.

compose.run("api/user", { id: "xxx" }).then(
  resp => {
    console.log("response ", resp);
  },
  err => {
    console.log("error", err);
  }
);

createEventBus(options)

Utilize Composie as an Event Bus for efficient event handling in your applications.

import { createEventBus } from 'composie';

const eventBus = createEventBus(...)

interface IEventBusOptions<IContext extends IBaseContext> {
  /**
   * convert a normal callback to a route handler
   */
  convertCallback2Middleware?: (fn: Function) => ((ctx: IContext, next: Function): any)
  /**
   * create context function
   */
  createContext?: (channel: string, request: any) => IContext
  /**
   * throw when no route found
   */
  throwWhenNoRoute?: boolean
}

for detail usage check the example below

Use Composite as a Event Bus

import { createEventBus } from 'composie';
const eventBus = createEventBus({
  // this is default the default converter
  convertCallback2Middleware: (fn) => {
    return async (ctx, next) => {
      try {
        const response = await fn(ctx.request);
        if (typeof response !== 'undefined') {
          ctx.response = response
        }
        return next();
      } catch (err) {
        throw err;
      }
    }
  }
});

eventBus.use(...)
eventBus.on('userChanged', (userInfo) => {
  console.log(userInfo)
})
eventBus.emit('userChanged', { name: 'xxx', email: 'xxxx'})

Advanced Example

Following example shows how to use Composie as an advanced fetch module for a real world application.


import Composie, { ComposieError } from 'composie';
const grab = new Composie({
  createContext: (channel, request) => ({
    // lower case the channel name
    channel: channel.toLowerCase(),
    request,
  },
  throwWhenNoRoute: true
));

grab.use(async (ctx, next) => {
  try {
    // append query string to the url
    if (ctx.request.qs) {
      const qs = new URLSearchParams(ctx.request.qs);
      const url = new URL(ctx.request.url);
      // append query string to the url, concat with the existing query string
      url.search = new URLSearchParams([...url.searchParams, ...qs]);
      ctx.request.url = url.toString();
    }

    await next();
  } catch (err) {
    if (err instanceof ComposieError) {
      if (err.code === ComposieError.CODES.ROUTE_NOT_FOUND) {
        console.error('Route not found');
      } else if (err.code === '') {
        console.error('Method not allowed');
      }
      console.error(err.message);
    } else {
      if (err.code === '401') {
        location.path = '/login';
      }
    }
  }
});
grab.use('post/', (ctx, next) => {
  ctx.request.method = ctx.request.method || 'POST';
  return next();
});

grab.on('post/json', (ctx, next) => {
  if (ctx.request.data) {
   ctx.request.headers = {
     ...ctx.request.headers,
     'Content-Type': 'application/json'
   };
   ctx.request.body = JSON.stringify(ctx.request.data);
  }
  ctx.response = await fetch(ctx.request).then(res => res.json());
});

grab.on('post/form', async (ctx, next) => {
  if (ctx.request.data) {
    ctx.request.headers = {
      ...ctx.request.headers,
      'Content-Type': 'application/x-www-form-urlencoded'
    };
    ctx.request.body = new URLSearchParams(ctx.request.data);
  }
  ctx.response = await fetch(ctx.request).then(res => res.json());
})


grab.use('get/', (ctx, next) => {
  ctx.request.method = ctx.request.method || 'GET';
  return next();
});


grab.on('post/json', async (ctx, next) => {
  ctx.response = await fetch(ctx.request).then(res => res.json());
  return next();
});


grab.emit('post/json', {
  url: 'https://jsonplaceholder.typicode.com/users/1'
}).then((response) => {
  console.log(response);
});

Contribution Guide

We welcome contributions! Please read the following guide to understand how to contribute to the project.

Submitting Issues

If you find a bug or have a feature request, please submit an issue detailing the problem or suggestion.

Submitting Pull Requests

  1. Fork the repository
  2. Create a new branch (git checkout -b feature-branch)
  3. Commit your changes (`git commit -am '