Created
November 16, 2012 17:12
-
-
Save mmthomas/4089076 to your computer and use it in GitHub Desktop.
Revisions
-
mmthomas revised this gist
Dec 12, 2012 . 1 changed file with 2 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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 * */ /** -
mmthomas revised this gist
Dec 12, 2012 . 1 changed file with 0 additions and 255 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,255 +0,0 @@ -
mmthomas revised this gist
Dec 12, 2012 . 1 changed file with 255 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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); }); -
mmthomas revised this gist
Dec 12, 2012 . 1 changed file with 0 additions and 255 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,255 +0,0 @@ -
mmthomas revised this gist
Nov 23, 2012 . 1 changed file with 2 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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. * */ -
mmthomas revised this gist
Nov 16, 2012 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -101,9 +101,9 @@ function ServiceKernel() { // call the service constructor function ConstructorThunk() { return definition.apply(this, arguments[0]); } ConstructorThunk.prototype = definition.prototype; instance = new ConstructorThunk(dependencies); instances[identity] = instance; -
mmthomas revised this gist
Nov 16, 2012 . 1 changed file with 4 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -100,10 +100,11 @@ function ServiceKernel() { } // call the service constructor function ConstructorThunk() { return service.apply(this, arguments[0]); } ConstructorThunk.prototype = service.prototype; instance = new ConstructorThunk(dependencies); instances[identity] = instance; -
mmthomas created this gist
Nov 16, 2012 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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); }); This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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; }