UNPKG

async-injection

Version:

A robust lightweight dependency injection library for TypeScript.

196 lines 10 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ClassBasedProvider = void 0; const bindable_provider_js_1 = require("./bindable-provider.js"); const constants_js_1 = require("./constants.js"); const decorators_js_1 = require("./decorators.js"); const state_js_1 = require("./state.js"); const utils_js_1 = require("./utils.js"); /** * @inheritDoc * This specialization invokes it's configured class constructor synchronously and then scans for (and invokes) any @PostConstruct (which may be synchronous or asynchronous). */ class ClassBasedProvider extends bindable_provider_js_1.BindableProvider { constructor(injector, id, maker) { super(injector, id, maker); } /** * @inheritDoc * @see the class description for this Provider. * This method is just a singleton guard, the real work is done by provideAsStateImpl. */ provideAsState() { let retVal = this.singleton; if (!retVal) { retVal = this.provideAsStateImpl(); } if (this.singleton === null) this.singleton = retVal; return retVal; } /** * @inheritDoc * This specialization returns undefined if 'asyncOnly' is true and there is no asynchronous PostConstruct annotation (since class constructors can never by asynchronous). */ resolveIfSingleton(asyncOnly) { if ((!asyncOnly) || Reflect.getMetadata(constants_js_1.POSTCONSTRUCT_ASYNC_METADATA_KEY, this.maker)) return super.resolveIfSingleton(false); return undefined; } /** * Make a resolved or pending State that reflects any @PostConstruct annotations. */ makePostConstructState(obj) { var _a, _b; if (typeof obj === 'object' && (!Array.isArray(obj)) && obj.constructor) { let maybeAsync = false; let pcFn; if (typeof this.successHandler === 'function') { maybeAsync = true; pcFn = () => { return this.successHandler(obj, this.injector, this.id, this.maker); }; } else { /* eslint-disable @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */ // Check to see if there is a @PostConstruct annotation on a method of the class. let postConstruct = Reflect.getMetadata(constants_js_1.POSTCONSTRUCT_SYNC_METADATA_KEY, obj.constructor); if (!postConstruct) { maybeAsync = true; postConstruct = Reflect.getMetadata(constants_js_1.POSTCONSTRUCT_ASYNC_METADATA_KEY, obj.constructor); } if (postConstruct && obj.constructor.prototype[postConstruct] && typeof obj.constructor.prototype[postConstruct] === 'function') pcFn = (_b = (_a = obj[postConstruct]).bind) === null || _b === void 0 ? void 0 : _b.call(_a, obj); /* eslint-enable @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */ } if (pcFn) { let result; try { result = pcFn(); } catch (err) { // The post construction method threw while executing, give the errorHandler (if any) a crack at recovery. try { obj = this.queryErrorHandler(err, obj); // The returned obj is unlikely to be the original obj. return state_js_1.State.MakeState(null, undefined, obj); } catch (e) { // could not recover, propagate the error. return state_js_1.State.MakeState(null, e, undefined); } } // The post construction method says it will let us know when it's finished. if (result && (result instanceof Promise || (maybeAsync && (0, utils_js_1.isPromise)(result)))) { // Return a State that is pending (the other return statements in this method return a State which is resolved or rejected). /* eslint-disable @typescript-eslint/no-unsafe-argument */ return state_js_1.State.MakeState(this.makePromiseForObj(result, () => obj)); } } } // No PostConstruct, just return a resolved State return state_js_1.State.MakeState(null, undefined, obj); } /** * This method collects the States of all the constructor parameters for our target class. */ getConstructorParameterStates() { const argTypes = Reflect.getMetadata(constants_js_1.REFLECT_PARAMS, this.maker); if (argTypes === undefined || !Array.isArray(argTypes)) { return []; } return argTypes.map((argType, index) => { // The reflect-metadata API fails on circular dependencies returning undefined instead. // Additionally, it cannot return generic types (no runtime type info). // If an Inject annotation precedes the parameter, then that is what should get injected. const overrideToken = (0, decorators_js_1._getInjectedIdAt)(this.maker, index); // If there was no Inject annotation, we might still be able to determine what to inject using the 'argType' (aka Reflect design:paramtypes). const actualToken = overrideToken === undefined ? argType : overrideToken; if (actualToken === undefined) { // No Inject annotation, and the type is not known. throw new Error(`Injection error. Unable to determine parameter ${index} type/value of ${this.maker.toString()} constructor`); } // Ask our container to resolve the parameter. /* eslint-disable @typescript-eslint/no-unsafe-argument */ let param = this.injector.resolveState(actualToken); // If the parameter could not be resolved, see if there is an @Optional annotation if ((!param.pending) && param.rejected) { const md = (0, decorators_js_1._getOptionalDefaultAt)(this.maker, index); if (md) param = state_js_1.State.MakeState(null, undefined, md.value); } return param; }); } /** * Gather the needed constructor parameters, invoke the constructor, and figure out what post construction needs done. */ provideAsStateImpl() { const params = this.getConstructorParameterStates(); // If any of the params are in a rejected state, we cannot construct. const firstRejectedParam = params.find((p) => { return (!p.pending) && p.rejected; }); if (firstRejectedParam) return firstRejectedParam; if (params.some(p => p.pending)) { // Some of the parameters needed for construction are not yet available, wait for them and then attempt construction. // We do this by mapping each param to a Promise (pending or not), and then awaiting them all. // This might create some unnecessary (but immediately resolved) Promise objects, // BUT, it allows us to chain for failure *and* substitute the Optional (if one exists). const objPromise = this.makePromiseForObj(Promise.all(params.map((p, idx) => { if (p.pending) { return p.promise.catch(err => { // This was a promised param that failed to resolve. // If there is an Optional decorator, use that, otherwise, failure is failure. const md = (0, decorators_js_1._getOptionalDefaultAt)(this.maker, idx); if (!md) throw err; return md.value; }); } if (p.rejected) return Promise.reject(p.rejected); return Promise.resolve(p.fulfilled); })), (values) => { if (values) { // All the parameters are now available, instantiate the class. // If this throws, it will be handled by our caller. return Reflect.construct(this.maker, values); } }); // Once the obj is resolved, then we need to check for PostConstruct and if it was async, wait for that too. return state_js_1.State.MakeState(objPromise.then((obj) => { const state = this.makePostConstructState(obj); if (state.pending) { return state.promise; // chain (aka wait some more). } else if (state.rejected) { // eslint-disable-next-line @typescript-eslint/no-unsafe-return return state.rejected; // error } else { return state.fulfilled; // value (aka obj). } })); } else { // All parameters needed for construction are available, instantiate the object. try { const newObj = Reflect.construct(this.maker, params.map((p) => p.fulfilled)); return this.makePostConstructState(newObj); } catch (err) { // There was an error, give the errorHandler (if any) a crack at recovery. try { return state_js_1.State.MakeState(null, undefined, this.queryErrorHandler(err)); } catch (e) { // could not recover, propagate the error. return state_js_1.State.MakeState(null, e, undefined); } } } } } exports.ClassBasedProvider = ClassBasedProvider; //# sourceMappingURL=class-provider.js.map