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

monopole

v0.3.0

Published

A versatile dependency injection container with features like value bindings, resolvers, aliases, and support for singleton, transient, and scoped lifetimes.

Downloads

103

Readme

monopole

This library provides a powerful and flexible dependency injection container for Deno applications. It allows you to easily manage your application's dependencies and their lifetimes. The library offers a variety of features, including value bindings, resolvers, aliases, and support for different lifetimes (singleton, transient, and scoped).

Features

  • Value bindings (also support async)
  • Resolver bindings (also support async)
  • Class bindings
  • Alias bindings
  • Inject decorator for resolving dependencies
  • Circular dependency resolution (even self dependency!)
  • Support for singleton, transient, and scoped lifetimes
  • Module
  • Bootstrap

Usage

with Deno

import { createContainer } from "https://deno.land/x/monopole/mod.ts";

const container = createContainer();

with Node.js & Browser

Install

npm install monopole
import { createContainer } from "monopole";

// Usage is as above :-)

Value bindings

Value bindings allow you to bind a value directly to a specific key. This is useful when you want to store configuration values, pre-built instances, or other simple values in the container.

Example:

const container = createContainer();
container.value("message", "hello world!");
container.value("instance", Promise.resolve({ name: "instance" }));

const message = await container.resolve("message");
const instance = await container.resolve("instance");

console.log(message); // "hello world!"
console.log(instance); // { name: "instance" }

Resolver bindings

Resolver bindings allow you to provide a factory function that will be invoked when the dependency is resolved. This is useful when you want to create an instance of a class, build an object or return a value based on runtime information.

Example:

const container = createContainer();

container.resolver("resolver", () => ({ message: "this is resolver" }));
container.resolver("asyncResolver", async () => {
  return new Promise((resolve) =>
    setTimeout(() => resolve({ message: "this is async resolver" }), 50)
  );
});

const resolver = await container.resolve("resolver");
const asyncResolver = await container.resolve("asyncResolver");

console.log(resolver); // { message: "this is resolver" }
console.log(asyncResolver); // { message: "this is async resolver" }

Class bindings

Class bindings allow you to bind a class constructor to a specific key. When the key is resolved, a new instance of the class will be created.

Example:

class BaseClass {
}

class MyClass extends BaseClass {
  constructor() {
    this.message = "hello world!";
  }
}

const container = createContainer();
container.bind(BaseClass, MyClass);

const instance = await container.resolve(BaseClass);

console.log(instance.message); // "hello world!"

Alias bindings

Alias bindings allow you to bind one key to another key, effectively creating an alias for a value in the container.

Example:

const container = createContainer();

container.value("original", "this is the original value");
container.alias("alias", "original");

const original = await container.resolve("original");
const alias = await container.resolve("alias");

console.log(original); // "this is the original value"
console.log(alias); // "this is the original value"

Inject decorator for resolving dependencies

The @Inject decorator is a convenient way to resolve dependencies and inject them into a class. This decorator makes it easy to specify which dependencies a class requires, while the dependency injection library takes care of the underlying instantiation and management of the dependencies.

In the provided example code, a test demonstrates how to use the @Inject decorator to resolve a dependency for the Controller class:

class Connection {
}

class Controller {
  @Inject("connection")
  public connection!: Connection;
}

container.bind("connection", Connection);
container.bind(Controller);

const controller = await container.resolve(Controller);

controller.connection instanceof Connection; // true

Circular dependency resolution

Circular dependency resolution Circular dependency resolution is a feature of the dependency injection library that allows you to handle cases where two or more classes depend on each other. This feature is useful in scenarios where classes have a mutual relationship, such as parent-child or sibling relationships. The library can resolve these circular dependencies automatically, ensuring that the correct instances are injected into the appropriate classes.

In the example test code provided, a circular dependency is created between the Parent and Child classes. Each class has an @Inject decorator on a property, indicating that it should be injected with an instance of the other class:

const container = createContainer();

class Parent {
  @Inject("child")
  public child!: Child;
}

class Child {
  @Inject("parent")
  public parent!: Parent;
}

container.bind("parent", Parent);
container.bind("child", Child);

// assert
const parent = await container.resolve<Parent>("parent");
const child = await container.resolve<Child>("child");

console.log(parent.child === child); // true
console.log(child.parent === parent); // true

Support for singleton, transient, and scoped lifetimes

The container supports different lifetimes for bindings:

  • Singleton: The instance will be created once and reused for all subsequent resolutions.
  • Transient: A new instance will be created for each resolution.
  • Scoped: The instance will be created once per scope.

Example:

class SingletonClass {}
class TransientClass {}
class ScopedClass {}

const container = createContainer();

container.bind(SingletonClass).lifetime(Lifetime.Singleton);
container.bind(TransientClass).lifetime(Lifetime.Transient);
container.bind(ScopedClass).lifetime(Lifetime.Scoped);

// Singleton example
const singleton1 = await container.resolve(SingletonClass);
const singleton2 = await container.resolve(SingletonClass);
console.log(singleton1 === singleton2); // true

// Transient example
const transient1 = await container.resolve(TransientClass);
const transient2 = await container.resolve(TransientClass);
console.log(transient1 === transient2); // false

// Scoped example
const scopedContainer = await container.scope();
const scoped1 = await scopedContainer.resolve(ScopedClass);
const scoped2 = await scopedContainer.resolve(ScopedClass);
console.log(scoped1 === scoped2); // true

Module

Modules offer a convenient way to organize and manage dependencies in your application. By separating concerns, they help make your code more modular and maintainable.

In the following example, a ConnectionModule is created that provides a Connection class and handles connecting and closing the connection during the boot and close phases of the application lifecycle.

class Connection {
  connect(): Promise<void>;
  close(): Promise<void>;
}

class ConnectionModule implements Module {
  provide(container: ModuleDescriptor) {
    container.bind(Connection);
  }

  async boot(container: ModuleDescriptor) {
    const connection = await container.resolve(Connection);
    await connection.connect();
  }

  async close(container: ModuleDescriptor) {
    const connection = await container.resolve(Connection);
    await connection.close();
  }
}

const container = createContainer();

container.register(new ConnectionModule());

await container.boot();

/* ... */

// When the application is shutting down
await container.close();

To use a module, simply create a new instance of it and register it with the container using the register method. The module's provide, boot, and close methods will be called automatically during the container's lifecycle.

Bootstrap

In this guide, we'll explore the boot process in detail and demonstrate how it enables you to access all objects via get without promises. We'll also show how the module's boot method is executed during this process.

Check out the example below:

const container = createContainer();

container.value("value", Promise.resolve("by value"));
container.resolver("resolver", async () => "by resolver");
container.alias("alias.value", "value");
container.alias("alias.resolver", "resolver");

await container.boot();

container.get("value") === "by value"; // true
container.get("resolver") === "by resolver"; // true
container.get("alias.value") === "by value"; // true
container.get("alias.resolver") === "by resolver"; // true

During the execution of the boot method, the following internal steps are performed:

  1. The registered modules are read and their provide methods are executed.
  2. The boot method of each registered module is executed.
  3. All registered singleton and scoped objects are created and stored within the container.

This process ensures that all dependencies are properly initialized and available for use throughout your application.

Example