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

inversify-components

v0.5.0

Published

Plugin based mini core framework. Combines InversifyJS with Extension Point Pattern / Service Provider Interfaces. Enables building components with inversify using binding descriptors.

Downloads

104

Readme

inversify-components

Small framework on top of InversifyJS to enable component based dependency injection. Each component describes its interfaces and bindings with the help of descriptors, and never accesses the dependency injection container directly. This enables you to:

  • Develop loosely coupled and independent components, without exploiting your whole dependency injection container
  • Enable / disable / mock whole components in your application
  • Use scoped child containers, for example bind dependencies only to a http request
  • Implement the extension point pattern to "plug-in" extensions of components from other components

Installation

Install inversify-components and set it as an dependency in your local package.json:

npm i --save inversify-components

Usage

Basic setup instructions

  1. Create the inversify-components container:
import { ContainerImpl } from "inversify-components";
let container = new ContainerImpl();
// Also supports options:
// let container = new ContainerImpl({ defaultScope: "Singleton" });
  1. Create your main application, which acts as the composition root:
import { MainApplication } from "inversify-components";

class App implements MainApplication {
  execute(container: Container) {
    // Start your application using the container!
  }
}
  1. Register all components (see below) you would like to use:
import { descriptor } from "my-component-descriptor";
container.componentRegistry.addFromDescriptor(descriptor);
  1. Register all bindings of all registered component descriptors
container.componentRegistry.autobind(container.inversifyInstance);
  1. Optional: Configure some of your components:
container.componentRegistry.lookup(nameOfComponent).addConfiguration({
  configurationKey: "configurationValue"
});
  1. Start your application
container.setMainApplication(new App());
container.runMain();

Components and component descriptors

inversify-components allows you to basically split your applications into independent components. To achieve this, each component exports a component descriptor:

import { ComponentDescriptor } from "inversify-components";

export const descriptor: ComponentDescriptor = {
  name: "name-of-component", // This must be unique for all registered components
  bindings: {
    root: (bindService, lookupService) => {
      // Binding of services is very similar to inversifyJS:
      bindService.bindGlobalService<TypeOfService>("service-name").to(MyServiceClass);
      
      // MyServiceClass is now bound to "name-of-component:service-name" and available in all other components.
    }
  }
};

Notice that each binding gets the unique component name as a prefix. This guarantees that there are not duplicate service bindings across all registered components.

Grab inversify container

You are also able to grab the inversify coontainer in a ComponentDescriptor:

import { ComponentDescriptor } from "inversify-components";

export const descriptor: ComponentDescriptor = {
  name: "name-of-component", // This must be unique for all registered components
  bindings: {
    root: (bindService, lookupService, inversifyContainer) => {
      // Unbind something..
      inversifyContainer.unbind("service");

      bindService.bindGlobalService<TypeOfService>("service-name").to(MyServiceClass);
    }
  }
};

So if needed, you are always in full control inside your dependency descriptors.

Changing the scope of a binding

The above component descriptor executes bindings for the root scope. This is the default scope for inversify-components, which is executed automatically at autobind. But you could also register bindings for a specific scope, and execute this scope at application runtime to a specific point in time:

import { ComponentDescriptor } from "inversify-components";

export const descriptor: ComponentDescriptor = {
  name: "name-of-component",
  bindings: {
    root: (bindService, lookupService) => {
      bindService.bindGlobalService<TypeOfService>("service-name").to(MyServiceClass);
    }
    request: (bindService, lookupService) => {
      // Is not available at application start, but as soon as you open your "request" scope:
      bindService.bindGlobalService<TypeOfService>("current-session").toDynamicValue(....);
    }
  }
};

// In your MainApplication / App, as soon as you would like to open the above "request" scope:
// 1) Create inversify child container
let scopedRequestContainer = container.inversifyInstance.createChild();

// 2) Possibly bind some dependent values to this container, e. g. the current request headers and body:
scopedRequestContainer.bind("request-body").toConstantValue(currentRequestBody);

// 3) Execute scoped "request" bindings in this container
container.componentRegistry.autobind(scopedRequestContainer, [], "request");

// 4) Go on in your compoisiton root with child container
scopedRequestContainer.get(...) // Maybe your request handler?

Using extension points

To enable plugging into your component, you can define extension points. This is done using symbols.

Component A: The component which owns the extension point and wants to load plugins:

import { ComponentDescriptor } from "inversify-components";
import { injectable, multiInject, optional } from "inversify";

const myExtensionPoints = {
  "firstExtensionPoint": Symbol("first-extension-point")
}

export const descriptor: ComponentDescriptor = {
  name: "component-a",
  
  // Register all available extension points
  interfaces: myExtensionPoints
};

@injectable()
class ClassUsingPlugins {
  // Now you can just inject all plugins registered at firstExtensionPoint and use them:
  constructor(@optional() @multiInject(myExtensionPoints.firstExtensionPoint) plugins) {
    this.plugins = plugins;
  }
}

Component B: The component which adds a plugin to extension point firstExtensionPoint:

export const descriptor: ComponentDescriptor = {
  name: "component-b",
  bindings: {
    root: (bindService, lookupService) => {
      let extensionPoint = lookupService.lookup("component-a").getInterface("firstExtensionPoint");
      bindService.bindExtension<ExtensionType>(extensionPoint).to(MyPluginClass);
    }
  }
};

Configuration

The basic style of configuring components is described in this gist. This style enables you to define default and required configurations without hassle.

Set default configuration

You can set a default configuration for your component by adding it to your descriptor:

const configuration: Configuration.Default = {
  "configurationKey": "configurationValue";
};

export const descriptor: ComponentDescriptor<Configuration.Default> = {
  name: "my-component-name",
  defaultConfiguration: configuration
}

Inject configuration values

In all of your classes, you can inject your component meta data, which includes the components configuration:

import { inject, injectable } from "inversify";
import { Component } from "inversify-components";

@injectable()
class MyClass {
  constrcutor(@inject("meta:component//my-component-name") component: Component<Configuration.Runtime>)
    this.configuration = this.component.configuration;
  }
}