1 | ;
|
2 | /*
|
3 | * @adonisjs/fold
|
4 | *
|
5 | * (c) Harminder Virk <virk@adonisjs.com>
|
6 | *
|
7 | * For the full copyright and license information, please view the LICENSE
|
8 | * file that was distributed with this source code.
|
9 | */
|
10 | Object.defineProperty(exports, "__esModule", { value: true });
|
11 | exports.Ioc = void 0;
|
12 | const utils_1 = require("@poppinss/utils");
|
13 | const helpers_1 = require("@poppinss/utils/build/helpers");
|
14 | const Fakes_1 = require("./Fakes");
|
15 | const Bindings_1 = require("./Bindings");
|
16 | const Injector_1 = require("./Injector");
|
17 | const Resolver_1 = require("../Resolver");
|
18 | const ImportAliases_1 = require("./ImportAliases");
|
19 | const helpers_2 = require("../helpers");
|
20 | const IocProxy_1 = require("./IocProxy");
|
21 | const IocLookupException_1 = require("../Exceptions/IocLookupException");
|
22 | class Ioc {
|
23 | constructor() {
|
24 | this.fakes = new Fakes_1.Fakes(this);
|
25 | this.bindings = new Bindings_1.Bindings(this);
|
26 | this.injector = new Injector_1.Injector(this);
|
27 | this.aliases = new ImportAliases_1.ImportAliases(this);
|
28 | /**
|
29 | * The current state of using proxies
|
30 | */
|
31 | this.usingProxies = false;
|
32 | /**
|
33 | * Define the module type for resolving auto import aliases. Defaults
|
34 | * to `cjs`
|
35 | */
|
36 | this.module = 'cjs';
|
37 | }
|
38 | /**
|
39 | * Registered aliases. The key is the alias and value is the
|
40 | * absolute directory path
|
41 | */
|
42 | get importAliases() {
|
43 | return this.aliases.list;
|
44 | }
|
45 | /**
|
46 | * Detect if the module export value is an esm module
|
47 | */
|
48 | isEsm(value) {
|
49 | return this.module === 'esm' ? true : (0, helpers_2.isEsm)(value);
|
50 | }
|
51 | /**
|
52 | * Wraps object and class to a proxy to enable the fakes
|
53 | * API
|
54 | */
|
55 | wrapAsProxy(namespace, value) {
|
56 | /**
|
57 | * Wrap objects inside proxy
|
58 | */
|
59 | if (helpers_1.types.isObject(value)) {
|
60 | return new IocProxy_1.IocProxyObject(namespace, value, this.fakes);
|
61 | }
|
62 | /**
|
63 | * Wrap class inside proxy
|
64 | */
|
65 | if (helpers_1.types.isClass(value)) {
|
66 | return (0, IocProxy_1.IocProxyClass)(namespace, value, this.fakes);
|
67 | }
|
68 | return value;
|
69 | }
|
70 | /**
|
71 | * Wrap value inside proxy by also inspecting for esm
|
72 | * default exports
|
73 | */
|
74 | wrapEsmModuleAsProxy(namespace, value) {
|
75 | /**
|
76 | * Wrap the default export of esm modules inside in a proxy and
|
77 | * not the entire module
|
78 | */
|
79 | if (this.isEsm(value)) {
|
80 | if (value.default) {
|
81 | /**
|
82 | * We should never mutate the actual ESM module object and always clone it first
|
83 | * for abvious reasons that objects are shared by reference
|
84 | */
|
85 | const clonedModule = Object.getOwnPropertyNames(value).reduce((result, key) => {
|
86 | result[key] = value[key];
|
87 | return result;
|
88 | }, {});
|
89 | clonedModule.default = this.wrapAsProxy(namespace, clonedModule.default);
|
90 | return clonedModule;
|
91 | }
|
92 | /**
|
93 | * We don't proxy named exports as we don't have a good story on what to proxy
|
94 | *
|
95 | * - Should we proxy the whole module?
|
96 | * - Or should be expose api to allow proxying a selected set of modules
|
97 | */
|
98 | return value;
|
99 | }
|
100 | return this.wrapAsProxy(namespace, value);
|
101 | }
|
102 | /**
|
103 | * Makes an instance of a class by injecting dependencies
|
104 | */
|
105 | makeRaw(value, args) {
|
106 | return this.injector.make(value, args || []);
|
107 | }
|
108 | /**
|
109 | * Makes an instance of a class asynchronously by injecting dependencies
|
110 | */
|
111 | async makeRawAsync(value, args) {
|
112 | return this.injector.makeAsync(value, args || []);
|
113 | }
|
114 | /**
|
115 | * Enable/disable proxies. Proxies are mainly required for fakes to
|
116 | * work
|
117 | */
|
118 | useProxies(enable = true) {
|
119 | this.usingProxies = !!enable;
|
120 | return this;
|
121 | }
|
122 | /**
|
123 | * Register a binding with a callback. The callback return value will be
|
124 | * used when binding is resolved
|
125 | */
|
126 | bind(binding, callback) {
|
127 | (0, helpers_2.ensureIsFunction)(callback, '"ioc.bind" expect 2nd argument to be a function');
|
128 | this.bindings.register(binding, callback, false);
|
129 | return this;
|
130 | }
|
131 | /**
|
132 | * Same as the [[bind]] method, but registers a singleton only. Singleton's callback
|
133 | * is invoked only for the first time and then the cached value is used
|
134 | */
|
135 | singleton(binding, callback) {
|
136 | (0, helpers_2.ensureIsFunction)(callback, '"ioc.singleton" expect 2nd argument to be a function');
|
137 | this.bindings.register(binding, callback, true);
|
138 | return this;
|
139 | }
|
140 | /**
|
141 | * Define an import alias
|
142 | */
|
143 | alias(absolutePath, alias) {
|
144 | this.aliases.register(absolutePath, alias);
|
145 | return this;
|
146 | }
|
147 | /**
|
148 | * Register a fake for a namespace. Fakes works both for "bindings" and "import aliases".
|
149 | * Fakes only work when proxies are enabled using "useProxies".
|
150 | */
|
151 | fake(namespace, callback) {
|
152 | (0, helpers_2.ensureIsFunction)(callback, '"ioc.fake" expect 2nd argument to be a function');
|
153 | this.fakes.register(namespace, callback);
|
154 | return this;
|
155 | }
|
156 | /**
|
157 | * Clear selected or all the fakes. Calling the method with no arguments
|
158 | * will clear all the fakes
|
159 | */
|
160 | restore(namespace) {
|
161 | namespace ? this.fakes.delete(namespace) : this.fakes.clear();
|
162 | return this;
|
163 | }
|
164 | /**
|
165 | * Find if a fake has been registered for a given namespace
|
166 | */
|
167 | hasFake(namespace) {
|
168 | return this.fakes.has(namespace);
|
169 | }
|
170 | /**
|
171 | * Find if a binding exists for a given namespace
|
172 | */
|
173 | hasBinding(namespace) {
|
174 | return this.bindings.has(namespace);
|
175 | }
|
176 | /**
|
177 | * Find if a namespace is part of the auto import aliases. Returns false, when namespace
|
178 | * is an alias path but has an explicit binding too
|
179 | */
|
180 | isAliasPath(namespace) {
|
181 | if (this.bindings.has(namespace)) {
|
182 | return false;
|
183 | }
|
184 | return this.aliases.has(namespace);
|
185 | }
|
186 | /**
|
187 | * Lookup a namespace. The output contains the complete namespace,
|
188 | * along with its type. The type is an "alias" or a "binding".
|
189 | *
|
190 | * Null is returned when unable to lookup the namespace inside the container
|
191 | *
|
192 | * Note: This method just checks if a namespace is registered or binding
|
193 | * or can be it resolved from auto import aliases or not. However,
|
194 | * it doesn't check for the module existence on the disk.
|
195 | *
|
196 | * Optionally you can define a prefix namespace
|
197 | * to be used to build the complete namespace. For example:
|
198 | *
|
199 | * - namespace: UsersController
|
200 | * - prefixNamespace: App/Controllers/Http
|
201 | * - Output: App/Controllers/Http/UsersController
|
202 | *
|
203 | * Prefix namespace is ignored for absolute namespaces. For example:
|
204 | *
|
205 | * - namespace: /App/UsersController
|
206 | * - prefixNamespace: App/Controllers/Http
|
207 | * - Output: App/UsersController
|
208 | */
|
209 | lookup(namespace, prefixNamespace) {
|
210 | if (typeof namespace !== 'string' && namespace['namespace'] && namespace['type']) {
|
211 | return namespace;
|
212 | }
|
213 | /**
|
214 | * Ensure namespace is defined as a string only
|
215 | */
|
216 | if (typeof namespace !== 'string') {
|
217 | throw IocLookupException_1.IocLookupException.invalidNamespace();
|
218 | }
|
219 | /**
|
220 | * Build complete namespace
|
221 | */
|
222 | if (namespace.startsWith('/')) {
|
223 | namespace = namespace.substr(1);
|
224 | }
|
225 | else if (prefixNamespace) {
|
226 | namespace = `${prefixNamespace.replace(/\/$/, '')}/${namespace}`;
|
227 | }
|
228 | /**
|
229 | * Namespace is a binding
|
230 | */
|
231 | if (this.hasBinding(namespace)) {
|
232 | return {
|
233 | type: 'binding',
|
234 | namespace: namespace,
|
235 | };
|
236 | }
|
237 | /**
|
238 | * Namespace is an alias
|
239 | */
|
240 | if (this.isAliasPath(namespace)) {
|
241 | return {
|
242 | type: 'alias',
|
243 | namespace: namespace,
|
244 | };
|
245 | }
|
246 | return null;
|
247 | }
|
248 | /**
|
249 | * Same as [[lookup]]. But raises exception instead of returning null
|
250 | */
|
251 | lookupOrFail(namespace, prefixNamespace) {
|
252 | const lookupNode = this.lookup(namespace, prefixNamespace);
|
253 | if (!lookupNode) {
|
254 | throw IocLookupException_1.IocLookupException.lookupFailed(namespace);
|
255 | }
|
256 | return lookupNode;
|
257 | }
|
258 | /**
|
259 | * Resolve a binding by invoking the binding factory function. An exception
|
260 | * is raised, if the binding namespace is unregistered.
|
261 | */
|
262 | resolveBinding(binding) {
|
263 | if (this.trapCallback) {
|
264 | return this.trapCallback(binding);
|
265 | }
|
266 | const value = this.bindings.resolve(binding);
|
267 | if (this.usingProxies) {
|
268 | return this.wrapAsProxy(binding, value);
|
269 | }
|
270 | return value;
|
271 | }
|
272 | /**
|
273 | * Import namespace from the auto import aliases. This method assumes you are
|
274 | * using native ES modules
|
275 | */
|
276 | async import(namespace) {
|
277 | if (this.trapCallback) {
|
278 | return this.trapCallback(namespace);
|
279 | }
|
280 | const value = await this.aliases.resolveAsync(namespace);
|
281 | if (this.usingProxies) {
|
282 | return this.wrapEsmModuleAsProxy(namespace, value);
|
283 | }
|
284 | return value;
|
285 | }
|
286 | /**
|
287 | * Same as the "import" method, but uses CJS for requiring the module from its
|
288 | * path
|
289 | */
|
290 | require(namespace) {
|
291 | if (this.trapCallback) {
|
292 | return this.trapCallback(namespace);
|
293 | }
|
294 | const value = this.aliases.resolve(namespace);
|
295 | if (this.usingProxies) {
|
296 | return this.wrapEsmModuleAsProxy(namespace, value);
|
297 | }
|
298 | return value;
|
299 | }
|
300 | /**
|
301 | * The use method looks up a namespace inside both the bindings and the
|
302 | * auto import aliases
|
303 | */
|
304 | use(namespace) {
|
305 | if (this.trapCallback) {
|
306 | return this.trapCallback(typeof namespace === 'string' ? namespace : namespace['namespace']);
|
307 | }
|
308 | const lookupNode = this.lookupOrFail(namespace);
|
309 | if (lookupNode.type === 'alias') {
|
310 | return this.require(lookupNode.namespace);
|
311 | }
|
312 | return this.resolveBinding(lookupNode.namespace);
|
313 | }
|
314 | /**
|
315 | * Same as the [[use]] method, but instead uses ES modules for resolving
|
316 | * the auto import aliases
|
317 | */
|
318 | async useAsync(namespace) {
|
319 | if (this.trapCallback) {
|
320 | return this.trapCallback(typeof namespace === 'string' ? namespace : namespace['namespace']);
|
321 | }
|
322 | const lookupNode = this.lookupOrFail(namespace);
|
323 | if (lookupNode.type === 'alias') {
|
324 | return this.import(lookupNode.namespace);
|
325 | }
|
326 | return this.resolveBinding(lookupNode.namespace);
|
327 | }
|
328 | /**
|
329 | * Makes an instance of the class by first resolving it.
|
330 | */
|
331 | make(namespace, args) {
|
332 | const isContainerNamespace = typeof namespace === 'string' || (namespace['namespace'] && namespace['type']);
|
333 | /**
|
334 | * Value is not a container namespace or a lookup
|
335 | * node
|
336 | */
|
337 | if (!isContainerNamespace) {
|
338 | return this.makeRaw(namespace, args);
|
339 | }
|
340 | /**
|
341 | * Invoke trap callback (if registered)
|
342 | */
|
343 | if (this.trapCallback) {
|
344 | return this.trapCallback(typeof namespace === 'string' ? namespace : namespace['namespace']);
|
345 | }
|
346 | const lookupNode = this.lookupOrFail(namespace);
|
347 | /**
|
348 | * We do not touch bindings at all. The factory function
|
349 | * return value is used as it is
|
350 | */
|
351 | if (lookupNode.type === 'binding') {
|
352 | return this.resolveBinding(lookupNode.namespace);
|
353 | }
|
354 | const value = this.require(lookupNode.namespace);
|
355 | /**
|
356 | * We attempt to make an instance of only the export
|
357 | * default of a ES module
|
358 | */
|
359 | if (this.isEsm(value) && value.default) {
|
360 | return this.makeRaw(value.default, args || []);
|
361 | }
|
362 | return this.makeRaw(value, args);
|
363 | }
|
364 | /**
|
365 | * Same as the [[make]] method, but instead uses ES modules for resolving
|
366 | * the auto import aliases
|
367 | */
|
368 | async makeAsync(namespace, args) {
|
369 | const isContainerNamespace = typeof namespace === 'string' || (namespace['namespace'] && namespace['type']);
|
370 | /**
|
371 | * Value is not a container namespace or a lookup
|
372 | * node
|
373 | */
|
374 | if (!isContainerNamespace) {
|
375 | return this.makeRawAsync(namespace, args);
|
376 | }
|
377 | /**
|
378 | * Invoke trap callback (if registered)
|
379 | */
|
380 | if (this.trapCallback) {
|
381 | return this.trapCallback(typeof namespace === 'string' ? namespace : namespace['namespace']);
|
382 | }
|
383 | const lookupNode = this.lookupOrFail(namespace);
|
384 | /**
|
385 | * We do not touch bindings at all. The factory function
|
386 | * return value is used as it is
|
387 | */
|
388 | if (lookupNode.type === 'binding') {
|
389 | return this.resolveBinding(lookupNode.namespace);
|
390 | }
|
391 | const value = await this.import(lookupNode.namespace);
|
392 | /**
|
393 | * We attempt to make an instance of only the export
|
394 | * default of a ES module
|
395 | */
|
396 | if (this.isEsm(value) && value.default) {
|
397 | return this.makeRawAsync(value.default, args || []);
|
398 | }
|
399 | return this.makeRawAsync(value, args);
|
400 | }
|
401 | /**
|
402 | * Define a callback to be called when all of the container
|
403 | * bindings are available.
|
404 | *
|
405 | * Note: This method is exclusive for bindings and doesn't resolve
|
406 | * auto import aliases
|
407 | */
|
408 | withBindings(namespaces, cb) {
|
409 | if (namespaces.every((namespace) => this.hasBinding(namespace))) {
|
410 | /**
|
411 | * The callback accepts a tuple, whereas map returns an array. So we
|
412 | * need to cast the value to any by hand
|
413 | */
|
414 | cb(...namespaces.map((namespace) => this.resolveBinding(namespace)));
|
415 | }
|
416 | }
|
417 | /**
|
418 | * @deprecated: Use "withBindings" instead
|
419 | */
|
420 | with(namespaces, cb) {
|
421 | process.emitWarning('DeprecationWarning', 'container.with() is deprecated. Use container.withBindings() instead');
|
422 | return this.withBindings(namespaces, cb);
|
423 | }
|
424 | /**
|
425 | * Call method on an object and automatically resolve its depdencies
|
426 | */
|
427 | call(target, method, args) {
|
428 | if (typeof target[method] !== 'function') {
|
429 | throw new utils_1.Exception(`Missing method "${method}" on "${target.constructor.name}"`);
|
430 | }
|
431 | return this.injector.call(target, method, args || []);
|
432 | }
|
433 | /**
|
434 | * Same as [[call]], but uses ES modules for resolving the auto
|
435 | * import aliases
|
436 | */
|
437 | async callAsync(target, method, args) {
|
438 | if (typeof target[method] !== 'function') {
|
439 | throw new utils_1.Exception(`Missing method "${method}" on "${target.constructor.name}"`);
|
440 | }
|
441 | return this.injector.callAsync(target, method, args || []);
|
442 | }
|
443 | /**
|
444 | * Trap container lookup calls. It includes
|
445 | *
|
446 | * - Ioc.use
|
447 | * - Ioc.useAsync
|
448 | * - Ioc.make
|
449 | * - Ioc.makeAsync
|
450 | * - Ioc.require
|
451 | * - Ioc.import
|
452 | * - Ioc.resolveBinding
|
453 | */
|
454 | trap(callback) {
|
455 | this.trapCallback = callback;
|
456 | return this;
|
457 | }
|
458 | /**
|
459 | * Returns the resolver instance to resolve Ioc container bindings with
|
460 | * little ease. Since, the IocResolver uses an in-memory cache to
|
461 | * improve the lookup speed, we suggest keeping a reference to
|
462 | * the output of this method to leverage caching
|
463 | */
|
464 | getResolver(fallbackMethod, rcNamespaceKey, fallbackNamespace) {
|
465 | return new Resolver_1.IocResolver(this, fallbackMethod, rcNamespaceKey, fallbackNamespace);
|
466 | }
|
467 | }
|
468 | exports.Ioc = Ioc;
|