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

@devninjas/chairo

v4.0.1

Published

Seneca micro-services plugin for hapi

Downloads

5

Readme

chairo

npm version

This is a fork of geek/chairo

chairo ("happy" in ancient Greek: χαίρω) is a Seneca micro-services plugin for hapi. The plugin integrates the Seneca functionality into hapi and provide tools to map its actions to server methods and views for easy access.

Usage

Install

// Install for œhapi/hapi v20 or higher
npm i @devninjas/chairo@4 -s

// Install for œhapi/hapi v19
npm i @devninjas/chairo@3 -s

// Install for œhapi/hapi v18
npm i @devninjas/chairo@2 -s

// Install for œhapi/hapi v17
npm i @devninjas/chairo@1 -s

Plugin Registration

chairo is registered with a hapi server using the server.register() method. Once registered it decorates the server object with a reference to the seneca object initialized using the provided plugin options. Default plugin options:

  • seneca: false
  • log: silent
  • actcache:
    • active: false

You can use an existing instance to seneca and let chairo do the decorations for your server. When setting the seneca option on the configuration, please note that the other settings won't be used.

const Chairo = require('chairo');
const Hapi = require('hapi');

const server = new Hapi.Server();

(async () => {
  // Register plugin

  try {
    await server.register({ register: Chairo });

    // Add a Seneca action

    let id = 0;
    server.seneca.addAsync({ generate: 'id' }, async message => {
      return { id: ++id };
    });

    // Invoke a Seneca action

    const result = await server.seneca.actAsync({ generate: 'id' });

    // result: { id: 1 }

    // Invoke a Seneca action without expecting an answer

    server.seneca.act({ generate: 'id' });
  } catch (err) {}
})();

In addition, the hapi request object is decorated with a reference to the seneca object for easy access:

server.route({
  method: 'POST',
  path: '/id',
  handler: function(request) {
    // Invoke a Seneca action using the request decoration

    return request.seneca.actAsync({ generate: 'id' });
  }
});

server.action(name, pattern, [options])

Maps a Seneca action pattern to a hapi server method where:

  • name - the server method name (same as the name used in server.method()).
  • pattern - the Seneca action pattern (e.g. 'generate:id' or { generate: 'id' }) to map.
  • options - optional settings options where:
    • cache - method caching options (same as the name used in server.method()).
    • generateKey - method generating custom cache key (same as the name used in server.method()).
'use strict';

const Chairo = require('chairo');
const Hapi = require('hapi');

const server = new Hapi.Server();
(async () => {
  try {
    await server.register(Chairo);

    // Set up a Seneca action

    let id = 0;
    server.seneca.addAsync({ generate: 'id' }, async message => {
      return { id: ++id };
    });

    server.seneca.addAsync({ calc: 'average' }, async message => {
      return {
        average:
          (message.samples.dataset.values[0] +
            message.samples.dataset.values[0]) /
          2
      };
    });

    // Map action to a hapi server method

    server.action('generate', 'generate:id', {
      cache: { expiresIn: 1000, generateTimeout: 3000 }
    });

    // Map action to a hapi server method with custom generateKey method

    server.action('average', 'calc:average', {
      cache: { expiresIn: 1000, generateTimeout: 3000 },
      generateKey: function(message) {
        return (
          'average-' +
          message.samples.dataset.values[0] +
          ':' +
          message.samples.dataset.values[1]
        );
      }
    });

    // Start hapi server (starts cache)

    await server.start();

    // Invoke server method

    const result1 = await server.methods.generate();
    // Invoke the same server method

    const result2 = await server.methods.generate();
    // result1 === result2 (cached)

    const avg1 = await server.methods.average({
      samples: { dataset: { values: [2, 3] } }
    });

    const avg2 = await server.methods.average({
      samples: { dataset: { values: [2, 3] } }
    });
    // avg1 == avg2 (cached)
  } catch (err) {}
})();

h.act(pattern)

Sends back a handler response using the result of a Seneca action where:

  • pattern - the Seneca action called to generate the response.
const Chairo = require('chairo');
const Hapi = require('hapi');

const server = new Hapi.Server();

(async () => {
  try {
    await server.register(Chairo);
    // Set up a Seneca action

    let id = 0;
    server.seneca.addAsync({ generate: 'id' }, async message => {
      return { id: ++id };
    });

    // Add route

    server.route({
      method: 'POST',
      path: '/id',
      handler: function(request, h) {
        // Reply using a Seneca action

        return h.act({ generate: 'id' });
      }
    });
  } catch (err) {}
})();

In addition, the act handler shortcut is also provided:

server.route({
  method: 'POST',
  path: '/id',
  handler: { act: 'generate:id' }
});

reply.compose(template, context, [options])

Renders a template view using the provided template and context where:

  • template - the view engine template (same as the name used in reply.view()).
  • context - the context object used to render the template. Chairo provides a special key $resolve where you can map context variables to Seneca actions matching they key's value pattern. Each of the services mapped to keys is resolved and the resultant key value maps are copied to the context root before redering the template.
  • options - optional settings passed to reply.view().

It requires the vision plugin to be registered with Hapi.

const Chairo = require('chairo');
const Handlebars = require('handlebars');
const Vision = require('vision');
const Hapi = require('hapi');

const server = new Hapi.Server();

(async () => {
  try {
    await server.register([Chairo, Vision]);
    // set up a few Seneca actions

    server.seneca.addAsync({ lookup: 'date' }, async message => {
      return { date: new Date().toString() };
    });

    server.seneca.addAsync({ load: 'user' }, async message => {
      return { name: message.name };
    });

    // Set up a hapi view engine

    server.views({
      engines: { html: Handlebars },
      path: __dirname + '/templates'
    });

    // Add route

    server.route({
      method: 'GET',
      path: '/',
      handler: function(request, h) {
        // Setup context with both Seneca actions and simple keys

        const context = {
          $resolve: {
            today: 'lookup:date', // Using string pattern
            user: { load: 'user', name: 'john' } // Using object pattern
          },
          general: {
            message: 'hello!'
          }
        };

        // Reply with rendered view

        return h.compose('example', context);
      }
    });
  } catch (err) {}
})();

Using the template ./templates/example.html:

<div>
    <h1>{{today.date}}</h1>
    <h2>{{user.name}}</h2>
    <h3>{{general.message}}</h3>
</div>

In addition, the compose handler shortcut is also provided:

server.route({
  method: 'POST',
  path: '/id',
  handler: {
    compose: {
      template: 'example',
      context: {
        $resolve: {
          today: 'lookup:date',
          user: { load: 'user', name: 'john' }
        },
        general: {
          message: 'hello!'
        }
      }
    }
  }
});