metaf-resolvable
Version:
Abstract resolvable mixin and injection root for adding DI/IoC to any framework
144 lines • 6.81 kB
JavaScript
"use strict";
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Resolver = exports.override = void 0;
var utils_1 = require("./utils");
// we are using private symbol in order to force users work with override function
// so override always type compatible with original value
var overrideSymbol = Symbol('Overrides');
/**
* Function that meant to be used as only way for specifying overrides
* @description creates a `[key, value]` pair describing override
* @template T type of dependency key that has to be overwritten
* @param key actual key that has to be overwritten
* @param value value that will be passed as dependency for this `key`
* @returns object with symbol property which point to `[key, value]` pair
*/
function override(key, value) {
var _a;
return _a = {},
_a[overrideSymbol] = [key, value],
_a;
}
exports.override = override;
/**
* Class that is responsible for resolving dependencies and requirements
* @description contains all resolved dependencies, resolvable class implementations
* and methods that used by `Resolvable` factories in order to create properly resolved
* classes with dependencies
* > **NOTE:** it also meant to be extended in order to provide first-class support
* for some particular frameworks e.g. `react`
*/
var Resolver = /** @class */ (function () {
/**
* Creates an instance of resolver.
* @param [overrides]
*/
function Resolver(overrides) {
if (overrides === void 0) { overrides = []; }
this.injectionRoot = new WeakMap(overrides.map(function (overridePair) { return overridePair[overrideSymbol]; }));
this.implementations = new WeakMap();
// we want these function to have correct `this` context,
// but can't use arrow functions because it breaks normal inheritance flow
this.inject = this.inject.bind(this);
this.resolveFor = this.resolveFor.bind(this);
this.resolveRequirements = this.resolveRequirements.bind(this);
this.initialize = this.initialize.bind(this);
this.setOverride = this.setOverride.bind(this);
}
/**
* Entry point for resolving requirements for particular resolvable class
* @description resolves `requirements` for `classImpl` and applies them to `ApplicationRoot`
* > **NOTE:** default implementation does nothing, since only Resolver for specific framework
* knows how to apply requirements to particular environment
* @template R type of `requirements` tuple
* @template C type of `classImpl` constructor
* @param requirements tuple of specific requirements that should be met by hosting application
* @param classImpl particular resolvable class implementation
* @returns `classImpl` or something with exactly same interface
*/
Resolver.prototype.resolveRequirements = function (requirements, classImpl) {
return classImpl;
};
/**
* Entry point for resolving dependencies of particular instance
* @description resolves `injections` for `instance` of `classImpl` resolvable class
* @template I type of `injections` dictionary
* @param classImpl class constructor that was used for creating this instance
* @param [injections] dictionary of dependencies that should be resolved and initialized
* @param [instance] particular class instance
* @param [args] arguments that were passed to constructor function for creating `instance`
* @returns dictionary of resolved and initialized dependencies
*/
Resolver.prototype.resolveFor = function (classImpl, injections, instance, args) {
var _this = this;
if (injections === void 0) { injections = {}; }
if (this.implementations.has(classImpl))
return this.implementations.get(classImpl);
var resolvedDependencies = {};
// TODO: remove this cast once Object.keys typings would be fixed
Object.keys(injections)
.forEach(function (injectionName) {
return resolvedDependencies[injectionName] = _this.inject(injections[injectionName]);
});
this.implementations.set(classImpl, resolvedDependencies);
return resolvedDependencies;
};
/**
* This method is used internally to properly initialize dependency
* @description initializes injectable `obj`
* > **NOTE:** this function is pure, but it's possible that ancestors may need
* another solution for initializing instances, because of framework
* requirements, conventions and best practices
* @param obj dependency that has to be initialized
* @returns initialized dependency
*/
Resolver.prototype.initialize = function (obj) {
if ((0, utils_1.isConstructor)(obj))
return new obj();
if ((0, utils_1.isCallable)(obj))
return obj();
throw new Error("Dependency expected to be constructable or callable, but got ".concat(typeof obj));
};
/**
* This method is used to inject resolved `dependency` for `injectionKey`
* @description Injects resolver
* @template T type of `injectionKey` that maps to type of `dependency` value
* @param injectionKey actual key of `dependency`
* @returns resolved `dependency` instance
*/
Resolver.prototype.inject = function (injectionKey) {
if (!this.injectionRoot.has(injectionKey)) {
this.injectionRoot.set(injectionKey, this.initialize(injectionKey));
}
return this.injectionRoot.get(injectionKey);
};
/**
* This method works with result of `override` helper function
* @description Sets override for particular `injectionKey`
* @template T type of `injectionKey` that maps to type of `dependency` value
* @param overridePair actual `[key, value]` pair retrieved from override helper
*/
Resolver.prototype.setOverride = function (overridePair) {
var _a = __read(overridePair[overrideSymbol], 2), key = _a[0], value = _a[1];
this.injectionRoot.set(key, value);
};
return Resolver;
}());
exports.Resolver = Resolver;
//# sourceMappingURL=Resolver.js.map