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

cyclus

v2.0.3

Published

IOC and lifecycle for javascript object

Downloads

105

Readme

Cyclus

build status codecov dependencies Status devDependencies Status

'Cyclus' is a tiny lib for managing the lifecycle and dependencies of software components which have runtime state.

Inspire from 'Component' in clojure, make sure you read the document here

Usage

npm install cyclus
import { Lifecycle, SystemMap, using } from "cyclus";
// or import { Lifecycle, SystemMap, using } from "cyclus/dist/browser/cyclus";
// or import { Lifecycle, SystemMap, using } from "cyclus/dist/es5";
// or import { Lifecycle, SystemMap, using } from "cyclus/dist/es6";
// or import { Lifecycle, SystemMap, using } from "cyclus/dist/esnext";

Creating Components

To create a component, define a class that extends Lifecycle class.

class Database extends Lifecycle {
  dbConnection: string;

  start() {
    console.log("Start Database");
    this.dbConnection = "OPENED";
  }

  stop() {
    console.log("Stop Database");
    this.dbConnection = "CLOSED";
  }
}
  
class Scheduler extends Lifecycle {
  tick: number;

  start() {
    console.log("Start Scheduler");
    this.tick = 10;
  }

  stop() {
    console.log("Stop Scheduler");
  }
}

Optionally, provide a constructor function that takes arguments for the essential configuration parameters of the component, leaving the runtime state blank.

const createDatabase = (): Lifecycle => new Database();

Define other components in terms of the components on which they depend.

class ExampleComponent extends Lifecycle {
  database: Database;
  scheduler: Scheduler;

  start() {
    console.log("Start ExampleComponent");
  }

  stop() {
    console.log("Stop ExampleComponent");
  }
}

Do not pass component dependencies in a constructor. Systems are responsible for injecting runtime dependencies into the components they contain: see the next section.

Systems

Components are composed into systems. A system is a component which knows how to start and stop other components. It is also responsible for injecting dependencies into the components which need them.

Specify the dependency relationships among components with the using function. using takes a component and a collection of keys naming that component's dependencies.

If the component and the system use the same keys, then you can specify dependencies as a array of keys:

const system = new SystemMap({
  database: createDatabase(),
  scheduler: createScheduler(),
  exampleComponent: using(createExampleComponent(), [
    "database",
    "scheduler"
  ])
});

If the component and the system use different keys, then specify them as a map of { [component-key]: "system-key" }. That is, the using keys match the keys in the component, the values match keys in the system.

const system = new SystemMap({
  db: createDatabase(),
  sched: createScheduler(),
  exampleComponent: using(createExampleComponent(), {
    database: "db",
    scheduler: "sched"
  })
});
//  ^          ^
//  |          |
//  |          \- Keys in the system map
//  |
//  \- Keys in the ExampleComponent record

The system map provides its own implementation of the Lifecycle protocol which uses this dependency information (stored as metadata on each component) to start the components in the correct order.

Before starting each component, the system will assign its dependencies based on the metadata provided by using.

Stop a system by calling the stop method on it. This will stop each component, in reverse dependency order, and then re-assign the dependencies of each component. Note: stop is not the exact inverse of start; component dependencies will still be associated.

system.start();

Simple system

SystemMap can access object that is not an instance of LifeCycle component. Make sure you see all examples here

const system = new SystemMap({
  inputChannel: csp.chan(1024),
  resultChannel: csp.chan(1024),
  db: new Database(config.database),
  mailgun: new EmailService(config.mailgun),
  worker: using(new Worker({ workFn: logAndSendEmails }), {
    inputChannel: "inputChannel",
    db: "db",
    emailService: "mailgun",
    resultChannel: "resultChannel"
  }),
  outgoingEmails: using(
    new Queue(
      Object.assign({}, config.outgoingEmails, {
        outgoingMessagesChan: csp.chan()
      })
    ),
    {
      incomingMessagesChan: "inputChannel"
    }
  ),
  sendEmails: using(
    new Queue(
      Object.assign({}, config.sendEmails, {
        incomingMessagesChan: csp.chan()
      })
    ),
    {
      outgoingMessagesChan: "resultChannel"
    }
  )
});

Replace

Calling replace with given hashmap of new dependencies

Gracefully restart

system.replace({ database: createNewDatabase() }, { shouldRestart: true });

This will automatically stop previous version of the database, and start a new one. Example for reloadable app under example/reloadable-system

alt text

alt text

Additionally, { shouldRestart: [...someSystemKeys] } will receive an array of components. This will stop those components and start them again.

system.replace({ database: createNewDatabase() }, { shouldRestart: ["scheduler"] });

In this case, it will stop both database and scheduler then start them again

Hot patch a running system

For many cases, gracefully restart won't work correctly due to the workload on each component. For example, we can't stop a database in the middle of heavy load from client. So the best way is to create a new instance, replace a running component, then stop old component

const oldDatabaseComponent = system.map.database;
const newDatabaseComponent = createNewDatabase();

await newDatabaseComponent.start();
await system.replace({ database: newDatabaseComponent });
await oldDatabaseComponent.stop(); // this has to process any remained request before exist

Testing

system.replace({ database: createFakeDatabase() })
system.start();

Promise

start, stop, or replace will return a promise instead of running synchronously. So makes sure you wait for the promise to resolve.

const order = [];

class Component1 extends Lifecycle {
  async start() {
    await timeout(1000);
    order.push("Start component 1 after 1000");
  }

  async stop() {
    await timeout(1000);
    order.push("Stop component 1 after 1000");
  }
}

class Component2 extends Lifecycle {
  async start() {
    await timeout(2000);
    order.push("Start component 2 after 2000");
  }

  async stop() {
    await timeout(2000);
    order.push("Stop component 2 after 2000");
  }
}

class Component3 extends Lifecycle {
  component1: Component1;
  component2: Component2;

  start() {
    order.push("Start component 3");
  }

  stop() {
    order.push("Stop component 3");
  }
}

const system = new SystemMap({
  component1: createComponent1(),
  component2: createComponent2(),
  component3: using(createComponent3(), ["component1", "component2"])
});

system.start().then(() => {
  // system started
  // order = [
  //   "Start component 2 after 2000",
  //   "Start component 1 after 1000",
  //   "Start component 3"
  // ];
});

Idempotent

A system is idempotent, this means it doesn't how many time your trigger a system to start or stop, it will behave as if you was calling it once. For example,

system.start();
system.start();  // => system.start();
system.start();

Raw access to component

After component initialized, it will create a map at .map from system, this is intended for internal access only. This can be use to access component after they are started or stopped, but do not modify system.map directly, use replace instead.

const system = new SystemMap({
  component1: createComponent1(),
  component2: createComponent2(),
  component3: using(createComponent3(), ["component1", "component2"])
});

system.start().then(() => {
  // system started

  console.log(system.map); // => { component1, component2, component3 }
});