UNPKG

@shahen.poghosyan/awilix

Version:

Extremely powerful dependency injection container.

421 lines 13.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const lifetime_1 = require("./lifetime"); const injection_mode_1 = require("./injection-mode"); const utils_1 = require("./utils"); const param_parser_1 = require("./param-parser"); const errors_1 = require("./errors"); /** * RESOLVER symbol can be used by modules loaded by * `loadModules` to configure their lifetime, injection mode, etc. */ exports.RESOLVER = Symbol('Awilix Resolver Config'); /** * Creates a simple value resolver where the given value will always be resolved. * * @param {string} name * The name to register the value as. * * @param {*} value * The value to resolve. * * @return {object} * The resolver. */ function asValue(value) { return { resolve: () => value }; } exports.asValue = asValue; /** * Creates a factory resolver, where the given factory function * will be invoked with `new` when requested. * * @param {string} name * The name to register the value as. * * @param {Function} fn * The function to register. * * @param {object} opts * Additional options for the resolver. * * @return {object} * The resolver. */ function asFunction(fn, opts) { if (!utils_1.isFunction(fn)) { throw new errors_1.AwilixTypeError('asFunction', 'fn', 'function', fn); } const defaults = { lifetime: lifetime_1.Lifetime.TRANSIENT }; opts = makeOptions(defaults, opts, fn[exports.RESOLVER]); const resolve = generateResolve(fn); let result = Object.assign({ resolve }, opts); return createDisposableResolver(createBuildResolver(result)); } exports.asFunction = asFunction; /** * Like a factory resolver, but for classes that require `new`. * * @param {string} name * The name to register the value as. * * @param {Class} Type * The function to register. * * @param {object} opts * Additional options for the resolver. * * @return {object} * The resolver. */ function asClass(Type, opts) { if (!utils_1.isFunction(Type)) { throw new errors_1.AwilixTypeError('asClass', 'Type', 'class', Type); } const defaults = { lifetime: lifetime_1.Lifetime.TRANSIENT }; opts = makeOptions(defaults, opts, Type[exports.RESOLVER]); // A function to handle object construction for us, as to make the generateResolve more reusable const newClass = function newClass() { return Reflect.construct(Type, arguments); }; const resolve = makeResolveLazy(generateResolve(newClass, Type)); return createDisposableResolver(createBuildResolver(Object.assign({}, opts, { resolve }))); } exports.asClass = asClass; /** * Resolves to the specified registration. */ function aliasTo(name) { return { resolve(container) { return container.resolve(name); } }; } exports.aliasTo = aliasTo; /** * Given an options object, creates a fluid interface * to manage it. * * @param {*} obj * The object to return. * * @return {object} * The interface. */ function createBuildResolver(obj) { function setLifetime(value) { return createBuildResolver(Object.assign({}, this, { lifetime: value })); } function setInjectionMode(value) { return createBuildResolver(Object.assign({}, this, { injectionMode: value })); } function inject(injector) { return createBuildResolver(Object.assign({}, this, { injector })); } return updateResolver(obj, { setLifetime, inject, transient: partial(setLifetime, lifetime_1.Lifetime.TRANSIENT), scoped: partial(setLifetime, lifetime_1.Lifetime.SCOPED), singleton: partial(setLifetime, lifetime_1.Lifetime.SINGLETON), setInjectionMode, proxy: partial(setInjectionMode, injection_mode_1.InjectionMode.PROXY), classic: partial(setInjectionMode, injection_mode_1.InjectionMode.CLASSIC) }); } exports.createBuildResolver = createBuildResolver; /** * Given a resolver, returns an object with methods to manage the disposer * function. * @param obj */ function createDisposableResolver(obj) { function disposer(dispose) { return createDisposableResolver(Object.assign({}, this, { dispose })); } return updateResolver(obj, { disposer }); } exports.createDisposableResolver = createDisposableResolver; /** * Partially apply arguments to the given function. */ function partial(fn, arg1) { return function partiallyApplied() { return fn.call(this, arg1); }; } /** * Makes an options object based on defaults. * * @param {object} defaults * Default options. * * @param {...} rest * The input to check and possibly assign to the resulting object * * @return {object} */ function makeOptions(defaults, ...rest) { return Object.assign({}, defaults, ...rest); } /** * Creates a new resolver with props merged from both. * * @param source * @param target */ function updateResolver(source, target) { const result = Object.assign({}, source, target); return result; } /** * Returns a wrapped `resolve` function that provides values * from the injector and defers to `container.resolve`. * * @param {AwilixContainer} container * @param {Object} locals * @return {Function} */ function wrapWithLocals(container, locals) { return function wrappedResolve(name, resolveOpts) { if (name in locals) { return locals[name]; } return container.resolve(name, resolveOpts); }; } /** * Returns a new Proxy that checks the result from `injector` * for values before delegating to the actual container. * * @param {Object} cradle * @param {Function} injector * @return {Proxy} */ function createInjectorProxy(container, injector) { const locals = injector(container); const allKeys = utils_1.uniq([ ...Reflect.ownKeys(container.cradle), ...Reflect.ownKeys(locals) ]); // TODO: Lots of duplication here from the container proxy. // Need to refactor. const proxy = new Proxy({}, { /** * Resolves the value by first checking the locals, then the container. */ get(target, name) { if (name === Symbol.iterator) { return function* iterateRegistrationsAndLocals() { for (const prop in container.cradle) { yield prop; } for (const prop in locals) { yield prop; } }; } if (name in locals) { return locals[name]; } return container.resolve(name); }, /** * Used for `Object.keys`. */ ownKeys() { return allKeys; }, /** * Used for `Object.keys`. */ getOwnPropertyDescriptor(target, key) { if (allKeys.indexOf(key) > -1) { return { enumerable: true, configurable: true }; } return undefined; } }); return proxy; } /** * Makes dependency resolution lazy by wrapping the resolved value of original resolve function in a Proxy object * * @this {Registration} * The `this` context is a resolver. * * @param {Function} originalResolve * The original function for dependency resolution * * @return {Function} * The function used for lazy dependency resolution */ function makeResolveLazy(originalResolve) { let resolved = null; return function resolve(container) { let self = this; return new Proxy({}, { construct(_, argArray, newTarget) { if (!resolved) { resolved = originalResolve.call(self, container); } return Reflect.construct(resolved, argArray, newTarget); }, defineProperty(_, p, attributes) { if (!resolved) { resolved = originalResolve.call(self, container); } return Reflect.defineProperty(resolved, p, attributes); }, deleteProperty(_, propertyKey) { if (!resolved) { resolved = originalResolve.call(self, container); } return Reflect.deleteProperty(resolved, propertyKey); }, enumerate(_) { if (!resolved) { resolved = originalResolve.call(self, container); } return Reflect.enumerate(resolved); }, get(_, name) { if (!resolved) { resolved = originalResolve.call(self, container); } return resolved[name]; }, getOwnPropertyDescriptor(_, p) { if (!resolved) { resolved = originalResolve.call(self, container); } return Reflect.getOwnPropertyDescriptor(resolved, p); }, getPrototypeOf() { if (!resolved) { resolved = originalResolve.call(self, container); } return Object.getPrototypeOf(resolved); }, has(_, p) { if (!resolved) { resolved = originalResolve.call(self, container); } return Reflect.has(resolved, p); }, isExtensible() { if (!resolved) { resolved = originalResolve.call(self, container); } return resolved.isExtensible(); }, ownKeys(_) { if (!resolved) { resolved = originalResolve.call(self, container); } return Reflect.ownKeys(resolved); }, preventExtensions(_) { if (!resolved) { resolved = originalResolve.call(self, container); } return Reflect.preventExtensions(resolved); }, set(_, p, value) { if (!resolved) { resolved = originalResolve.call(self, container); } return Reflect.set(resolved, p, value); }, setPrototypeOf(_, proto) { if (!resolved) { resolved = originalResolve.call(self, container); } return Reflect.setPrototypeOf(resolved, proto); } }); }; } /** * Returns a resolve function used to construct the dependency graph * * @this {Registration} * The `this` context is a resolver. * * @param {Function} fn * The function to construct * * @param {Function} dependencyParseTarget * The function to parse for the dependencies of the construction target * * @param {boolean} isFunction * Is the resolution target an actual function or a mask for a constructor? * * @return {Function} * The function used for dependency resolution */ function generateResolve(fn, dependencyParseTarget) { // If the function used for dependency parsing is falsy, use the supplied function if (!dependencyParseTarget) { dependencyParseTarget = fn; } // Parse out the dependencies // NOTE: we do this regardless of whether PROXY is used or not, // because if this fails, we want it to fail early (at startup) rather // than at resolution time. const dependencies = parseDependencies(dependencyParseTarget); // Use a regular function instead of an arrow function to facilitate binding to the resolver. return function resolve(container) { // Because the container holds a global reolutionMode we need to determine it in the proper order of precedence: // resolver -> container -> default value const injectionMode = this.injectionMode || container.options.injectionMode || injection_mode_1.InjectionMode.PROXY; if (injectionMode !== injection_mode_1.InjectionMode.CLASSIC) { // If we have a custom injector, we need to wrap the cradle. const cradle = this.injector ? createInjectorProxy(container, this.injector) : container.cradle; // Return the target injected with the cradle return fn(cradle); } // We have dependencies so we need to resolve them manually if (dependencies.length > 0) { const resolve = this.injector ? wrapWithLocals(container, this.injector(container)) : container.resolve; const children = dependencies.map(p => resolve(p.name, { allowUnregistered: p.optional })); return fn(...children); } return fn(); }; } /** * Parses the dependencies from the given function. * If it's a class and has an extends clause, and no reported dependencies, attempt to parse it's super constructor. */ function parseDependencies(fn) { const result = param_parser_1.parseParameterList(fn.toString()); if (result.length > 0) { return result; } const parent = Object.getPrototypeOf(fn); if (typeof parent === 'function' && parent !== Function.prototype) { // Try to parse the parent return parseDependencies(parent); } return result; } //# sourceMappingURL=resolvers.js.map