injectt

    1.0.0 • Public • Published

    Injectt

    Straightforward Dependency Injection for Node and the browser

    npm install injectt

    An Example

    src/a.js

    class ClassA {
      costructor(b, c, param1, param2) {
        this.b = b;
        this.c = c;
        this.param1 = param1;
        this.param2 = param2;
      }
     
      // service name, requried
      static get $provides() {
        return "a";
      }
     
      // dependencies which will be passed
      // to the constructor, optional
      static get $requires() {
        return ["b", "c"];
      }
    }
     
    module.exports = ClassA;

    src/b.js

    class ClassB {
      costructor(c) {
        this.c = c;
      }
     
      static get $provides() {
        return "b";
      }
     
      static get $requires() {
        return ["c"];
      }
    }
     
    module.exports = ClassB;

    src/c.js

    class ClassC {
      costructor() {}
     
      static get $provides() {
        return "c";
      }
    }
     
    module.exports = ClassC;

    main.js

    const path = require("path");
    const Injectt = require("injectt");
     
    const di = new Injectt();
     
    // load all the services recursively
    // (only available in Node, not in browser)
    di.load(path.resolve(__dirname, "src"));
     
    // or register the classes by hand:
    //di.registerClass(require("./src/a"));
    //di.registerClass(require("./src/b"));
    //di.registerClass(require("./src/c"));
     
    const a = di.get("a", "hello world", 42);
     
    // the first argument of .get() is the service name
    assert(instanceof ClassA);
    assert(a.b instanceof ClassB);
    assert(a.c instanceof ClassC);
     
    // anything in .get() after the service name will be passed as
    // additional parameters to service's constructor
    assert(a.param1 === "hello world");
    assert(a.param2 === 42);
     
    // we can also store a variable of any type, not only services
    di.registerInstance(42, "theAnswer");
    assert(di.get("theAnswer") === 42);

    Lifecycle

    A service can have an optional static property $lifecycle which should be a string "perRequest", "unique" or "singleton".

    Omitting the property is the same as setting it to "perRequest".

    perRequest

    Each call to .get() is considered a request. Services with the lifecycle of perRequest are only instantiated once per each request.

    In the example above both a and b, which is a dependency of a, receive an instance of ClassC. Because this happens during execution of the same .get() call both services receive the same instance of ClassC.

    unique

    If ClassC had $lifecycle set to "unique" then services a and b would have received different instances of the same class ClassC. Unique services are never reused.

    singleton

    Consider the following:

    const a1 = di.get("a");
    const a2 = di.get("a");

    As we have two .get() calls these are two separate requests. So service c is instantiated twice.

    // ClassC.$lifecycle === "perRequest"
    assert(a1.c === a1.b.c);
    assert(a2.c === a2.b.c);
    assert(a1 !== a2);
    assert(a1.c !== a2.c);

    The third possible value for $lifecycle is "singleton". Singleton services are instantiated only once during the whole life of the DI container.

    In our example if ClassC was singleton then an instance of it whould have been constructed during the first .get() and then reused for any number of subsequent .get()'s.

    // ClassC.$lifecycle === "singleton"
    assert(a1.c === a1.b.c);
    assert(a2.c === a2.b.c);
    assert(a1 !== a2);
    assert(a1.c === a2.c);

    API

    • constructor()

      Constructs an instance of DI container.

      NOTE The container self-registers its own instance as a service named "di".

    • registerInstance(instance, name)

      Register variable instance value as service name. When the service is requested exactly this value will be returned.

      Returns service name

    • registerClass(classFunc)

      Register a class referred to by classFunc as a service. The class should have at least static property $provides defined which is used as service name. When the service is requested it will be either instantiated or possibly reused if it were instantiated before.

      Static properties which have a special meaning:

      • $provides - (required) service name

      • $requires - an array of dependicies names which will be instantiated and passed to the constructor in the same order

      • $lifecycle - one of:

        • perRequest - (default) service is instantiated once per each request

        • unique - service instances are never reused, new instances are always instantiated

        • singleton - service is instantiated only once per the whole lifetime of the container

      Returns service name

    • load(root [, options])

      Options:

      {
        dirInclude: null | [String] | [RegExp],
        dirExclude: null | [String] | [RegExp],
        fileInclude: null | [String] | [RegExp],
        fileExclude: null | [String] | [RegExp],
      }

      Passing null in *include/*exclude options means "skip this check".

      Default options:

      {
        dirInclude: null,
        dirExclude: [/^__/], // directory names must not start with two underscores
        fileInclude: [/\.js$/], // file names must end with .js extension
        fileExclude: [/\.test\.js$/], // but skip test files
      }

      Recursively loads and registers all the services in the given directory root. To be considered a service the file should match fileInclude option, do not match fileExclude option, and export a class with $provides static property defined. Subdirectories are checked againt dirInclude/dirExclude options.

      NOTE: This method is not available in the browser version of the library

    • has(name)

      Checks if a service is registered. Returns boolean.

    • search(re)

      Does search among the services by applying regular expression re to each service name

      Returns an array of matched names

    • getClass(name)

      Retrieves the class (constructor function really) of a service without instantiating it.

      name could also be a RegExp or an Array of strings, in which case getClass() will return a Map(name --> constructor function) of matching services.

    • get(name, ...extra)

      Instantiates if needs to and returns a service registered as name passing all the rest of the parameters to service constructor

      name could also be a RegExp or an Array of strings, in which case get() will return a Map(name --> instance) of matching services.

    • singletons(...extra)

      Returns Map(name -> instance) of all the singleton services

    Additional Notes

    • Cyclic dependencies are not allowed (an error is thrown).

    • If requested dependecy is not found an error is thrown. You can mark a dependency as optional by adding "?" at the end of the name:

      class SomeClass {
        constructor(a, b, c) {
          // b might be null
        }
       
        static get $provides() {
          return "someClass";
        }
       
        // b is optional, becomes null if not found
        static get $requires() {
          return ["a", "b?", "c"];
        }
      }

    Modern style

    Usually we use ES6 imports in the browser and we often have "static class properties" proposal enabled via Babel.

    In which case ClassA could be rewritten like this:

    class ClassA {
      static $provides = "a";
      static $requires = ["b", "c"];
     
      constructor(a, b) {
        this.a = a;
        this.b = b;
      }
    }
     
    export default ClassA;
    import Injectt from "injectt";
    import ClassA from "./src/a";
     
    const di = new Injectt();
    di.registerClass(ClassA);
    // ...

    Credits

    Inspired by Roy Jacobs' Intravenous

    Install

    npm i injectt

    DownloadsWeekly Downloads

    1

    Version

    1.0.0

    License

    MIT

    Unpacked Size

    132 kB

    Total Files

    42

    Last publish

    Collaborators

    • basarevych