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

@stone-js/service-container

v0.0.43

Published

Vanilla Javascript IoC Service Container with proposal decorator, proxy resolver and destructuring injection

Downloads

280

Readme

Stone.js: Service Container

npm npm npm Maintenance Publish Package to npmjs Conventional Commits

IoC Service Container with proposal decorator, proxy resolver and destructuring injection provides a very simple, centralized container that stores and resolves libraries, objects, and values to better organize code, manage dependencies, and enhance testability.


Synopsis

The Service Container is an advanced JavaScript/TypeScript library that helps you manage dependencies effectively in your applications. It utilizes the Inversion of Control (IoC) pattern to simplify dependency injection, allowing you to create highly decoupled and maintainable codebases.

By using Dependency Injection (DI), the service container ensures that objects are provided with their dependencies from an external source, rather than creating them internally. This approach improves testability, minimizes code duplication, and promotes the Single Responsibility Principle by allowing objects to focus only on their primary functions.

In addition to DI, the Service Container implements the Proxy pattern, which lets you access dependencies as properties of the container itself. This feature, combined with destructuring, enables a streamlined approach to dependency injection, making it easy to use dependencies without the need for extra boilerplate code.

With the Service Container, you can easily register, resolve, and manage dependencies, making it suitable for projects of any scale. It supports binding instances, singletons, and factories, as well as providing auto and conditional binding options. This flexibility makes your application more modular, scalable, and maintainable, while ensuring efficient dependency management throughout the development process.

Installation

The Service Container library is available from the npm registry and can be installed as follows:

npm i @stone-js/service-container

Yarn:

yarn add @stone-js/service-container

PNPM:

pnpm add @stone-js/service-container

[!NOTE] This package is Pure ESM. If you are unfamiliar with what that means or how to handle it in your project, please refer to this guide on Pure ESM packages.

Make sure your project setup is compatible with ESM. This might involve updating your package.json or using certain bundler configurations.

The Service Container module can only be imported via ESM import syntax:

import { Container } from '@stone-js/service-container';

Getting Started

To get started with the Service Container, you need to import the Container class and create an instance of it. This instance will act as the central registry for all your dependencies in your application.

Let's begin with a simple example to discover the power of the service container by logging a message.

1. Create the Logger Class

First, create a simple Logger class to log messages:

// Your Logger class
class Logger {
  log(message: string) {
    console.log(message);
  }
}

2. Create the Class that Provides the Message

Next, create a class A that provides a message to be logged:

// Your class A
class A {
  getMessage(): string {
    return 'Hello World!';
  }
}

3. Create the Class that Logs the Message Using the Logger

Now, create a class B that depends on Logger and A to log the message:

import { Binding } from '@stone-js/service-container';

// Your class B
class B {
  private readonly a: A;
  private readonly logger: Logger;

  constructor({ a, logger }: Binding<A | Logger>) { // Dependency injection by destructuring
    this.a = a as A;
    this.logger = logger as Logger;
  }

  logMessage() {
    this.logger.log(this.a.getMessage());
  }
}

4. Use the Container to Manage Dependencies

Finally, create an instance of the Container class and use it to manage these dependencies:

import { Container } from '@stone-js/service-container';

// Create a container instance
const container = new Container();

// Auto-register components
container
  .autoBinding(A)
  .autoBinding(B)
  .autoBinding(Logger)
  .alias(A, 'a')
  .alias(B, 'b')
  .alias(Logger, 'logger');

// Make B instance
const b = container.make(B);

// Call logMessage method
b.logMessage(); // Output: Hello World!

In this guide, we covered how to define and register dependencies using the Container class, and how to leverage dependency injection through destructuring and the Proxy pattern.

By following these steps, you can effectively manage dependencies in your applications, making them more modular, testable, and maintainable.

Usage

The true power of Stone Service Container lies in its ability to simplify and enhance the management of dependencies within your application. By leveraging the Inversion of Control (IoC) and Proxy patterns, the library offers a highly flexible and intuitive way to bind, resolve, and inject dependencies. Whether you're working with simple instances, complex singletons, or dynamic factories, the Service Container provides a robust foundation for building scalable and maintainable applications.

Binding

You can bind instances, singletons, and factories to the container using various methods provided by the Container class.

Instance

To bind a single instance or value to the container:

// Bind value
container.instance('config', { apiUrl: 'https://api.example.com' });

