Wondering what’s next for npm?Check out our public roadmap! »

    @perivel/verdic
    TypeScript icon, indicating that this package has built-in type declarations

    1.0.6 • Public • Published

    Verdic

    Verdic is a minimalist dependency injection container for Typescript. To keep with this minimalist philosophy, we intentionally keep Verdic's feature set focused on the core functionalities of a Dependency Injection Container, leaving more "nice to have" features (like decorators) to be added by the user. By doing so, Verdic becomes more flexible and reusabe.

    installation

    To install Verdic, just use the following command with NPM

    npm i @perivel/verdic
    

    Similarly, run the following command with Yarn

    yarn add @perivel/verdic
    

    Usage

    Creating a Container

    The VerdicContainer 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 VerdicContainer class as follows.

    import { VerdicContainer } from '@perivel/verdic';
    
    const container = new VerdicContainer();
    
    // ... do some more stuff here.

    Binding Dependencies

    To bind a dependency, you can either use the bindFactory() method to tell Verdic 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 Verdic 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 Verdic 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 Verdic 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 Verdic uses the class name as the identifier for the binded dependency. That is to say, in Verdic, container.bindInstance(AbstractBuzz, new ConcreteBuzz()) and container.bindInstance(ConcreteBuzz, new ConcreteBuzz()) are two different dependencies.

    Verdic 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 Verdic'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 Verdic'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, Verdic will first check to see if there is an instance of the dependency that is available. If there is, Verdic returns that instance. If there is no instance of that dependency available, Verdic will attempt to use the dependency's registered factory to create an instance of that dependency. If there is no registered factory, Verdic throws a DependencyNotFoundException error.

    Circular Dependencies

    Verdic automatically detects circular dependencies. Whenever Verdic 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, Verdic 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, Verdic 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, verdic 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, Verdic 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, Verdic 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, Verdic lets you pass your own custom ModuleParser class for it to use.

    import { VerdicContainer, ModuleParser } from '@perivel/verdic';
    
    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 Verdic 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 Verdic 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, Verdic will throw an InvalidModuleException error.

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

    const customContainer = new VerdicContainer(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 Verdic, feel free to open an issue, where we'd be happy to help you out.

    License

    Verdic is available under the MIT license.

    Install

    npm i @perivel/verdic

    DownloadsWeekly Downloads

    1

    Version

    1.0.6

    License

    MIT

    Unpacked Size

    86.2 kB

    Total Files

    86

    Last publish

    Collaborators

    • avatar