UNPKG

awilix

Version:

Extremely powerful dependency injection container.

194 lines 6.27 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.loadModules = loadModules; const camel_case_1 = require("camel-case"); const url_1 = require("url"); const lifetime_1 = require("./lifetime"); const resolvers_1 = require("./resolvers"); const utils_1 = require("./utils"); const nameFormatters = { camelCase: (s) => (0, camel_case_1.camelCase)(s), }; /** * Given an array of glob strings, will call `require` * on them, and call their default exported function with the * container as the first parameter. * * @param {AwilixContainer} dependencies.container * The container to install loaded modules in. * * @param {Function} dependencies.listModules * The listModules function to use for listing modules. * * @param {Function} dependencies.require * The require function - it's a dependency because it makes testing easier. * * @param {String[]} globPatterns * The array of globs to use when loading modules. * * @param {Object} opts * Passed to `listModules`, e.g. `{ cwd: '...' }`. * * @param {(string, ModuleDescriptor) => string} opts.formatName * Used to format the name the module is registered with in the container. * * @param {boolean} opts.esModules * Set to `true` to use Node's native ECMAScriptModules modules * * @return {Object} * Returns an object describing the result. */ function loadModules(dependencies, globPatterns, opts) { opts ??= {}; const container = dependencies.container; opts = optsWithDefaults(opts); const modules = dependencies.listModules(globPatterns, opts); if (opts.esModules) { return loadEsModules(dependencies, container, modules, opts); } else { const result = modules.map((m) => { const loaded = dependencies.require(m.path); return parseLoadedModule(loaded, m); }); return registerModules(result, container, modules, opts); } } /** * Loads the modules using native ES6 modules and the async import() * @param {AwilixContainer} container * @param {ModuleDescriptor[]} modules * @param {LoadModulesOptions} opts */ async function loadEsModules(dependencies, container, modules, opts) { const importPromises = []; for (const m of modules) { const fileUrl = (0, url_1.pathToFileURL)(m.path).toString(); importPromises.push(dependencies.require(fileUrl)); } const imports = await Promise.all(importPromises); const result = []; for (let i = 0; i < modules.length; i++) { result.push(parseLoadedModule(imports[i], modules[i])); } return registerModules(result, container, modules, opts); } /** * Parses the module which has been required * * @param {any} loaded * @param {ModuleDescriptor} m */ function parseLoadedModule(loaded, m) { const items = []; // Meh, it happens. if (!loaded) { return items; } if ((0, utils_1.isFunction)(loaded)) { // for module.exports = ... items.push({ name: m.name, path: m.path, value: loaded, opts: m.opts, }); return items; } if (loaded.default && (0, utils_1.isFunction)(loaded.default)) { // ES6 default export items.push({ name: m.name, path: m.path, value: loaded.default, opts: m.opts, }); } // loop through non-default exports, but require the RESOLVER property set for // it to be a valid service module export. for (const key of Object.keys(loaded)) { if (key === 'default') { // default case handled separately due to its different name (file name) continue; } if ((0, utils_1.isFunction)(loaded[key]) && resolvers_1.RESOLVER in loaded[key]) { items.push({ name: key, path: m.path, value: loaded[key], opts: m.opts, }); } } return items; } /** * Registers the modules * * @param {ModuleDescriptorVal[][]} modulesToRegister * @param {AwilixContainer} container * @param {ModuleDescriptor[]} modules * @param {LoadModulesOptions} opts */ function registerModules(modulesToRegister, container, modules, opts) { modulesToRegister .reduce((acc, cur) => acc.concat(cur), []) .filter((x) => x) .forEach(registerDescriptor.bind(null, container, opts)); return { loadedModules: modules, }; } /** * Returns a new options object with defaults applied. */ function optsWithDefaults(opts) { return { // Does a somewhat-deep merge on the registration options. resolverOptions: { lifetime: lifetime_1.Lifetime.TRANSIENT, ...(opts && opts.resolverOptions), }, ...opts, }; } /** * Given a module descriptor, reads it and registers it's value with the container. * * @param {AwilixContainer} container * @param {LoadModulesOptions} opts * @param {ModuleDescriptor} moduleDescriptor */ function registerDescriptor(container, opts, moduleDescriptor) { const inlineConfig = moduleDescriptor.value[resolvers_1.RESOLVER]; let name = inlineConfig && inlineConfig.name; if (!name) { name = moduleDescriptor.name; let formatter = opts.formatName; if (formatter) { if (typeof formatter === 'string') { formatter = nameFormatters[formatter]; } if (formatter) { name = formatter(name, moduleDescriptor); } } } let moduleDescriptorOpts = moduleDescriptor.opts; if (typeof moduleDescriptorOpts === 'string') { moduleDescriptorOpts = { lifetime: moduleDescriptorOpts }; } const regOpts = { ...opts.resolverOptions, ...moduleDescriptorOpts, ...inlineConfig, }; // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type const reg = regOpts.register ? regOpts.register : (0, utils_1.isClass)(moduleDescriptor.value) ? resolvers_1.asClass : resolvers_1.asFunction; container.register(name, reg(moduleDescriptor.value, regOpts)); } //# sourceMappingURL=load-modules.js.map