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

@swindle/container

v1.0.1

Published

A simple dependency injection container for TypesScript.

Downloads

3

Readme

Swindle Container

A minimalist dependency injection container for Typescript.

Dependency Injection?

What is it

Dependency Injection is a technique in which an object receives other objects that it depends on. These other objects are referred to as "dependencies". For more information about Dependency injection, refer to the Wikipedia page about the subject found here.

Dependency Injection Container (DIC)?

A Dependency Injection Container (or DIC for short) is an object that knows how to create other objects. In terms of Dependency Injection, the job of a Dependency injection Container is to manage the creation and configuration of objects. In order to achieve this, it must be aware about about the contructor arguments and the corresponding relationships between these objects.

installation

To install Swindle, just use the following command with NPM

npm i @swindle/container

Similarly, run the following command with Yarn

yarn add @swindle/container

Usage

Creating a Container

The Container class is the main class you will be interacting with. It has all the functionality needed for registering bindings, registering instances, and retrieving dependnecies.

To create a contaner, we instanciate the Container class as follows.

import { Container } from '@swindle/container';

const container = new Container();

// ... do some more stuff here.

Binding Dependencies

To bind a dependency, you can either use the bindFactory() method to tell Swindle how to instanciate your dependency, or use the bindInstance() method to bind a specific instance to the container. Once a binding is created, it can later be referenced the dependency's class name.

bindFactory(token, factory)

The bindFactory() method defines a factory function to tell Swindle how to instanciate your dependency. The bindFactory() method takes two arguments. The first argument is the token that will be used to refer to your dependency. This is normally the name of the class you are binding. The second argument is a factory function that tells Swindle how to instanciate your dependency when it is needed. The factory function accepts the container as a single argument. And, expects an instance of your dependency to be returned. Below is an example of how we might use the bindFactory() method to bind an instance of Foo to our container.

class Foo {
   constructor() {}
}

container.bindFactory(Foo, (container) => {
   return new Foo();
});

In the above example, we define a class Foo, and add a binding that maps the class Foo to our factory method. Since Foo() does not require any arguments, the factory function is pretty trivial. However, in many cases, the dependencies we register will themselves have other dependencies. So, in this next example, let's look at how we can create a binding where our dependency itself has other dependencies. Here, we will build on our previous example by defining a Bar class that depends on Foo.

class Bar {
   private foo: Foo;
   
   constructor(theFoo: Foo) {
      this.foo = theFoo;
   }
}

container.bindFactory(Bar, (container) => {
   return new Bar(container.get(Foo));
});

In the above example, you can see that we simply refer to the contianer to retrieve any additional dependencies we may need to instanciate our binding. Once our bindings are created, we can refer to them later at any time using the get() method, described in the next section.

Something to note is that bindings created with the bindFactory() method are lazy loaded. That means, the container will not create instances of these dependencies until they are needed.

bindInstance(token, instance)

There are times when we cannot always rely on a factory function to create our dependencies. A good example of this is with abstract classes. Since we cannot instanciate an abstract class, we need a way to tell Swindle how to link the token of an abstract class to a specific instance. It is in these situations where the bindInstance() method comes in handy. Unlike bindFactory(), bindInstance() binds a class to a specific instance. And, that instance will always be returned in every request for that dependency. Let's bind another dependency to our container. This time, let's use the bindInstance() method.

abstract class AbstractBuzz {
   constructor() {}
}
class ConcreteBuzz extends AbstractBuzz {
   constructor() {
      super();
   }
}

container.bindInstance(AbstractBuzz, new ConcreteBuzz());

Instead of passing a factory function, like in bindFactory(), we pass an instance of our dependency as the second argument to bindInstance(). Now, every request for the AbstractBuzz dependency will return the instance we provided. Since we are binding instances directly to our container, bindings created with bindInstance() are kept alive in the container, unlike bindings registered with the bindFactory() method above.

Something important to note herre is that Swindle uses the class name as the identifier for the binded dependency. That is to say, in Swindle, container.bindInstance(AbstractBuzz, new ConcreteBuzz()) and container.bindInstance(ConcreteBuzz, new ConcreteBuzz()) are two different dependencies.

Swindle only allows you to register one binding for each class. Attempting to register more that one dependency of a class will throw a DuplicateBindingException for bindFactory() or a DuplicateInstanceException for bindInstance(). To check whether or not you already have a registered Dependency for a class, you can use Swindle's has() method, like below.

