UNPKG

b-ioc-js

Version:

A magic free, dirt simple IoC container for Javascript.

120 lines (119 loc) 4.56 kB
/** * IoC Module * @module b-ioc-js */ export * from './types.js'; let bindings = {}; let resolvedBindings = {}; let singletons = {}; let resolvedSingletons = {}; function isString(obj) { return Object.prototype.toString.call(obj) === '[object String]'; } function isObject(obj) { const type = typeof obj; return type === 'function' || type === 'object' && !!obj; } function isFunction(obj) { return typeof obj === 'function' || false; } function inObject(key, obj) { // biome-ignore lint/suspicious/noPrototypeBuiltins: Legacy return obj.hasOwnProperty(key); } /** * Gets all of the current bindings in the container * @return {Bindings} The containers bindings */ export function getBindings() { return bindings; } /** * Gets all of the current singletons in the container * @return {Singletons} The containers singletons */ export function getSingletons() { return singletons; } /** * Resets container to default state */ export function clear() { bindings = {}; resolvedBindings = {}; singletons = {}; resolvedSingletons = {}; } /** * Assigns to our bindings object * @template T * @param {String} binding The name of the IoC binding * @param {Factory<T> | T} closure Factory method or value to bind to container */ export function bind(binding, closure) { if (inObject(binding, bindings) || inObject(binding, singletons)) throw new Error(`Binding: ${binding} already binded.`); if (!isFunction(closure)) throw new Error(`Binding: ${binding} does not implement a factory.`); bindings[binding] = closure; } /** * Assigns to our singleton object * @template T * @param {String} binding The name of the IoC binding * @param {Factory<T> | T} closure Factory method or value to bind to container */ export function singleton(binding, closure) { if (inObject(binding, singletons) || inObject(binding, bindings)) throw new Error(`Singleton: ${binding} already binded.`); singletons[binding] = closure; } /** * Grabs a binding from the IoC. Leverages node require as a fallback * @template T * @param {String} binding The name of the binding in the container * @returns {T} The instance of the binding */ export function use(binding) { // biome-ignore lint/complexity/noArguments: Apply arguments const args = Array.prototype.slice.call(arguments, 1); // first check bindings if (inObject(binding, bindings)) { if (resolvedBindings[binding]) { throw new Error(`Cyclic dependency detected in binding: ${binding}.`); } resolvedBindings[binding] = true; const instance = bindings[binding].apply(null, args); resolvedBindings[binding] = false; return instance; } // then check singletons if (inObject(binding, singletons)) { if (!inObject(binding, resolvedSingletons)) { // we are not guarenteed to receive a factory function for a singleton if (isFunction(singletons[binding])) resolvedSingletons[binding] = singletons[binding].apply(null, args); else resolvedSingletons[binding] = singletons[binding]; } return resolvedSingletons[binding]; } // finally check node_modules throw new Error(`Binding: ${binding} not found.`); } /** * Creates an instance of a class and will inject dependencies defined in static * inject method. This is an alternative to using Ioc.bind * @template T * @param {T} Obj The class you wish to create a new instance of * @return {T} The instantiated function instance */ export function make(Obj) { if (!isFunction(Obj)) throw new Error(`.make implementation error, expected function got: ${typeof Obj}`); if (!Obj.inject) throw new Error(`.make requires ${Obj.constructor.name} to have a static inject method.`); const dependencies = Obj.inject(); if (dependencies.length === 0) return new Obj(); const resolved = []; dependencies.forEach((dependency)=>{ if (!isString(dependency) && !isObject(dependency)) { throw new Error('static .inject implementation error, a string or object is required.'); } // string based binding if (isString(dependency)) resolved.push(exports.use(dependency)); // binding you want to pass args to if (isObject(dependency)) { dependency.args.unshift(dependency.key); resolved.push(exports.use.apply(null, dependency.args)); } }); return new (Function.prototype.bind.apply(Obj, [ null ].concat(resolved)))(); }