UNPKG

16 kBJavaScriptView Raw
1"use strict";
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 */
10Object.defineProperty(exports, "__esModule", { value: true });
11exports.Ioc = void 0;
12const utils_1 = require("@poppinss/utils");
13const helpers_1 = require("@poppinss/utils/build/helpers");
14const Fakes_1 = require("./Fakes");
15const Bindings_1 = require("./Bindings");
16const Injector_1 = require("./Injector");
17const Resolver_1 = require("../Resolver");
18const ImportAliases_1 = require("./ImportAliases");
19const helpers_2 = require("../helpers");
20const IocProxy_1 = require("./IocProxy");
21const IocLookupException_1 = require("../Exceptions/IocLookupException");
22class 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}
468exports.Ioc = Ioc;