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

kojo

v8.2.1

Published

An event-driven microservice framework. Kōjō (工場) means 'plant' in Japanese.

Downloads

95

Readme

🏭 Kojo

An event-driven microservice framework. Kōjō (工場) means 'factory' in Japanese.

The idea of this framework emerged after a couple of years of using Seneca, which in turn is a great tool for building microservices but probably wants to be too abstract.

Kojo, on the other hand, is very straightforward: it has subscribers (or routes, or endpoints), services and methods which are just plain functions. Subscribers susbscribe to a pub/sub (or request/response, or a schedule) transport of your choice and call services while services perform various tasks via their methods.

Tests status Coverage Status Known Vulnerabilities

Installation

 npm i kojo

Usage

NOTE: Starting from v8.0.0 this package moved to native ESM modules.

Create a service with a method (services/user/create.js):

export default async function (userData) {
   
   const [ kojo, logger ] = this;  // kojo instance and the logger
   const { pg: pool } = kojo.state;  // get previously set pg connection

   logger.debug('creating', userData);  // logger will automatically add module and method name
   const query = `INSERT INTO ... RETURNING *`;
   const result = await pool.query(query);
   const newRecord = result ? result.rows[0] : null;
   
   if (newRecord)
       logger.info('created', newRecord);
   
   return newRecord;
}

Create a subscriber (subscribers/user.create.js):

export default (kojo, logger) => {

   const { user } = kojo.services;  // we defined `user` service above
   const  { nats } = kojo.state; // as with pg connection above we have nats connection too

   nats.subscribe('user.create', async (userData) => {
       
       const newUser = await user.create(userData);
       
       if (newUser) 
           nats.publish('user.created', newUser);
   });
}

Add connections, initialize kojo:

...
import Kojo from 'kojo';


async function main() {

   const kojo = new Kojo({ name: 'users' });

   const pool = new pg.Pool({
      user: 'pg_user',
      database: 'db_name',
      password: 'password',
      host: 'localhost'
   });
   kojo.set('pg', pool);  // can be used as `kojo.get('pg')`

   const nats = new NATS({...});
   kojo.set('nats', nats);

   await kojo.ready();  // await for services and subscribers to initialize
}

return main();

Services and their methods

Service is just a directory with files which represent methods. For example:

🗀 kojo/
├── 🗀 services/
│   ├── 🗀 user/
│   │   ├── 🖹 register.js
│   │   ├── 🖹 update.js
│   │   ├── 🖹 list.js
│   │   └── ...
│   └── 🗀 profile/
│       ├── 🖹 create.js
│       ├── 🖹 update.js
│       └── ...

We see two services user and profile both of which have some methods. These methods are available from anywhere via kojo instance:

  • kojo.services.user.list()
  • kojo.services.profile.update()
  • etc

A method file must export an async function which (usually) returns a value. It will have kojo instance and logger in its context:

export default async function () {

    const [ kojo, logger ] = this;  // instance and logger passed in context
    ...
    const { profile } = kojo.services;
    
    logger.debug('creating profile', userData);
    return profile.create(userData);
};

Important: for method's context to be available, the method must be defined via function() {}, not arrow ()=>{}

Kojo is also an EventEmitter and can publish internal events:

...
const [ kojo, logger ] = this;
...
kojo.emit('profile.created', newProfile);
...
kojo.on('profile.created', (newProfile) => {
...
});

Thus, you can create 'internal' subscribers which listen to events.

Note: Methods named test are ignored and not registered. These are reserved for unit tests.

Subscribers

🗀 kojo/
├── 🗀 subscribers/
│   ├── 🖹 user.register.js
│   ├── 🖹 user.update.js
│   ├── 🖹 profile.created.js
│   └── ...

Subscriber exports an async function which is called once during kojo initialization and is not available otherwise. It is supposed to have a single subscription to a pub/sub transport subject or services's internal event, or http route and is recommended to be named accordingly. For example, subscribers/internal.user.registered.js:

export default async (kojo, logger) => {

    const { user } = kojo.services;
    
    const nats = kojo.get('nats');
    user.on('registered', (newUser) => {
        logger.debug('publishing notification');
        nats.publish('notification', newUser);
    });
};

Unlike service method, subscriber function can be defined via arrow and has kojo instance and logger as arguments, not context.

Logger

Kojo uses a custom mechanism for 'smart' logging from subscribers and services. 'Smart' means that if you log from method user.register, log entries will include "user.register":

logger.debug(userData);
☢ test.QOmup DEBUG [user.register] {...user data}

You can always use your own logger, provided you register it as an extra, but this logger will, of course, not have this 'smart' feature.

Docs

Read the docs.

Logic placement strategy

It is sometimes difficult to decide on where business logic should reside: subscribers or services. A rule of a thumb could be the following - place logic inside subscribers when in doubt. When code starts repeating or getting complicated - its time for introducing some services to keep it DRY and maintainable.

Subscriber should be the single point of entry for an event bound to your microservice, external or internal. You should be able to easily tell what exactly a microservice is responsible for by just looking at its subscribers directory. You can then track the rest of the logic by inspecting a subscriber and following the services it uses.

Test

npm test

Troubleshooting

If you see this error message:

TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".json" for /kojo/package.json

you need to launch your service with Node's --experimental-json-modules option:

node service.js --experimental-json-modules