UNPKG

async-injection

Version:

A robust lightweight dependency injection library for TypeScript.

229 lines 13.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Injectable = Injectable; exports.Inject = Inject; exports._getInjectedIdAt = _getInjectedIdAt; exports._getInjectedIdForMethod = _getInjectedIdForMethod; exports.Optional = Optional; exports._getOptionalDefaultAt = _getOptionalDefaultAt; exports._getOptionalDefaultForMethod = _getOptionalDefaultForMethod; exports.PostConstruct = PostConstruct; exports.Release = Release; /** * These decorators all apply the information they collect (whether class, method, or parameter data) as tagged metadata on the class's constructor */ const constants_1 = require("./constants"); // Help user locate misapplied decorators. function targetHint(target) { let hint; if (target) { hint = target.name; if ((!hint) && target.constructor) { hint = target.constructor.name; } } return hint !== null && hint !== void 0 ? hint : ''; } // Validate that 'target' is a class constructor function. function isClassConstructor(target) { if (typeof target === 'function' && target.hasOwnProperty('prototype')) { return target.prototype.constructor === target; } return false; } // Ensure consistency in our meta-data name getting/setting. function makeParamIdxKey(idx) { return `index-${idx}`; } // Key for @Inject/@Optional metadata stored on a method parameter. function makeMethodParamIdxKey(methodName, idx) { return `${String(methodName)}:index-${idx}`; } // Validate that the specified target is a parameter of a class constructor function validateConstructorParam(decorator, target, idx) { if (!isClassConstructor(target)) { throw new Error('@' + decorator + ' is not valid here [' + targetHint(target) + ']'); } return makeParamIdxKey(idx); } // Validate that the target is an instance method parameter (non-constructor). function validateMethodParam(decorator, target, methodName, idx) { if (typeof target !== 'object' || typeof target.constructor !== 'function') { throw new Error('@' + decorator + ' is not valid here [' + targetHint(target) + ']'); } if (!Reflect.hasOwnMetadata(constants_1.REFLECT_PARAMS, target, methodName)) { // REFLECT_PARAMS not yet set (decoration order), so we cannot pre-validate the method; accept silently. } return makeMethodParamIdxKey(methodName, idx); } // Validate the decorator was only applied once. function validateSingleConstructorParam(decorator, target, idx) { const propKey = validateConstructorParam(decorator, target, idx); if (Reflect.hasOwnMetadata(decorator, target, propKey)) { throw new Error('@' + decorator + ' applied multiple times [' + target.constructor.name + ']'); } return propKey; } /** * Placed just before the class declaration, this class decorator applies metadata to the class constructor indicating that the user intends to bind the class into the container. * This decorator will throw if not placed on a class declaration, or if placed more than once on a class declaration. */ function Injectable() { /** * @param target The constructor function of the class that is being decorated * @returns Undefined (nothing), as this decorator does not modify the constructor in any way. */ return function (target) { if (Reflect.hasOwnMetadata(constants_1.INJECTABLE_METADATA_KEY, target)) { throw new Error('@Injectable applied multiple times [' + targetHint(target) + ']'); } Reflect.defineMetadata(constants_1.INJECTABLE_METADATA_KEY, true, target); }; } /** * Placed just before a constructor parameter, this parameter decorator allows for specificity and control over the type of Object that will be injected into the parameter. * In the absence of this decorator the container will use whatever is bound to a parameter's type (or throw an error if it is unable to recognize the type). * This decorator may also be placed on a parameter of a method annotated with @PostConstruct, in which case the container will resolve and inject the value before invoking that method. * * @param id The identifier of the bound type that should be injected. */ function Inject(id) { /** * @param target The constructor function of the class (for constructor params), or the class prototype (for method params). * @param parameterName The name of the parameter (undefined for constructor params, the method name for method params) * @param parameterIndex The ordinal index of the parameter in the function’s parameter list * @returns Undefined (nothing), as this decorator does not modify the parameter in any way. */ return function (target, parameterName, parameterIndex) { if (id === undefined) { throw new Error('Undefined id passed to @Inject [' + targetHint(target) + ']'); } if (parameterName === undefined) { // Constructor parameter const paramKey = validateSingleConstructorParam('Inject', target, parameterIndex); Reflect.defineMetadata(constants_1.INJECT_METADATA_KEY, id, target, paramKey); } else { // Method parameter — silently accepted (intended for @PostConstruct methods) const paramKey = validateMethodParam('Inject', target, parameterName, parameterIndex); Reflect.defineMetadata(constants_1.INJECT_METADATA_KEY, id, target, paramKey); } }; } /** * This is a helper function used by the container to retrieve the @Inject metadata for a specifically indexed constructor parameter * * @param target The constructor function of the class (we don't allow @Inject on anything else). * @param parameterIndex The ordinal index of the parameter in the constructor’s parameter list * @see Inject */ function _getInjectedIdAt(target, parameterIndex) { return Reflect.getMetadata(constants_1.INJECT_METADATA_KEY, target, makeParamIdxKey(parameterIndex)); } /** * Retrieve the @Inject metadata for a specifically indexed parameter of a named method. */ function _getInjectedIdForMethod(prototype, methodName, parameterIndex) { return Reflect.getMetadata(constants_1.INJECT_METADATA_KEY, prototype, makeMethodParamIdxKey(methodName, parameterIndex)); } /** * Placed just before a constructor parameter, this parameter decorator signals the container that it should supply the 'alt' constant value (undefined by default) if for *any* reason it is unable to otherwise resolve the type of the parameter. * This decorator may also be placed on a parameter of a method annotated with @PostConstruct. * WARNING! It is your responsibility to ensure that alt is of the appropriate type/value. */ function Optional(alt) { /** * @param target The constructor function of the class (for constructor params), or the class prototype (for method params). * @param parameterName The name of the parameter (undefined for constructor params, the method name for method params) * @param parameterIndex The ordinal index of the parameter in the function’s parameter list * @returns Undefined (nothing), as this decorator does not modify the parameter in any way. */ return function (target, parameterName, parameterIndex) { if (parameterName === undefined) { // Constructor parameter const paramKey = validateSingleConstructorParam('Optional', target, parameterIndex); Reflect.defineMetadata(constants_1.OPTIONAL_METADATA_KEY, { value: alt }, target, paramKey); } else { // Method parameter — silently accepted (intended for @PostConstruct methods) const paramKey = validateMethodParam('Optional', target, parameterName, parameterIndex); Reflect.defineMetadata(constants_1.OPTIONAL_METADATA_KEY, { value: alt }, target, paramKey); } }; } /** * This is a helper function used by the container to retrieve the @Optional metadata for a specifically indexed constructor parameter * * @param target The constructor function of the class (we don't allow @Optional on anything else). * @param parameterIndex The ordinal index of the parameter in the constructor’s parameter list * @see Optional * @returns an object containing the value provided in the decorator, or undefined if no annotation was present. */ function _getOptionalDefaultAt(target, parameterIndex) { return Reflect.getMetadata(constants_1.OPTIONAL_METADATA_KEY, target, makeParamIdxKey(parameterIndex)); // See the @Optional decorator before making any changes here. } /** * Retrieve the @Optional metadata for a specifically indexed parameter of a named method. */ function _getOptionalDefaultForMethod(prototype, methodName, parameterIndex) { return Reflect.getMetadata(constants_1.OPTIONAL_METADATA_KEY, prototype, makeMethodParamIdxKey(methodName, parameterIndex)); } /** * Placed just before a class method, this method decorator flags a method that should be called after an object has been instantiated by the container, but before it is put into service. * The method will be assumed to be synchronous unless the method signature explicitly declares its return type to be ": Promise<something>". * Parameters will be resolved by the container just as they are for constructors. * This decorator will throw if placed on a non-method or a static method of a class, or if placed on a method more than once, or if placed on more than one method for a class. */ function PostConstruct() { /** * @param prototypeOrConstructor The prototype of the class (we don't allow @PostConstruct on anything other than a class instance method. * @param methodName The name of the method. * @param _descriptor The Property Descriptor for the method. * @returns Undefined (nothing), as this decorator does not modify the method in any way. */ return function (target, methodName, _descriptor) { if (typeof target !== 'object' || typeof target.constructor !== 'function') { throw new Error('@PostConstruct not applied to instance method [' + target.toString() + '/' + methodName.toString() + ']'); } if (Reflect.hasOwnMetadata(constants_1.POSTCONSTRUCT_SYNC_METADATA_KEY, target.constructor) || Reflect.hasOwnMetadata(constants_1.POSTCONSTRUCT_ASYNC_METADATA_KEY, target.constructor)) { throw new Error('@PostConstruct applied multiple times [' + targetHint(target.constructor) + ']'); } const rt = Reflect.getMetadata(constants_1.REFLECT_RETURN, target, methodName); if (typeof rt === 'function') { Reflect.defineMetadata(constants_1.POSTCONSTRUCT_ASYNC_METADATA_KEY, methodName, target.constructor); } else { Reflect.defineMetadata(constants_1.POSTCONSTRUCT_SYNC_METADATA_KEY, methodName, target.constructor); } }; } /** * Placed just before a class method, this decorator identifies a method which should be called when an object is removed from service. * If invoked by the container, the container will drop any references it has to the object when the method returns. * Note that this decorator is *not* a guarantee (or even an implication) that the decorated method will be called (JavaScript has no mechanism to enforce such a contract). * This decorator simply serves as a flag to indicate a method which is intended to clean up resources allocated by the object *which would not otherwise be garbage collected*. * You should *not* use this decorator as a general "object finalization" method. It has very limited scope and purpose. * The decorated method must complete normally (no throwing), as "release" is not an abort-able process. * This decorator will throw if placed on a non-method or a static method of a class, or if placed on a method more than once, or if placed on more than one method for a class. * The @see InvokeReleaseMethod helper function can search for and invoke the @Release decorated method of an object. * Also @see Container.releaseSingletons for the intended usage of this decorator. * It is intended that after the @Release decorated method of an object is called, that object will not be used again, but this is of course not enforced). */ function Release() { /** * @param prototypeOrConstructor The prototype of the class (we don't allow @Release on anything other than a class instance method. * @param methodName The name of the method. * @param _descriptor The Property Descriptor for the method. * @returns Undefined (nothing), as this decorator does not modify the method in any way. */ return function (target, methodName, _descriptor) { if (typeof target !== 'object' || typeof target.constructor !== 'function') { throw new Error('@Release not applied to instance method [' + target.toString() + '/' + methodName.toString() + ']'); } if (Reflect.hasOwnMetadata(constants_1.RELEASE_METADATA_KEY, target.constructor)) { throw new Error('@Release applied multiple times [' + targetHint(target.constructor) + ']'); } Reflect.defineMetadata(constants_1.RELEASE_METADATA_KEY, methodName, target.constructor); }; } //# sourceMappingURL=decorators.js.map