UNPKG

awilix

Version:

Extremely powerful dependency injection container.

444 lines 17.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.createContainer = createContainer; const util = __importStar(require("util")); const errors_1 = require("./errors"); const injection_mode_1 = require("./injection-mode"); const lifetime_1 = require("./lifetime"); const list_modules_1 = require("./list-modules"); const load_module_native_js_1 = require("./load-module-native.js"); const load_modules_1 = require("./load-modules"); const resolvers_1 = require("./resolvers"); const utils_1 = require("./utils"); /** * 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'. * * @param {boolean} options.strict True if the container should run in strict mode with additional * validation for resolver configuration correctness. Defaults to false. * * @return {AwilixContainer<T>} The container. */ function createContainer(options = {}) { return createContainerInternal(options); } function createContainerInternal(options, parentContainer, parentResolutionStack) { options = { injectionMode: injection_mode_1.InjectionMode.PROXY, strict: false, ...options, }; /** * Tracks the names and lifetimes of the modules being resolved. Used to detect circular * dependencies and, in strict mode, lifetime leakage issues. */ const resolutionStack = parentResolutionStack ?? []; // 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, [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 { ...(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 createContainerInternal(options, container, resolutionStack); } /** * 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 resolver = obj[key]; // If strict mode is enabled, check to ensure we are not registering a singleton on a non-root // container. if (options.strict && resolver.lifetime === lifetime_1.Lifetime.SINGLETON) { if (parentContainer) { throw new errors_1.AwilixRegistrationError(key, 'Cannot register a singleton on a scoped container.'); } } registrations[key] = resolver; } 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.some(({ name: parentName }) => parentName === name)) { 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); } const lifetime = resolver.lifetime || lifetime_1.Lifetime.TRANSIENT; // if we are running in strict mode, this resolver is not explicitly marked leak-safe, and any // of the parents have a shorter lifetime than the one requested, throw an error. if (options.strict && !resolver.isLeakSafe) { const maybeLongerLifetimeParentIndex = resolutionStack.findIndex(({ lifetime: parentLifetime }) => (0, lifetime_1.isLifetimeLonger)(parentLifetime, lifetime)); if (maybeLongerLifetimeParentIndex > -1) { throw new errors_1.AwilixResolutionError(name, resolutionStack, `Dependency '${name.toString()}' has a shorter lifetime than its ancestor: '${resolutionStack[maybeLongerLifetimeParentIndex].name.toString()}'`); } } // Pushes the currently-resolving module information onto the stack resolutionStack.push({ name, lifetime }); // Do the thing let cached; let resolved; switch (lifetime) { 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) { // if we are running in strict mode, perform singleton resolution using the root // container only. resolved = resolver.resolve(options.strict ? rootContainer : 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. // If this container cache does not have it, // resolve and cache it rather than using the parent // container's cache. 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. Mutate the existing array rather than // updating the reference to ensure all parent containers' stacks are also updated. resolutionStack.length = 0; 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|Constructor|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) { // eslint-disable-next-line @typescript-eslint/no-require-imports return require(uri); }, listModules: list_modules_1.listModules, container, }; if (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); } } //# sourceMappingURL=container.js.map