if (container.has(Foo) {...} // returns true
if (container.has(Bas) {...} // returns false

If you need to bind multiple instances of the same class, you can do so by binding each class to a module. This is something we will go over in a later section.

Retrieving Dependencies

Once we have binded our dependencies, we can retrieve them using Swindle's get() method. The get() method takes the class name as its only argument, which it uses to retrieve the dependency. Following up from our earlier example, we can get the dependencies we binded earlier like so:

 const foo = container.get(Foo);
 const bar = container.get(Bar);
 const buzz = container.get(AbstractBuzz);

If get() is unable to resolve the dependency, it will throw a DependencyNotFoundException error.

Dependency Resolution

When get() is invoked, Swindle will first check to see if there is an instance of the dependency that is available. If there is, Swindle returns that instance. If there is no instance of that dependency available, Swindle will attempt to use the dependency's registered factory to create an instance of that dependency. If there is no registered factory, Swindle throws a DependencyNotFoundException error.

Circular Dependencies

Swindle automatically detects circular dependencies. Whenever Swindle detects a circular dependency, it will throw a CircularDependencyException error.

Modules

When your application starts to grow, managing all your dependencies can become overwhelming. To help with this, Swindle lets you organize your dependencies into modules. Modules are like containers within your container. You can use modules to beteer compartmentalize your dependencies.

Creating a Module

By default, Swindle stores your dependencies in the global module (or root module). To create a module, use the createModule() method. The createModule() method takes a unique module name as its only argument.

container.createModule('mymodule');

Module names must be unique. If you attept to create two modules of the same name, Swindle will throw a DuplicateModuleException error. If you want to check whether or not a module already exists, you can do so using the containsModule() method.

if (container.containsModule('mymodule')) ... // returns true

Once a module is created, we are free to register dependencies to it, just like we did before.

Accessing a Module

To access a module, use the module() method.

container.module('mymodule').bindFactory(Foo, (container) => {
   return new Foo();
});

container.module('mymodule').get(Foo);

Using the module() method, we can bind and access module dependencies just like we did for global dependencies. Dependencies binded to a module are unique to that module. For example, the below snippet would be referring to two different Foo dependencies.

const globalFoo = container.get(Foo);
const moduleFoo = container.module('mymodule').get(Foo);

The first line is referring to the Global Foo dependency. The second is referring to a Foo dependency defined in the mymodule module.

Submodules

In addition to modules, Swindle also allows you to create submodules. Submodules are created using the createModule() method in much the same way as you create modules.

class UserFactory {

   constructor() {...}

   public createUser(name: string): User {...}
}

container.createModule('users.factories');
container.module('users.factories').bind(UserFactory, (container) => {
   return new UserFactory();
});

Here, we first create a module called users, which itself has a sub-module called factories. We then bind a UserFactory class to the factories sub-module within our users module. Notice here we are using a dot-notation to reference our submodules. By default, we reference our submodules by using the dot-notation. If need be, this behavior can be customized, as we will see a little later.

We can now access our UserFactory like so.

const user = container.module('users.factories').get(UserFactory).createUser("John");

Like with modules, bindings created within a submodule is only accessible within the submodule it was binded to.

Customizing How We Access Modules and Sub-Modules

By default, Swindle lets you name your modules and submodules any combination of upper and lower case characters (A-Z, a-z), numbers (0 - 9), dashes (-), and underscores (_). For most use cases, this will work just fine. However, if you want to customize this behavior, Swindle lets you pass your own custom ModuleParser class for it to use.

import { Container, ModuleParser } from '@swindle/container';

class MyModuleParser extends ModuleParser {

   constructor() {
      super();
   }

   public canParse(modulePath: string): boolean {
      return new RegExp('^[A-Za-z>-]+$').test(name);
   }

   public delimiter(): string {
      return '->';
   }
}

Currently, there are two things we can customize. The canParse() method determines which sequences of characters Swindle will recognize as a valid module path. Here, we are restricting all module names to only contain capital and lower case characters, dashes, and the greater-than symbol. The second method we can customize is the delimiter() method. This method tells Swindle to use the -> character to differenciate between modules and sub-modules (maybe you are just missing the old days when PHP was still cool).

Something to keep in mind when creating your own Module Parser is that your delimiter character(s) must be a valid character for your parser. That is, that character returns true when checked by canParse(). Otherwise, Swindle will throw an InvalidModuleException error.

To tell Swindle to use our custom module parser, we just pass it as an argument when creating our Container instance.

const customContainer = new Container(new MyModuleParser());
customContainer.createModule('module->submodule');

Notice here we can noe use our custom delimiter to create and access modules.

If, at any time, you want to get the instance of the Module Parser your container is currently using, you can do so with the parser() method.

const parser = customContainer.parser();

Tests

To run the tests, use the following command with NPM

npm run test

Or, with Yarn

yarn test

Issues and Bug Reporting

If you are facing any problems while using Swindle, feel free to open an issue, where we'd be happy to help you out.

License

Swindle is available under the MIT license.