// Bind instance
container.instance('ApiUrl', new URL('https://api.example.com'));

Singleton

To bind a resolver function that returns a singleton instance:

// Bind a singleton using a resolver function
container.singleton('logger', () => new Logger());

// Resolve the singleton (same instance every time)
const logger1 = container.make('logger');
const logger2 = container.make('logger');
console.log(logger1 === logger2); // Should output: true

Factory

To bind a resolver function that returns a new instance each time:

// To bind a resolver function that returns a new instance each time
container.binding('userService', () => new UserService());

Auto Binding

The autoBinding method in the service container simplifies the process of binding dependencies by ensuring they are only bound if not already present in the container. This method can be used implicitly or explicitly, providing flexibility in how dependencies are registered and resolved.

Implicit Auto Binding

Implicit auto binding allows you to automatically register and bind a class to the container using the class's own name. This approach simplifies dependency management by using the class name as the key for resolution.

// Auto binding Logger
container.autoBinding(Logger);

// Resolve Logger (it will be registered implicitly if not found)
const logger = container.make('Logger'); // Prefer to use a string key for clarity.

Explicit Auto Binding

Explicit auto binding allows you to register and bind a class to the container with a specified key, as a singleton or factory, and with optional aliases. This method offers greater flexibility in naming and accessing dependencies.

// Auto binding Logger with specified key and aliases
container.autoBinding('logger', Logger, true, ['logging', 'log']);

// Resolve Logger using different aliases
const logger = container.make('logger');
const loggerByAlias = container.make('logging');

Conditional Binding

You can bind instances, singletons, and factories to the container conditionally using various methods provided by the Container class.

Instance

To bind a single instance or value to the container, if it is not already bound:

// Bind value conditionally
container.instanceIf('config', { apiUrl: 'https://api.example.com' });

// Bind instance conditionally
container.instanceIf('ApiUrl', new URL('https://api.example.com'));

Singleton

To bind a resolver function that returns a singleton instance, if it is not already bound:

container.singletonIf('logger', () => new Logger());

Factory

To bind a resolver function that returns a new instance each time, if it is not already bound:

container.bindingIf('userService', () => new UserService());

Resolving Dependencies

You can resolve dependencies from the container using the make method:

const config = container.make('config');
const logger = container.make('logger');
const userService = container.make('userService');

On-Demand Resolving

This method provides a convenient way to resolve a value from the container by its key and return it wrapped in a factory function. This approach is useful when you need to create multiple instances of a dependency on demand.

// Get the logger factory function
const loggerFactory = container.factory('logger');

// Resolve logger on demand
const logger = loggerFactory();

Conditional Resolving

This method checks if the dependency is already bound in the container. If it is, the dependency is resolved. If not, the method binds the dependency and then resolves it. This ensures that dependencies are always available when needed, without redundant bindings.

// Assuming Logger is a class that we want to ensure is bound and resolved
container.resolve('Logger', true);

// Resolve the logger instance
const logger = container.make('Logger');

Using Aliases

Aliases allow you to reference bindings by alternative names.

Setting Aliases

To set an alias for a binding:

// Set an alias for Logger
container.alias(Logger, ['logger', 'logging', 'log']);

Retrieving Aliases

To retrieve a binding by its alias:

const logger = container.make('logging');

Checking Bindings

You can check if a binding or alias exists in the container:

// With bound method
const hasLogger = container.bound(Logger); // true

// Can be used with alias
const hasLoggerAlias = container.bound('logging'); // true

// An alias of bound method
const hasLoggerAliasUsingHas = container.has(Logger); // true

// Check if this alias exists
const isAlias = container.isAlias('logging'); // true

Clearing the Container

To remove all bindings and reset the container:

// Clear all bindings and aliases
container.clear();

With this library, managing dependencies in your application becomes much simpler and more efficient. By adhering to the principles of Inversion of Control and Dependency Injection, you can create more modular, testable, and maintainable code.

Summary

The Stone Service Container simplifies dependency management in your applications, making them more modular, testable, and maintainable. It provides tools for binding instances, singletons, and factories, as well as auto and conditional binding. By leveraging Inversion of Control and dependency injection, the service container ensures efficient, flexible, and scalable management of dependencies, fitting projects of any size.

API documentation

Contributing

See Contributing Guide.

Credits