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

evtx

v0.3.3

Published

Aspect oriented middleware library

Downloads

920

Readme

EvtX Build Status

EvtX has been developed originally to help crafting of services oriented socketIO servers. But it's now fully independent from socketIO.

It's just is a tiny layer to facilitate method calls in an aspect oriented way.

It's mainly inspired by express and featherJS's hooks. Compared to featherJS it doesn't just focus on CRUD methods, you can wrapped any methods you want.

It's just 100 lines of code that help to call methods surrounded by before and after hooks organized in 3 levels: evtx, service and method.

Usage

Service

A Service is defined by a plain old JavaScript object:

const calculator = {
  name: 'calculator',
  sum(input) {
    return Promise.resolve(sum(input));
  },
  product(input) {
    return Promise.resolve(product(input));
  }
};

sum and product are targets methods.

Target methods will get an input and must return an output value wrapped in a Promise. You can use them in an asynchronous context, event outside of an asynchronous context you must return a Promise.

Do not use service definition directly, but via evtx (see bellow);

Context

A context object is passed along chain hooks, created first before the first hook, passed via before hooks, used as the execution context this of the target method and passed again to after hooks.

A evtx context is made of:

  • locals: local execution context injected by Evtx#run(Object)
  • globals: global execution context injected by Evtx(Object)
  • input: data to transform
  • output: data returned by the target method.
  • service: service name
  • method: method name
  • evtx: evtx object, useful to get an other service evtx.service(name).
  • message: original message passed to run()

Context#locals and #globals can be enhanced at EvtX() call or for each EvtX#run() call.

EvtX

Main object to declare services:

  import EvtX fom 'evtx';

  const evtx = EvtX(globals)
    .use(calculator.name, calculator)
    .use('test', testService);

To get a service:

  const service = evtx.service(calculator.name);

Returned service is a wrapper around previous definition and will be executed in the execution context of an EvtX context, not in it's definition context .

To execute a method on a service:

  const message = { method: 'sum', service: calculator.name, input: [1, 2] };
  evtx
    .run(message, locals)
    .then(res => should(res).equal(3))
  • { method, service } are mandatory props to target a specific method in a service previously declared.
  • input is an optional prop forwarded to target method as input parameter.

run accepts a second parameter as a global context that will be merged with evtx context object so we can enhance context received by all hooks by foreign props.

or

  evtx.service(calculator.name).sum([1, 2]).then();

An EvtX service is an EventEmitter, so to emit a message along hooks chain or within a target method, just do:

Target method:

  const people = {
    addOne({ people }) {
      return People.add(people).then(newPeople => {
        this.emit('peopleAdded', people);
        return newPeople;
      };
    },
  };

Hook:

  const emit = ctx => {
    ctx.emit('peopleAdded', people);
    return Promise.resolve(ctx);
  }

One can also emit an event like this:

  // in a target method
  this.evtx.service(serviceName).emit( ... );
  // in a hook
  ctx.evtx.service(serviceName).emit( ... );

To subscribe to an event:

  const service = evtx.service('people');
  service.on('peopleAdded, () => );

Warning: service may change during hooks chain.

One can use configure method to setup a service:

  const initPeople = (evtx) => {
    evtx.use('people', people);
    loginfo('people service registered');
  };

  const evtx = evtX()
    .register(initPeople)
    .register( ... );

Hooks

Hooks are kind of middlewares called before and after a service's method.

  • before hooks will authorize to transform input, enhance a context, change target service, target method
  • after hooks will authorize to transform output and context

Hooks could be registered globally to an evtx object to a service or to individual methods.

A hook is a method like this:

  const incInput = (ctx) => {
    const { input } = ctx;
    return Promise.resolve({ ...ctx, input: input.map(x => x+1 ) });
  };

Input param is a context that will follow the hooks chain. This is a service level hook that can be use as a before hook for a service or a method. Like service's method a hook must return a Promise.

The chain calls looks like this:

  run(message)
    => evtx before hooks :: (ctx): Promise(ctx)
      => service before hooks :: (ctx): Promise(ctx)
        => method before hooks :: (ctx): Promise(ctx)
          => method.bind(ctx, ctx.input): Promise(output)
        => service after hooks :: (ctx): Promise(ctx)
    => evtx after hooks :: (ctx): Promise(ctx)
  => return Promise.resolve(ctx.output)

Warning: if you call directly a method from a service (evtx.service(serviceName)[methodName](ctx)) evtx level hooks will not be called neither before nor after ones.

Evtx level hooks

They are called before or after service level hooks. Before hooks can enhance the context object and change the target's service and or method:

  const changeService = (ctx) => Promise.resolve({ ...ctx, method: 'join', service: 'join' });

After hooks can update context and result:

  const incResult = (ctx) => {
    const { output } = ctx;
    return Promise.resolve({ ...ctx, output: output + 1 });
  };

Register them like this:

  evtx.before(changeService, otherHook).after(incResult, otherHook);

If before or after are called many times, only last calls set hooks.

Services and methods hooks

Service hooks are called before or after service's method hooks. Method hooks are called before or after the service's method call.

Let's define a hook to increment each value of the input array:

    const incInput = (ctx) => {
      const { input } = ctx;
      return Promise.resolve({ ...ctx, input: input.map(x => x+1 ) });
    };

We will re use incResult. So to register service and method level hooks, do:

  const bhooks = {
    all: [incInput],
    'sum': [incInput],
  };
  const ahooks = {
    all: [incResult],
    'product': [incResult]
  };
  evtx.service(calculator.name)
    .before(bhooks)
    .after(ahooks);

Service level hooks can rewrite target method, not method's onces.

Use case: craft a socketIO backend

We use redux on the front end to manage actions. Some actions should be managed by backend services hosted by a socketIO server. It that case actions are sent and received by a redux middleware Requested actions follow a { type, payload, replyTo } structure. Respond actions { type, payload } Input type is a string made of 'service:method'. Responses will be emitted with a type set to replyTo value.

formatServiceMethod before hook is used to structure a right ctx object. formatResponse after hook structure the answer sent after via socketIO.

The glue between EvtX en socketIO is made like this:

  const formatServiceMethod = (ctx) => {
    const { message: { type, payload } } = ctx;
    const [service, method] = type.split(':');
    return Promise.resolve({ ...ctx, service, method, input: payload });
  };

  const formatResponse = (ctx) => {
    const { output, message: { replyTo }} = ctx;
    if (replyTo) return Promise.resolve({ ...ctx, output: { payload: output, type: replyTo }});
    return Promise.resolve(ctx);
  };

  const init = (ctx) => {
    const { io } = ctx;
    const promise = new Promise((resolve) => {
      const evtx = evtX()
        .before(formatServiceMethod)
        .configure(initPeople)
        .after(formatResponse);

      io.on('connection', (socket) => {
        socket.on('action', (message) => {
          loginfo(`receive ${message.type} action`);
          const ctx = { ...message, io, socket };
          evtx.run(ctx)
            .then((res) => {
              socket.emit('action', res)
              loginfo(`sent ${res.type} action`);
            })
            .catch((error) => {
              console.error(error);
              socket.emit('action', { type: 'EVTX:ERROR', error })
            });
        });
      });

      loginfo(`EvtX setup.`);
      resolve({ ...ctx, evtx });
    });

    return promise;
  };

That's all folks ...