UNPKG

awilix

Version:

Extremely powerful dependency injection container.

381 lines 14.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createContainer = void 0; const util = require("util"); const list_modules_1 = require("./list-modules"); const load_modules_1 = require("./load-modules"); const resolvers_1 = require("./resolvers"); const utils_1 = require("./utils"); const injection_mode_1 = require("./injection-mode"); const lifetime_1 = require("./lifetime"); const errors_1 = require("./errors"); const load_module_native_js_1 = require("./load-module-native.js"); /** * Family tree symbol. */ const FAMILY_TREE = Symbol('familyTree'); /** * Roll Up Registrations symbol. */ const ROLL_UP_REGISTRATIONS = Symbol('rollUpRegistrations'); /** * The string representation when calling toString. */ const CRADLE_STRING_TAG = 'AwilixContainerCradle'; /** * Creates an Awilix container instance. * * @param {Function} options.require * The require function to use. Defaults to require. * * @param {string} options.injectionMode * The mode used by the container to resolve dependencies. Defaults to 'Proxy'. * * @return {AwilixContainer<T>} * The container. */ function createContainer(options, parentContainer) { options = Object.assign({ injectionMode: injection_mode_1.InjectionMode.PROXY }, options); // The resolution stack is used to keep track // of what modules are being resolved, so when // an error occurs, we have something to present // to the poor developer who fucked up. let resolutionStack = []; // Internal registration store for this container. const registrations = {}; /** * The `Proxy` that is passed to functions so they can resolve their dependencies without * knowing where they come from. I call it the "cradle" because * it is where registered things come to life at resolution-time. */ const cradle = new Proxy({ [util.inspect.custom]: toStringRepresentationFn, }, { /** * The `get` handler is invoked whenever a get-call for `container.cradle.*` is made. * * @param {object} _target * The proxy target. Irrelevant. * * @param {string} name * The property name. * * @return {*} * Whatever the resolve call returns. */ get: (_target, name) => resolve(name), /** * Setting things on the cradle throws an error. * * @param {object} target * @param {string} name */ set: (_target, name) => { throw new Error(`Attempted setting property "${name}" on container cradle - this is not allowed.`); }, /** * Used for `Object.keys`. */ ownKeys() { return Array.from(cradle); }, /** * Used for `Object.keys`. */ getOwnPropertyDescriptor(target, key) { const regs = rollUpRegistrations(); if (Object.getOwnPropertyDescriptor(regs, key)) { return { enumerable: true, configurable: true, }; } return undefined; }, }); // The container being exposed. const container = { options, cradle, inspect, cache: new Map(), loadModules, createScope, register: register, build, resolve, hasRegistration, dispose, getRegistration, [util.inspect.custom]: inspect, // tslint:disable-next-line [ROLL_UP_REGISTRATIONS]: rollUpRegistrations, get registrations() { return rollUpRegistrations(); }, }; // Track the family tree. const familyTree = parentContainer ? [container].concat(parentContainer[FAMILY_TREE]) : [container]; container[FAMILY_TREE] = familyTree; // We need a reference to the root container, // so we can retrieve and store singletons. const rootContainer = (0, utils_1.last)(familyTree); return container; /** * Used by util.inspect (which is used by console.log). */ function inspect() { return `[AwilixContainer (${parentContainer ? 'scoped, ' : ''}registrations: ${Object.keys(container.registrations).length})]`; } /** * Rolls up registrations from the family tree. * * This can get pretty expensive. Only used when * iterating the cradle proxy, which is not something * that should be done in day-to-day use, mostly for debugging. * * @param {boolean} bustCache * Forces a recomputation. * * @return {object} * The merged registrations object. */ function rollUpRegistrations() { return Object.assign(Object.assign({}, (parentContainer && parentContainer[ROLL_UP_REGISTRATIONS]())), registrations); } /** * Used for providing an iterator to the cradle. */ function* cradleIterator() { const registrations = rollUpRegistrations(); for (const registrationName in registrations) { yield registrationName; } } /** * Creates a scoped container. * * @return {object} * The scoped container. */ function createScope() { return createContainer(options, container); } /** * Adds a registration for a resolver. */ function register(arg1, arg2) { const obj = (0, utils_1.nameValueToObject)(arg1, arg2); const keys = [...Object.keys(obj), ...Object.getOwnPropertySymbols(obj)]; for (const key of keys) { const value = obj[key]; registrations[key] = value; } return container; } /** * Returned to `util.inspect` and Symbol.toStringTag when attempting to resolve * a custom inspector function on the cradle. */ function toStringRepresentationFn() { return Object.prototype.toString.call(cradle); } /** * Recursively gets a registration by name if it exists in the * current container or any of its' parents. * * @param name {string | symbol} The registration name. */ function getRegistration(name) { const resolver = registrations[name]; if (resolver) { return resolver; } if (parentContainer) { return parentContainer.getRegistration(name); } return null; } /** * Resolves the registration with the given name. * * @param {string | symbol} name * The name of the registration to resolve. * * @param {ResolveOptions} resolveOpts * The resolve options. * * @return {any} * Whatever was resolved. */ function resolve(name, resolveOpts) { resolveOpts = resolveOpts || {}; try { // Grab the registration by name. const resolver = getRegistration(name); if (resolutionStack.indexOf(name) > -1) { throw new errors_1.AwilixResolutionError(name, resolutionStack, 'Cyclic dependencies detected.'); } // Used in JSON.stringify. if (name === 'toJSON') { return toStringRepresentationFn; } // Used in console.log. if (name === 'constructor') { return createContainer; } if (!resolver) { // Checks for some edge cases. switch (name) { // The following checks ensure that console.log on the cradle does not // throw an error (issue #7). case util.inspect.custom: case 'inspect': case 'toString': return toStringRepresentationFn; case Symbol.toStringTag: return CRADLE_STRING_TAG; // Edge case: Promise unwrapping will look for a "then" property and attempt to call it. // Return undefined so that we won't cause a resolution error. (issue #109) case 'then': return undefined; // When using `Array.from` or spreading the cradle, this will // return the registration names. case Symbol.iterator: return cradleIterator; } if (resolveOpts.allowUnregistered) { return undefined; } throw new errors_1.AwilixResolutionError(name, resolutionStack); } // Pushes the currently-resolving module name onto the stack resolutionStack.push(name); // Do the thing let cached; let resolved; switch (resolver.lifetime || lifetime_1.Lifetime.TRANSIENT) { case lifetime_1.Lifetime.TRANSIENT: // Transient lifetime means resolve every time. resolved = resolver.resolve(container); break; case lifetime_1.Lifetime.SINGLETON: // Singleton lifetime means cache at all times, regardless of scope. cached = rootContainer.cache.get(name); if (!cached) { resolved = resolver.resolve(container); rootContainer.cache.set(name, { resolver, value: resolved }); } else { resolved = cached.value; } break; case lifetime_1.Lifetime.SCOPED: // Scoped lifetime means that the container // that resolves the registration also caches it. // When a registration is not found, we travel up // the family tree until we find one that is cached. cached = container.cache.get(name); if (cached !== undefined) { // We found one! resolved = cached.value; break; } // If we still have not found one, we need to resolve and cache it. resolved = resolver.resolve(container); container.cache.set(name, { resolver, value: resolved }); break; default: throw new errors_1.AwilixResolutionError(name, resolutionStack, `Unknown lifetime "${resolver.lifetime}"`); } // Pop it from the stack again, ready for the next resolution resolutionStack.pop(); return resolved; } catch (err) { // When we get an error we need to reset the stack. resolutionStack = []; throw err; } } /** * Checks if the registration with the given name exists. * * @param {string | symbol} name * The name of the registration to resolve. * * @return {boolean} * Whether or not the registration exists. */ function hasRegistration(name) { return !!getRegistration(name); } /** * Given a registration, class or function, builds it up and returns it. * Does not cache it, this means that any lifetime configured in case of passing * a registration will not be used. * * @param {Resolver|Class|Function} targetOrResolver * @param {ResolverOptions} opts */ function build(targetOrResolver, opts) { if (targetOrResolver && targetOrResolver.resolve) { return targetOrResolver.resolve(container); } const funcName = 'build'; const paramName = 'targetOrResolver'; errors_1.AwilixTypeError.assert(targetOrResolver, funcName, paramName, 'a registration, function or class', targetOrResolver); errors_1.AwilixTypeError.assert(typeof targetOrResolver === 'function', funcName, paramName, 'a function or class', targetOrResolver); const resolver = (0, utils_1.isClass)(targetOrResolver) ? (0, resolvers_1.asClass)(targetOrResolver, opts) : (0, resolvers_1.asFunction)(targetOrResolver, opts); return resolver.resolve(container); } /** * Binds `lib/loadModules` to this container, and provides * real implementations of it's dependencies. * * Additionally, any modules using the `dependsOn` API * will be resolved. * * @see lib/loadModules.js documentation. */ function loadModules(globPatterns, opts) { const _loadModulesDeps = { require: options.require || function (uri) { return require(uri); }, listModules: list_modules_1.listModules, container, }; if (opts === null || opts === void 0 ? void 0 : opts.esModules) { _loadModulesDeps.require = load_module_native_js_1.importModule; return (0, load_modules_1.loadModules)(_loadModulesDeps, globPatterns, opts).then(() => container); } else { (0, load_modules_1.loadModules)(_loadModulesDeps, globPatterns, opts); return container; } } /** * Disposes this container and it's children, calling the disposer * on all disposable registrations and clearing the cache. */ function dispose() { const entries = Array.from(container.cache.entries()); container.cache.clear(); return Promise.all(entries.map(([, entry]) => { const { resolver, value } = entry; const disposable = resolver; if (disposable.dispose) { return Promise.resolve().then(() => disposable.dispose(value)); } return Promise.resolve(); })).then(() => undefined); } } exports.createContainer = createContainer; //# sourceMappingURL=container.js.map