UNPKG

awilix

Version:

Extremely powerful dependency injection container.

352 lines 10.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RESOLVER = void 0; exports.asValue = asValue; exports.asFunction = asFunction; exports.asClass = asClass; exports.aliasTo = aliasTo; exports.createBuildResolver = createBuildResolver; exports.createDisposableResolver = createDisposableResolver; const errors_1 = require("./errors"); const injection_mode_1 = require("./injection-mode"); const lifetime_1 = require("./lifetime"); const param_parser_1 = require("./param-parser"); const utils_1 = require("./utils"); // We parse the signature of any `Function`, so we want to allow `Function` types. /* eslint-disable @typescript-eslint/no-unsafe-function-type */ /** * 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. The value is * marked as leak-safe since in strict mode, the value will only be resolved when it is not leaking * upwards from a child scope to a parent singleton. * * @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, isLeakSafe: true, }; } /** * 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 (!(0, 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); const result = { resolve, ...opts, }; return createDisposableResolver(createBuildResolver(result)); } /** * 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 (!(0, 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(...args) { return Reflect.construct(Type, args); }; const resolve = generateResolve(newClass, Type); return createDisposableResolver(createBuildResolver({ ...opts, resolve, })); } /** * Resolves to the specified registration. Marked as leak-safe since the alias target is what should * be checked for lifetime leaks. */ function aliasTo(name) { return { resolve(container) { return container.resolve(name); }, isLeakSafe: true, }; } /** * 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({ ...this, lifetime: value, }); } function setInjectionMode(value) { return createBuildResolver({ ...this, injectionMode: value, }); } function inject(injector) { return createBuildResolver({ ...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), }); } /** * Given a resolver, returns an object with methods to manage the disposer * function. * @param obj */ function createDisposableResolver(obj) { function disposer(dispose) { return createDisposableResolver({ ...this, dispose, }); } return updateResolver(obj, { disposer, }); } /** * 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 = { ...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 = (0, 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; } /** * 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 that extends another class, and it does * not have a defined constructor, attempt to parse it's super constructor. */ function parseDependencies(fn) { const result = (0, param_parser_1.parseParameterList)(fn.toString()); if (!result) { // No defined constructor for a class, check if there is a parent // we can parse. const parent = Object.getPrototypeOf(fn); if (typeof parent === 'function' && parent !== Function.prototype) { // Try to parse the parent return parseDependencies(parent); } return []; } return result; } //# sourceMappingURL=resolvers.js.map