Skip to content

Instantly share code, notes, and snippets.

@mmthomas
Created November 16, 2012 17:12
Show Gist options
  • Select an option

  • Save mmthomas/4089076 to your computer and use it in GitHub Desktop.

Select an option

Save mmthomas/4089076 to your computer and use it in GitHub Desktop.

Revisions

  1. mmthomas revised this gist Dec 12, 2012. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions ServiceKernel.js
    Original file line number Diff line number Diff line change
    @@ -7,6 +7,8 @@
    *
    * MIT Licensed.
    *
    * Unit tests can be found at https://gist.github.com/4270523
    *
    */

    /**
  2. mmthomas revised this gist Dec 12, 2012. 1 changed file with 0 additions and 255 deletions.
    255 changes: 0 additions & 255 deletions ServiceKernel.Tests.js
    Original file line number Diff line number Diff line change
    @@ -1,255 +0,0 @@
    // requires QUnit test framework

    module("ServiceKernelTests");

    if (typeof throws === "undefined") throws = raises;

    test("require unregistered service throws", function () {
    var kernel = new ServiceKernel();

    throws(function () {
    kernel.require("a", function (a) {
    ok(false, "should not get here.");
    });
    }, Error);
    });

    test("require 'require' service succeeds", function () {
    var kernel = new ServiceKernel();

    var requireCallbackInvoked = false;
    kernel.require("require", function (require) {
    ok(typeof require === "function");
    require("require", function (require2) {
    deepEqual(require, require2);
    requireCallbackInvoked = true;
    });
    });

    ok(requireCallbackInvoked);
    });

    test("require 'kernel' service succeeds", function () {
    var kernel = new ServiceKernel();

    var requireCallbackInvoked = false;
    kernel.require("kernel", function (kernel2) {
    deepEqual(kernel, kernel2);
    requireCallbackInvoked = true;
    });

    ok(requireCallbackInvoked);
    });

    test("undefine existing service returns true", function () {
    var kernel = new ServiceKernel();
    ok(kernel.undefine("require"));
    ok(kernel.undefine("kernel"));
    });

    test("require undefined service throws", function () {
    var kernel = new ServiceKernel();

    kernel.undefine("require");
    throws(function () {
    kernel.require("require", function () {
    ok(false, "should not get here.");
    });
    }, Error);

    kernel.undefine("kernel");
    throws(function () {
    kernel.require("kernel", function () {
    ok(false, "should not get here.");
    });
    }, Error);
    });

    test("undefine non-existent service returns false", function () {
    var kernel = new ServiceKernel();
    ok(!kernel.undefine("foo"));
    });

    test("define existing service throws", function () {
    var kernel = new ServiceKernel();

    throws(function () {
    kernel.define("require", function () {
    });
    }, Error);
    throws(function () {
    kernel.define("kernel", function () {
    });
    }, Error);

    var service = defineService(function () {
    return this;
    });

    throws(function () {
    kernel.define("require", service);
    }, Error);
    throws(function () {
    kernel.define("kernel", service);
    }, Error);
    });

    test("require service instance succeeds", function () {
    var kernel = new ServiceKernel();

    var a = {
    foo: 0
    };
    kernel.defineInstance("a", a);

    var requireCallbackInvoked = false;
    kernel.require("a", function (a2) {
    deepEqual(a, a2);
    requireCallbackInvoked = true;
    });

    ok(requireCallbackInvoked);
    });

    test("require service succeeds", function () {
    var kernel = new ServiceKernel();

    kernel.define("a", function () {
    return function () {
    return "a";
    }
    });

    var requireCallbackInvoked = false;
    kernel.require("a", function (a) {
    equal("a", a());
    requireCallbackInvoked = true;
    });

    ok(requireCallbackInvoked);
    });

    test("require service with dependency succeeds", function () {
    var kernel = new ServiceKernel();

    kernel.define("a", function () {
    return {
    foo: function () {
    return "afoo";
    }
    };
    });

    var bServiceInitialized = false;
    kernel.define("b", ["a"], function (a) {
    bServiceInitialized = true;
    return {
    a_foo: function () {
    return a.foo();
    }
    };
    });

    var requireCallbackInvoked = false;
    kernel.require("b", function (b) {
    equal("afoo", b.a_foo());
    requireCallbackInvoked = true;
    });

    ok(requireCallbackInvoked);
    ok(bServiceInitialized);
    });

    test("require service with circular dependency succeeds", function () {
    var kernel = new ServiceKernel();

    var aServiceInitialized = false;
    kernel.define("a", [
    "require",
    "b"
    ], function (require, b) {
    strictEqual("undefined", typeof b);
    aServiceInitialized = true;
    return {
    foo: function () {
    return require("b").foo();
    }
    };
    });

    var bServiceInitialized = false;
    kernel.define("b", ["a"], function (a) {
    bServiceInitialized = true;
    return {
    foo: function () {
    return "bfoo";
    },
    a_foo: function () {
    return a.foo();
    }
    };
    });

    var requireCallbackInvoked = false;
    kernel.require("b", function (b) {
    equal("bfoo", b.foo());
    equal("bfoo", b.a_foo());
    requireCallbackInvoked = true;
    });

    ok(requireCallbackInvoked);
    ok(bServiceInitialized);
    ok(aServiceInitialized);
    });

    test("require service with circular dependency and dependent deferred require succeeds", function () {
    var kernel = new ServiceKernel();

    var aServiceInitialized = false;
    var bRequireCallbackInvoked = false;
    kernel.define("a", ["require", "b"], function (require, b) {

    strictEqual("undefined", typeof b);
    require(["b"], function(b) {

    bRequireCallbackInvoked = true;
    equal("bfoo", b.foo());

    });

    // above require callback can't be invoked until b finished being defined
    ok(!bRequireCallbackInvoked);

    aServiceInitialized = true;
    return {
    foo: function () {
    return require("b").foo();
    }
    };
    });

    var bServiceInitialized = false;
    kernel.define("b", ["a"], function (a) {
    bServiceInitialized = true;
    return {
    foo: function () {
    return "bfoo";
    },
    a_foo: function () {
    return a.foo();
    }
    };
    });

    var requireCallbackInvoked = false;
    kernel.require("b", function (b) {
    equal("bfoo", b.foo());
    equal("bfoo", b.a_foo());
    requireCallbackInvoked = true;
    });

    ok(bRequireCallbackInvoked);
    ok(requireCallbackInvoked);
    ok(bServiceInitialized);
    ok(aServiceInitialized);
    });
  3. mmthomas revised this gist Dec 12, 2012. 1 changed file with 255 additions and 0 deletions.
    255 changes: 255 additions & 0 deletions ServiceKernel.Tests.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,255 @@
    // requires QUnit test framework

    module("ServiceKernelTests");

    if (typeof throws === "undefined") throws = raises;

    test("require unregistered service throws", function () {
    var kernel = new ServiceKernel();

    throws(function () {
    kernel.require("a", function (a) {
    ok(false, "should not get here.");
    });
    }, Error);
    });

    test("require 'require' service succeeds", function () {
    var kernel = new ServiceKernel();

    var requireCallbackInvoked = false;
    kernel.require("require", function (require) {
    ok(typeof require === "function");
    require("require", function (require2) {
    deepEqual(require, require2);
    requireCallbackInvoked = true;
    });
    });

    ok(requireCallbackInvoked);
    });

    test("require 'kernel' service succeeds", function () {
    var kernel = new ServiceKernel();

    var requireCallbackInvoked = false;
    kernel.require("kernel", function (kernel2) {
    deepEqual(kernel, kernel2);
    requireCallbackInvoked = true;
    });

    ok(requireCallbackInvoked);
    });

    test("undefine existing service returns true", function () {
    var kernel = new ServiceKernel();
    ok(kernel.undefine("require"));
    ok(kernel.undefine("kernel"));
    });

    test("require undefined service throws", function () {
    var kernel = new ServiceKernel();

    kernel.undefine("require");
    throws(function () {
    kernel.require("require", function () {
    ok(false, "should not get here.");
    });
    }, Error);

    kernel.undefine("kernel");
    throws(function () {
    kernel.require("kernel", function () {
    ok(false, "should not get here.");
    });
    }, Error);
    });

    test("undefine non-existent service returns false", function () {
    var kernel = new ServiceKernel();
    ok(!kernel.undefine("foo"));
    });

    test("define existing service throws", function () {
    var kernel = new ServiceKernel();

    throws(function () {
    kernel.define("require", function () {
    });
    }, Error);
    throws(function () {
    kernel.define("kernel", function () {
    });
    }, Error);

    var service = defineService(function () {
    return this;
    });

    throws(function () {
    kernel.define("require", service);
    }, Error);
    throws(function () {
    kernel.define("kernel", service);
    }, Error);
    });

    test("require service instance succeeds", function () {
    var kernel = new ServiceKernel();

    var a = {
    foo: 0
    };
    kernel.defineInstance("a", a);

    var requireCallbackInvoked = false;
    kernel.require("a", function (a2) {
    deepEqual(a, a2);
    requireCallbackInvoked = true;
    });

    ok(requireCallbackInvoked);
    });

    test("require service succeeds", function () {
    var kernel = new ServiceKernel();

    kernel.define("a", function () {
    return function () {
    return "a";
    }
    });

    var requireCallbackInvoked = false;
    kernel.require("a", function (a) {
    equal("a", a());
    requireCallbackInvoked = true;
    });

    ok(requireCallbackInvoked);
    });

    test("require service with dependency succeeds", function () {
    var kernel = new ServiceKernel();

    kernel.define("a", function () {
    return {
    foo: function () {
    return "afoo";
    }
    };
    });

    var bServiceInitialized = false;
    kernel.define("b", ["a"], function (a) {
    bServiceInitialized = true;
    return {
    a_foo: function () {
    return a.foo();
    }
    };
    });

    var requireCallbackInvoked = false;
    kernel.require("b", function (b) {
    equal("afoo", b.a_foo());
    requireCallbackInvoked = true;
    });

    ok(requireCallbackInvoked);
    ok(bServiceInitialized);
    });

    test("require service with circular dependency succeeds", function () {
    var kernel = new ServiceKernel();

    var aServiceInitialized = false;
    kernel.define("a", [
    "require",
    "b"
    ], function (require, b) {
    strictEqual("undefined", typeof b);
    aServiceInitialized = true;
    return {
    foo: function () {
    return require("b").foo();
    }
    };
    });

    var bServiceInitialized = false;
    kernel.define("b", ["a"], function (a) {
    bServiceInitialized = true;
    return {
    foo: function () {
    return "bfoo";
    },
    a_foo: function () {
    return a.foo();
    }
    };
    });

    var requireCallbackInvoked = false;
    kernel.require("b", function (b) {
    equal("bfoo", b.foo());
    equal("bfoo", b.a_foo());
    requireCallbackInvoked = true;
    });

    ok(requireCallbackInvoked);
    ok(bServiceInitialized);
    ok(aServiceInitialized);
    });

    test("require service with circular dependency and dependent deferred require succeeds", function () {
    var kernel = new ServiceKernel();

    var aServiceInitialized = false;
    var bRequireCallbackInvoked = false;
    kernel.define("a", ["require", "b"], function (require, b) {

    strictEqual("undefined", typeof b);
    require(["b"], function(b) {

    bRequireCallbackInvoked = true;
    equal("bfoo", b.foo());

    });

    // above require callback can't be invoked until b finished being defined
    ok(!bRequireCallbackInvoked);

    aServiceInitialized = true;
    return {
    foo: function () {
    return require("b").foo();
    }
    };
    });

    var bServiceInitialized = false;
    kernel.define("b", ["a"], function (a) {
    bServiceInitialized = true;
    return {
    foo: function () {
    return "bfoo";
    },
    a_foo: function () {
    return a.foo();
    }
    };
    });

    var requireCallbackInvoked = false;
    kernel.require("b", function (b) {
    equal("bfoo", b.foo());
    equal("bfoo", b.a_foo());
    requireCallbackInvoked = true;
    });

    ok(bRequireCallbackInvoked);
    ok(requireCallbackInvoked);
    ok(bServiceInitialized);
    ok(aServiceInitialized);
    });
  4. mmthomas revised this gist Dec 12, 2012. 1 changed file with 0 additions and 255 deletions.
    255 changes: 0 additions & 255 deletions ServiceKernel.Test.js
    Original file line number Diff line number Diff line change
    @@ -1,255 +0,0 @@
    // requires QUnit test framework

    module("ServiceKernelTests");

    if (typeof throws === "undefined") throws = raises;

    test("require unregistered service throws", function () {
    var kernel = new ServiceKernel();

    throws(function () {
    kernel.require("a", function (a) {
    ok(false, "should not get here.");
    });
    }, Error);
    });

    test("require 'require' service succeeds", function () {
    var kernel = new ServiceKernel();

    var requireCallbackInvoked = false;
    kernel.require("require", function (require) {
    ok(typeof require === "function");
    require("require", function (require2) {
    deepEqual(require, require2);
    requireCallbackInvoked = true;
    });
    });

    ok(requireCallbackInvoked);
    });

    test("require 'kernel' service succeeds", function () {
    var kernel = new ServiceKernel();

    var requireCallbackInvoked = false;
    kernel.require("kernel", function (kernel2) {
    deepEqual(kernel, kernel2);
    requireCallbackInvoked = true;
    });

    ok(requireCallbackInvoked);
    });

    test("undefine existing service returns true", function () {
    var kernel = new ServiceKernel();
    ok(kernel.undefine("require"));
    ok(kernel.undefine("kernel"));
    });

    test("require undefined service throws", function () {
    var kernel = new ServiceKernel();

    kernel.undefine("require");
    throws(function () {
    kernel.require("require", function () {
    ok(false, "should not get here.");
    });
    }, Error);

    kernel.undefine("kernel");
    throws(function () {
    kernel.require("kernel", function () {
    ok(false, "should not get here.");
    });
    }, Error);
    });

    test("undefine non-existent service returns false", function () {
    var kernel = new ServiceKernel();
    ok(!kernel.undefine("foo"));
    });

    test("define existing service throws", function () {
    var kernel = new ServiceKernel();

    throws(function () {
    kernel.define("require", function () {
    });
    }, Error);
    throws(function () {
    kernel.define("kernel", function () {
    });
    }, Error);

    var service = defineService(function () {
    return this;
    });

    throws(function () {
    kernel.define("require", service);
    }, Error);
    throws(function () {
    kernel.define("kernel", service);
    }, Error);
    });

    test("require service instance succeeds", function () {
    var kernel = new ServiceKernel();

    var a = {
    foo: 0
    };
    kernel.defineInstance("a", a);

    var requireCallbackInvoked = false;
    kernel.require("a", function (a2) {
    deepEqual(a, a2);
    requireCallbackInvoked = true;
    });

    ok(requireCallbackInvoked);
    });

    test("require service succeeds", function () {
    var kernel = new ServiceKernel();

    kernel.define("a", function () {
    return function () {
    return "a";
    }
    });

    var requireCallbackInvoked = false;
    kernel.require("a", function (a) {
    equal("a", a());
    requireCallbackInvoked = true;
    });

    ok(requireCallbackInvoked);
    });

    test("require service with dependency succeeds", function () {
    var kernel = new ServiceKernel();

    kernel.define("a", function () {
    return {
    foo: function () {
    return "afoo";
    }
    };
    });

    var bServiceInitialized = false;
    kernel.define("b", ["a"], function (a) {
    bServiceInitialized = true;
    return {
    a_foo: function () {
    return a.foo();
    }
    };
    });

    var requireCallbackInvoked = false;
    kernel.require("b", function (b) {
    equal("afoo", b.a_foo());
    requireCallbackInvoked = true;
    });

    ok(requireCallbackInvoked);
    ok(bServiceInitialized);
    });

    test("require service with circular dependency succeeds", function () {
    var kernel = new ServiceKernel();

    var aServiceInitialized = false;
    kernel.define("a", [
    "require",
    "b"
    ], function (require, b) {
    strictEqual("undefined", typeof b);
    aServiceInitialized = true;
    return {
    foo: function () {
    return require("b").foo();
    }
    };
    });

    var bServiceInitialized = false;
    kernel.define("b", ["a"], function (a) {
    bServiceInitialized = true;
    return {
    foo: function () {
    return "bfoo";
    },
    a_foo: function () {
    return a.foo();
    }
    };
    });

    var requireCallbackInvoked = false;
    kernel.require("b", function (b) {
    equal("bfoo", b.foo());
    equal("bfoo", b.a_foo());
    requireCallbackInvoked = true;
    });

    ok(requireCallbackInvoked);
    ok(bServiceInitialized);
    ok(aServiceInitialized);
    });

    test("require service with circular dependency and dependent deferred require succeeds", function () {
    var kernel = new ServiceKernel();

    var aServiceInitialized = false;
    var bRequireCallbackInvoked = false;
    kernel.define("a", ["require", "b"], function (require, b) {

    strictEqual("undefined", typeof b);
    require(["b"], function(b) {

    bRequireCallbackInvoked = true;
    equal("bfoo", b.foo());

    });

    // above require callback can't be invoked until b finished being defined
    ok(!bRequireCallbackInvoked);

    aServiceInitialized = true;
    return {
    foo: function () {
    return require("b").foo();
    }
    };
    });

    var bServiceInitialized = false;
    kernel.define("b", ["a"], function (a) {
    bServiceInitialized = true;
    return {
    foo: function () {
    return "bfoo";
    },
    a_foo: function () {
    return a.foo();
    }
    };
    });

    var requireCallbackInvoked = false;
    kernel.require("b", function (b) {
    equal("bfoo", b.foo());
    equal("bfoo", b.a_foo());
    requireCallbackInvoked = true;
    });

    ok(bRequireCallbackInvoked);
    ok(requireCallbackInvoked);
    ok(bServiceInitialized);
    ok(aServiceInitialized);
    });
  5. mmthomas revised this gist Nov 23, 2012. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions ServiceKernel.js
    Original file line number Diff line number Diff line change
    @@ -3,6 +3,8 @@
    * A simple JavaScript dependency injection container
    * By Monroe Thomas http://blog.coolmuse.com
    *
    * http://blog.coolmuse.com/2012/11/11/a-simple-javascript-dependency-injection-container/
    *
    * MIT Licensed.
    *
    */
  6. mmthomas revised this gist Nov 16, 2012. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions ServiceKernel.js
    Original file line number Diff line number Diff line change
    @@ -101,9 +101,9 @@ function ServiceKernel() {

    // call the service constructor
    function ConstructorThunk() {
    return service.apply(this, arguments[0]);
    return definition.apply(this, arguments[0]);
    }
    ConstructorThunk.prototype = service.prototype;
    ConstructorThunk.prototype = definition.prototype;
    instance = new ConstructorThunk(dependencies);

    instances[identity] = instance;
  7. mmthomas revised this gist Nov 16, 2012. 1 changed file with 4 additions and 3 deletions.
    7 changes: 4 additions & 3 deletions ServiceKernel.js
    Original file line number Diff line number Diff line change
    @@ -100,10 +100,11 @@ function ServiceKernel() {
    }

    // call the service constructor
    instance = definition.apply(definition, dependencies);
    if (typeof instance === "undefined") {
    throw new Error("The constructor for service '" + identity + "' did not return a value.");
    function ConstructorThunk() {
    return service.apply(this, arguments[0]);
    }
    ConstructorThunk.prototype = service.prototype;
    instance = new ConstructorThunk(dependencies);

    instances[identity] = instance;

  8. mmthomas created this gist Nov 16, 2012.
    255 changes: 255 additions & 0 deletions ServiceKernel.Test.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,255 @@
    // requires QUnit test framework

    module("ServiceKernelTests");

    if (typeof throws === "undefined") throws = raises;

    test("require unregistered service throws", function () {
    var kernel = new ServiceKernel();

    throws(function () {
    kernel.require("a", function (a) {
    ok(false, "should not get here.");
    });
    }, Error);
    });

    test("require 'require' service succeeds", function () {
    var kernel = new ServiceKernel();

    var requireCallbackInvoked = false;
    kernel.require("require", function (require) {
    ok(typeof require === "function");
    require("require", function (require2) {
    deepEqual(require, require2);
    requireCallbackInvoked = true;
    });
    });

    ok(requireCallbackInvoked);
    });

    test("require 'kernel' service succeeds", function () {
    var kernel = new ServiceKernel();

    var requireCallbackInvoked = false;
    kernel.require("kernel", function (kernel2) {
    deepEqual(kernel, kernel2);
    requireCallbackInvoked = true;
    });

    ok(requireCallbackInvoked);
    });

    test("undefine existing service returns true", function () {
    var kernel = new ServiceKernel();
    ok(kernel.undefine("require"));
    ok(kernel.undefine("kernel"));
    });

    test("require undefined service throws", function () {
    var kernel = new ServiceKernel();

    kernel.undefine("require");
    throws(function () {
    kernel.require("require", function () {
    ok(false, "should not get here.");
    });
    }, Error);

    kernel.undefine("kernel");
    throws(function () {
    kernel.require("kernel", function () {
    ok(false, "should not get here.");
    });
    }, Error);
    });

    test("undefine non-existent service returns false", function () {
    var kernel = new ServiceKernel();
    ok(!kernel.undefine("foo"));
    });

    test("define existing service throws", function () {
    var kernel = new ServiceKernel();

    throws(function () {
    kernel.define("require", function () {
    });
    }, Error);
    throws(function () {
    kernel.define("kernel", function () {
    });
    }, Error);

    var service = defineService(function () {
    return this;
    });

    throws(function () {
    kernel.define("require", service);
    }, Error);
    throws(function () {
    kernel.define("kernel", service);
    }, Error);
    });

    test("require service instance succeeds", function () {
    var kernel = new ServiceKernel();

    var a = {
    foo: 0
    };
    kernel.defineInstance("a", a);

    var requireCallbackInvoked = false;
    kernel.require("a", function (a2) {
    deepEqual(a, a2);
    requireCallbackInvoked = true;
    });

    ok(requireCallbackInvoked);
    });

    test("require service succeeds", function () {
    var kernel = new ServiceKernel();

    kernel.define("a", function () {
    return function () {
    return "a";
    }
    });

    var requireCallbackInvoked = false;
    kernel.require("a", function (a) {
    equal("a", a());
    requireCallbackInvoked = true;
    });

    ok(requireCallbackInvoked);
    });

    test("require service with dependency succeeds", function () {
    var kernel = new ServiceKernel();

    kernel.define("a", function () {
    return {
    foo: function () {
    return "afoo";
    }
    };
    });

    var bServiceInitialized = false;
    kernel.define("b", ["a"], function (a) {
    bServiceInitialized = true;
    return {
    a_foo: function () {
    return a.foo();
    }
    };
    });

    var requireCallbackInvoked = false;
    kernel.require("b", function (b) {
    equal("afoo", b.a_foo());
    requireCallbackInvoked = true;
    });

    ok(requireCallbackInvoked);
    ok(bServiceInitialized);
    });

    test("require service with circular dependency succeeds", function () {
    var kernel = new ServiceKernel();

    var aServiceInitialized = false;
    kernel.define("a", [
    "require",
    "b"
    ], function (require, b) {
    strictEqual("undefined", typeof b);
    aServiceInitialized = true;
    return {
    foo: function () {
    return require("b").foo();
    }
    };
    });

    var bServiceInitialized = false;
    kernel.define("b", ["a"], function (a) {
    bServiceInitialized = true;
    return {
    foo: function () {
    return "bfoo";
    },
    a_foo: function () {
    return a.foo();
    }
    };
    });

    var requireCallbackInvoked = false;
    kernel.require("b", function (b) {
    equal("bfoo", b.foo());
    equal("bfoo", b.a_foo());
    requireCallbackInvoked = true;
    });

    ok(requireCallbackInvoked);
    ok(bServiceInitialized);
    ok(aServiceInitialized);
    });

    test("require service with circular dependency and dependent deferred require succeeds", function () {
    var kernel = new ServiceKernel();

    var aServiceInitialized = false;
    var bRequireCallbackInvoked = false;
    kernel.define("a", ["require", "b"], function (require, b) {

    strictEqual("undefined", typeof b);
    require(["b"], function(b) {

    bRequireCallbackInvoked = true;
    equal("bfoo", b.foo());

    });

    // above require callback can't be invoked until b finished being defined
    ok(!bRequireCallbackInvoked);

    aServiceInitialized = true;
    return {
    foo: function () {
    return require("b").foo();
    }
    };
    });

    var bServiceInitialized = false;
    kernel.define("b", ["a"], function (a) {
    bServiceInitialized = true;
    return {
    foo: function () {
    return "bfoo";
    },
    a_foo: function () {
    return a.foo();
    }
    };
    });

    var requireCallbackInvoked = false;
    kernel.require("b", function (b) {
    equal("bfoo", b.foo());
    equal("bfoo", b.a_foo());
    requireCallbackInvoked = true;
    });

    ok(bRequireCallbackInvoked);
    ok(requireCallbackInvoked);
    ok(bServiceInitialized);
    ok(aServiceInitialized);
    });
    352 changes: 352 additions & 0 deletions ServiceKernel.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,352 @@
    /*
    *
    * A simple JavaScript dependency injection container
    * By Monroe Thomas http://blog.coolmuse.com
    *
    * MIT Licensed.
    *
    */

    /**
    * Defines a service by annotating a service constructor function with an array of
    * service identities
    * @param {Function|String|Array} identitiesOrConstructor The identities of service dependencies,
    * or the service constructor if no dependencies exist.
    * @param {Function} [serviceConstructor] The service constructor.
    * @return {Function} The annotated service constructor function.
    */
    function defineService(identitiesOrConstructor, serviceConstructor) {

    if (typeof identitiesOrConstructor === "function") {

    serviceConstructor = identitiesOrConstructor;
    if (typeof serviceConstructor.dependencyIdentities !== "undefined") {
    return serviceConstructor;
    }

    identitiesOrConstructor = [];

    } else if (typeof identitiesOrConstructor === "string") {

    identitiesOrConstructor = [identitiesOrConstructor]; // wrap in an array

    }

    if (!Array.isArray(identitiesOrConstructor)) throw new Error("identitiesOrConstructor must be an array.");
    if (typeof serviceConstructor !== "function") throw new Error("serviceConstructor must be a function.");

    // annotate the constructor with the dependency identity array
    serviceConstructor.dependencyIdentities = identitiesOrConstructor;
    return serviceConstructor;

    }

    /**
    * Returns a service kernel.
    * @constructor
    */
    function ServiceKernel() {

    var instances = {};
    var definitions = {};
    var beingResolved = {};
    var pendingCallbacks = [];

    /**
    * Returns the service instance corresponding to the specified service identity.
    * @param {String} identity
    * @return {*} The service instance; or undefined if the service is being resolved.
    */
    function getInstance(identity) {

    if (identity in beingResolved) return undefined;
    if (identity in instances) return instances[identity];
    if (identity in definitions) return resolveInstance(identity);

    throw new Error("The service '" + identity + "' has not been defined.", "identity");
    }

    /**
    * Resolves the service instance corresponding to the specified service identity.
    * @param {String} identity
    * @return {*} The service instance.
    */
    function resolveInstance(identity) {

    if (identity in beingResolved) {
    throw new Error("resolveInstance is already being called for the service '" + identity + "'.");
    }

    var instance;

    try {
    beingResolved[identity] = true;

    var definition = definitions[identity];

    // gather the service constructor arguments
    var dependencies = [];
    if (definition.dependencyIdentities && Array.isArray(definition.dependencyIdentities)) {

    for (var i = 0; i < definition.dependencyIdentities.length; i++) {

    // recursively resolve service dependency;
    // may be undefined in case of a circular dependency
    instance = getInstance(definition.dependencyIdentities[i]);
    dependencies.push(instance);

    }

    }

    // call the service constructor
    instance = definition.apply(definition, dependencies);
    if (typeof instance === "undefined") {
    throw new Error("The constructor for service '" + identity + "' did not return a value.");
    }

    instances[identity] = instance;

    } finally {
    delete beingResolved[identity];
    }

    // resolve any pending require calls that may need this instance
    resolvePending(identity, instance);

    return instance;
    }

    /**
    * Checks if any pending require callbacks can be completed with the specified service.
    * @param {String} identity The resolved service identity.
    * @param {*} instance The resolved service instance.
    */
    function resolvePending(identity, instance) {
    if (pendingCallbacks.length === 0)
    return;

    var resolved, i;
    for (i = 0; i < pendingCallbacks.length; i++) {
    if (pendingCallbacks[i].resolve(identity, instance)) {
    resolved = resolved || [];
    resolved.push(i);
    }
    }

    if (resolved) {
    for (i = 0; i < resolved.length; i++) {
    pendingCallbacks.splice(resolved[i], 1);
    }
    }
    }

    /**
    * Returns an object with a resolve function that can be called when a new service instance is created;
    * the resolve function will return true if all dependencies have been satisfied
    * and the callback method has been invoked; otherwise it will return false
    * @param {Array} identities An array of service identity strings.
    * @param {Array} dependencies An array of dependencies; unresolved dependencies have a value of undefined.
    * @param {Number} pending The number of unresolved dependencies.
    * @param {Function} callback The callback to invoke when all dependencies are resolved.
    * @return {Object} An object containing a resolve function.
    * @constructor
    */
    function PendingCallback(identities, dependencies, pending, callback) {

    if (!Array.isArray(identities)) throw new Error("identities must be an array.");
    if (!Array.isArray(dependencies)) throw new Error("dependencies must be an array.");
    if (typeof pending !== "number") throw new Error("pending must be a number.");
    if (typeof callback !== "function") throw new Error("callback must be a function.");

    if (pending <= 0) throw new Error("pending must be positive.");

    /**
    * Checks if the specified service resolves the callback criteria.
    * @param {String} identity The service identity to resolve.
    * @param {*} instance The resolved service instance.
    * @return {Boolean} True if all dependencies are resolved; otherwise false.
    */
    function resolve (identity, instance) {

    var index = identities.indexOf(identity);
    if (index === -1)
    return false;

    dependencies[index] = instance;
    if (0 === --pending) {
    callback.apply({}, dependencies);
    return true;
    }
    return false;

    }

    return {

    /**
    * Checks if the specified service resolves the callback criteria.
    * @param {String} identity The service identity to resolve.
    * @param {*} instance The resolved service instance.
    * @return {Boolean} True if all dependencies are resolved; otherwise false.
    */
    resolve : resolve
    }

    }

    /**
    * Defines a service within the kernel.
    * @param {String} identity The service identity.
    * @param {Function|String|Array} dependencyIdentitiesOrConstructor
    * The identities of service dependencies,
    * or the service constructor if no dependencies exist.
    * @param {Function} [serviceConstructor] The service constructor.
    */
    function define(identity, dependencyIdentitiesOrConstructor, serviceConstructor) {

    if (typeof identity !== "string") throw new Error("identity must be a string.");
    if (identity.length === 0) throw new Error("The identity string may not be empty.");

    if (identity in definitions) {
    throw new Error("The service '" + identity + "' has already been defined.");
    }

    var definition = defineService(dependencyIdentitiesOrConstructor, serviceConstructor);
    definitions[identity] = definition;

    }

    /**
    * Defines a service within the kernel based on an existing instance.
    * Equivalent to calling define(instance, function() { return instance; });
    * @param {String} identity The service identity.
    * @param {*} instance The service instance.
    */
    function defineInstance(identity, instance) {

    this.define(identity, function() { return instance; });

    }

    /**
    * Undefines a service.
    * @param {String} identity The service identity.
    * @return {Boolean} Returns true if the service was undefined; false otherwise.
    */
    function undefine(identity) {

    if (typeof identity !== "string") throw new Error("identity must be a string.");

    if (identity in definitions) {
    delete definitions[identity];
    if (identity in instances) {
    delete instances[identity];
    }
    return true;
    }

    return false;
    }

    /**
    * Returns one or more services. Has similar semantics to the AMD require() method.
    * @param {String|Array} identities The identities of the services required by the callback.
    * If callback is not specified, then this must be a string.
    * @param {Function} [callback] The callback to invoke with the required service instances.
    * If this is not specified, then identities must be a string,
    * and the required instance is returned.
    * @return {*} Returns the specified service instance if no callback is specified;
    * otherwise returns void.
    */
    function require(identities, callback) {

    // synchronous version
    if (typeof callback === "undefined") {

    if (typeof identities !== "string") throw new Error("identities must be a string when no callback is specified.");

    var instance = getInstance(identities);
    if (typeof instance === "undefined") {
    throw new Error("The service '" + identities + "' has not been defined.");
    }

    return instance;
    }

    if (typeof identities === "string") {
    identities = [identities]; // wrap in an array
    }

    if (!Array.isArray(identities)) throw new Error("identities must be an array.");
    if (typeof callback !== "function") throw new Error("callback must be a function.");

    // gather callback arguments
    var dependencies = [];
    var pending = 0;
    for (var i = 0; i < identities.length; i++) {

    var instance = getInstance(identities[i]);
    dependencies.push(instance);

    if (typeof instance === "undefined") {
    pending++;
    }
    }

    if (pending > 0) {
    pendingCallbacks.push(PendingCallback(identities, dependencies, pending, callback));
    } else {
    callback.apply({}, dependencies);
    }
    }

    // create the object that contains the kernel methods
    var kernel = {

    /**
    * Defines a service within the kernel.
    * @param {String} identity The service identity.
    * @param {Function|String|Array} dependencyIdentitiesOrConstructor
    * The identities of service dependencies,
    * or the service constructor if no dependencies exist.
    * @param {Function} [serviceConstructor] The service constructor.
    */
    define: define,

    /**
    * Defines a service within the kernel based on an existing instance.
    * Equivalent to calling define(instance, function() { return instance; });
    * @param {String} identity The service identity.
    * @param {*} instance The service instance.
    */
    defineInstance: defineInstance,

    /**
    * Undefines a service.
    * @param {String} identity The service identity.
    * @return {Boolean} Returns true if the service was undefined; false otherwise.
    */
    undefine : undefine,

    /**
    * Returns one or more services. Has similar semantics to the AMD require() method.
    * @param {String|Array} identities The identities of the services required by the callback.
    * If callback is not specified, then this must be a string.
    * @param {Function} [callback] The callback to invoke with the required service instances.
    * If this is not specified, then identities must be a string,
    * and the required instance is returned.
    * @return {*} Returns the specified service instance if no callback is specified;
    * otherwise returns void.
    */
    require : require
    }

    // define the kernel itself and its require method as services
    kernel.defineInstance("kernel", kernel);
    kernel.defineInstance("require", require.bind(this));

    return kernel;
    }