UNPKG

9.47 kBJavaScriptView Raw
1"use strict";
2// Copyright IBM Corp. and LoopBack contributors 2017,2019. All Rights Reserved.
3// Node module: @loopback/context
4// This file is licensed under the MIT License.
5// License text available at https://opensource.org/licenses/MIT
6Object.defineProperty(exports, "__esModule", { value: true });
7exports.resolveInjectedProperties = exports.resolveInjectedArguments = exports.instantiateClass = void 0;
8const tslib_1 = require("tslib");
9const metadata_1 = require("@loopback/metadata");
10const assert_1 = tslib_1.__importDefault(require("assert"));
11const debug_1 = tslib_1.__importDefault(require("debug"));
12const binding_filter_1 = require("./binding-filter");
13const inject_1 = require("./inject");
14const resolution_session_1 = require("./resolution-session");
15const value_promise_1 = require("./value-promise");
16const debug = (0, debug_1.default)('loopback:context:resolver');
17const getTargetName = metadata_1.DecoratorFactory.getTargetName;
18/**
19 * Create an instance of a class which constructor has arguments
20 * decorated with `@inject`.
21 *
22 * The function returns a class when all dependencies were
23 * resolved synchronously, or a Promise otherwise.
24 *
25 * @param ctor - The class constructor to call.
26 * @param ctx - The context containing values for `@inject` resolution
27 * @param session - Optional session for binding and dependency resolution
28 * @param nonInjectedArgs - Optional array of args for non-injected parameters
29 */
30function instantiateClass(ctor, ctx, session,
31// eslint-disable-next-line @typescript-eslint/no-explicit-any
32nonInjectedArgs) {
33 /* istanbul ignore if */
34 if (debug.enabled) {
35 debug('Instantiating %s', getTargetName(ctor));
36 if (nonInjectedArgs === null || nonInjectedArgs === void 0 ? void 0 : nonInjectedArgs.length) {
37 debug('Non-injected arguments:', nonInjectedArgs);
38 }
39 }
40 const argsOrPromise = resolveInjectedArguments(ctor, '', ctx, session, nonInjectedArgs);
41 const propertiesOrPromise = resolveInjectedProperties(ctor, ctx, session);
42 const inst = (0, value_promise_1.transformValueOrPromise)(argsOrPromise, args => {
43 /* istanbul ignore if */
44 if (debug.enabled) {
45 debug('Injected arguments for %s():', ctor.name, args);
46 }
47 return new ctor(...args);
48 });
49 return (0, value_promise_1.transformValueOrPromise)(propertiesOrPromise, props => {
50 /* istanbul ignore if */
51 if (debug.enabled) {
52 debug('Injected properties for %s:', ctor.name, props);
53 }
54 return (0, value_promise_1.transformValueOrPromise)(inst, obj => Object.assign(obj, props));
55 });
56}
57exports.instantiateClass = instantiateClass;
58/**
59 * If the scope of current binding is `SINGLETON`, reset the context
60 * to be the one that owns the current binding to make sure a singleton
61 * does not have dependencies injected from child contexts unless the
62 * injection is for method (excluding constructor) parameters.
63 */
64function resolveContext(ctx, injection, session) {
65 const currentBinding = session === null || session === void 0 ? void 0 : session.currentBinding;
66 if (currentBinding == null) {
67 // No current binding
68 return ctx;
69 }
70 const isConstructorOrPropertyInjection =
71 // constructor injection
72 !injection.member ||
73 // property injection
74 typeof injection.methodDescriptorOrParameterIndex !== 'number';
75 if (isConstructorOrPropertyInjection) {
76 // Set context to the resolution context of the current binding for
77 // constructor or property injections against a singleton
78 ctx = ctx.getResolutionContext(currentBinding);
79 }
80 return ctx;
81}
82/**
83 * Resolve the value or promise for a given injection
84 * @param ctx - Context
85 * @param injection - Descriptor of the injection
86 * @param session - Optional session for binding and dependency resolution
87 */
88function resolve(ctx, injection, session) {
89 /* istanbul ignore if */
90 if (debug.enabled) {
91 debug('Resolving an injection:', resolution_session_1.ResolutionSession.describeInjection(injection));
92 }
93 ctx = resolveContext(ctx, injection, session);
94 const resolved = resolution_session_1.ResolutionSession.runWithInjection(s => {
95 if (injection.resolve) {
96 // A custom resolve function is provided
97 return injection.resolve(ctx, injection, s);
98 }
99 else {
100 // Default to resolve the value from the context by binding key
101 (0, assert_1.default)((0, binding_filter_1.isBindingAddress)(injection.bindingSelector), 'The binding selector must be an address (string or BindingKey)');
102 const key = injection.bindingSelector;
103 const options = {
104 session: s,
105 ...injection.metadata,
106 };
107 return ctx.getValueOrPromise(key, options);
108 }
109 }, injection, session);
110 return resolved;
111}
112/**
113 * Given a function with arguments decorated with `@inject`,
114 * return the list of arguments resolved using the values
115 * bound in `ctx`.
116
117 * The function returns an argument array when all dependencies were
118 * resolved synchronously, or a Promise otherwise.
119 *
120 * @param target - The class for constructor injection or prototype for method
121 * injection
122 * @param method - The method name. If set to '', the constructor will
123 * be used.
124 * @param ctx - The context containing values for `@inject` resolution
125 * @param session - Optional session for binding and dependency resolution
126 * @param nonInjectedArgs - Optional array of args for non-injected parameters
127 */
128function resolveInjectedArguments(target, method, ctx, session,
129// eslint-disable-next-line @typescript-eslint/no-explicit-any
130nonInjectedArgs) {
131 /* istanbul ignore if */
132 if (debug.enabled) {
133 debug('Resolving injected arguments for %s', getTargetName(target, method));
134 }
135 const targetWithMethods = target;
136 if (method) {
137 (0, assert_1.default)(typeof targetWithMethods[method] === 'function', `Method ${method} not found`);
138 }
139 // NOTE: the array may be sparse, i.e.
140 // Object.keys(injectedArgs).length !== injectedArgs.length
141 // Example value:
142 // [ , 'key1', , 'key2']
143 const injectedArgs = (0, inject_1.describeInjectedArguments)(target, method);
144 const extraArgs = nonInjectedArgs !== null && nonInjectedArgs !== void 0 ? nonInjectedArgs : [];
145 let argLength = metadata_1.DecoratorFactory.getNumberOfParameters(target, method);
146 // Please note `injectedArgs` contains `undefined` for non-injected args
147 const numberOfInjected = injectedArgs.filter(i => i != null).length;
148 if (argLength < numberOfInjected + extraArgs.length) {
149 /**
150 * `Function.prototype.length` excludes the rest parameter and only includes
151 * parameters before the first one with a default value. For example,
152 * `hello(@inject('name') name: string = 'John')` gives 0 for argLength
153 */
154 argLength = numberOfInjected + extraArgs.length;
155 }
156 let nonInjectedIndex = 0;
157 return (0, value_promise_1.resolveList)(new Array(argLength), (val, ix) => {
158 // The `val` argument is not used as the resolver only uses `injectedArgs`
159 // and `extraArgs` to return the new value
160 const injection = ix < injectedArgs.length ? injectedArgs[ix] : undefined;
161 if (injection == null ||
162 (!injection.bindingSelector && !injection.resolve)) {
163 if (nonInjectedIndex < extraArgs.length) {
164 // Set the argument from the non-injected list
165 return extraArgs[nonInjectedIndex++];
166 }
167 else {
168 const name = getTargetName(target, method, ix);
169 throw new resolution_session_1.ResolutionError(`The argument '${name}' is not decorated for dependency injection ` +
170 'but no value was supplied by the caller. Did you forget to apply ' +
171 '@inject() to the argument?', { context: ctx, options: { session } });
172 }
173 }
174 return resolve(ctx, injection,
175 // Clone the session so that multiple arguments can be resolved in parallel
176 resolution_session_1.ResolutionSession.fork(session));
177 });
178}
179exports.resolveInjectedArguments = resolveInjectedArguments;
180/**
181 * Given a class with properties decorated with `@inject`,
182 * return the map of properties resolved using the values
183 * bound in `ctx`.
184
185 * The function returns an argument array when all dependencies were
186 * resolved synchronously, or a Promise otherwise.
187 *
188 * @param constructor - The class for which properties should be resolved.
189 * @param ctx - The context containing values for `@inject` resolution
190 * @param session - Optional session for binding and dependency resolution
191 */
192function resolveInjectedProperties(constructor, ctx, session) {
193 /* istanbul ignore if */
194 if (debug.enabled) {
195 debug('Resolving injected properties for %s', getTargetName(constructor));
196 }
197 const injectedProperties = (0, inject_1.describeInjectedProperties)(constructor.prototype);
198 return (0, value_promise_1.resolveMap)(injectedProperties, injection => resolve(ctx, injection,
199 // Clone the session so that multiple properties can be resolved in parallel
200 resolution_session_1.ResolutionSession.fork(session)));
201}
202exports.resolveInjectedProperties = resolveInjectedProperties;
203//# sourceMappingURL=resolver.js.map
\No newline at end of file