src/di.js
import {getAlias} from './alias';
import {name as pkgName} from '../package.json';
/**
* The dependency injection (DI) container of the application.
* @type {Map}
*/
const CONTAINER = new Map;
/**
* Creates a new object using the dependency injection (DI) container of the application.
*
* You may view this function as an enhanced version of the `new` operator. The function supports creating an object based on one of the following forms:
* - a string: a class alias representing the location and the class name of the object to be created.
* - a configuration object: the object must contain a `class` element which is treated as the class alias, and the rest of the name-value pairs will be used to initialize the corresponding object properties.
*
* @param {string|object} type The object type: a class alias or a configuration object.
* @param {...*} params The constructor parameters.
* @return {*} The newly created object.
* @throws {TypeError} The object type is not supported.
*/
export function createObject(type, ...params) {
let Type;
switch (typeof type) {
case 'object':
if (!type || typeof type.class != 'string')
throw new TypeError('Configuration must be an object containing a "class" property.');
Type = registerType(type.class);
let instance = new Type(...params);
delete type.class;
if (typeof instance.init == 'function') instance.init(type);
else Object.assign(instance, type);
return instance;
case 'string':
Type = registerType(type);
return new Type(...params);
default:
throw new TypeError(`Unsupported configuration type: ${typeof type}`);
}
}
/**
* Registers a type in the dependency injection (DI) container of the application.
*
* A type is registered using a class alias specifying the location and the name of the type to be registered.
* For example:
* - `@app/components/FooBar`: will be resolved to the `FooBar` class exported by the `@app/components.js` module, or the `@app/components/index.js` module.
* - `@core/FooBar`: will be resolved to the `FooBar` class exported by this package.
* - `@npm/foo/FooBar`: will be resolved to the `FooBar` class exported by the `foo` package located in the `node_modules` folder.
* - `@npm/@foo/bar/FooBar`: will be resolved to the `FooBar` class exported by the `@foo/bar` package located in the `node_modules` folder.
*
* @param {string} alias The class alias representing the location and the name of the type to be registered.
* @return {*} The resolved type.
* @throws {TypeError} The specified class alias is invalid, or the corresponding type is not found.
*/
export function registerType(alias) {
if (typeof alias != 'string' || !alias.length) throw new TypeError('The specified class alias is empty.');
if (!alias.startsWith('@')) alias = `@${alias}`;
if (CONTAINER.has(alias)) return CONTAINER.get(alias);
let parts = alias.split('/');
if (parts.length < 2) throw new TypeError(`The type name is missing from the "${alias}" class alias.`);
let className = parts.pop();
let path;
switch (parts[0]) {
case '@core':
parts.shift();
parts.unshift(pkgName);
path = parts.join('/');
break;
case '@npm':
parts.shift();
path = parts.join('/');
break;
default:
path = getAlias(parts.join('/'));
break;
}
try {
const module = require(path);
if (!(className in module)) throw new Error(`The "${className}" type is not found in module: "${path}"`);
CONTAINER.set(alias, module[className]);
}
catch (err) {
throw new TypeError(err.message);
}
return CONTAINER.get(alias);
}