@loopback/boot
Version:
A collection of Booters for LoopBack 4 Applications
193 lines • 8.35 kB
JavaScript
// Copyright IBM Corp. and LoopBack contributors 2018,2020. All Rights Reserved.
// Node module: @loopback/boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
Object.defineProperty(exports, "__esModule", { value: true });
exports._bindBooter = exports.bindBooter = exports.BootMixin = exports.Binding = void 0;
const core_1 = require("@loopback/core");
Object.defineProperty(exports, "Binding", { enumerable: true, get: function () { return core_1.Binding; } });
const boot_component_1 = require("../boot.component");
const component_application_booter_1 = require("../booters/component-application.booter");
const keys_1 = require("../keys");
/**
* Mixin for @loopback/boot. This Mixin provides the following:
* - Implements the Bootable Interface as follows.
* - Add a `projectRoot` property to the Class
* - Adds an optional `bootOptions` property to the Class that can be used to
* store the Booter conventions.
* - Adds the `BootComponent` to the Class (which binds the Bootstrapper and default Booters)
* - Provides the `boot()` convenience method to call Bootstrapper.boot()
* - Provides the `booter()` convenience method to bind a Booter(s) to the Application
* - Override `component()` to call `mountComponentBooters`
* - Adds `mountComponentBooters` which binds Booters to the application from `component.booters[]`
*
* @param superClass - Application class
* @returns A new class that extends the super class with boot related methods
*
* @typeParam T - Type of the application class as the target for the mixin
*/
function BootMixin(superClass) {
return class extends superClass {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(...args) {
super(...args);
this.component(boot_component_1.BootComponent);
// We Dynamically bind the Project Root and Boot Options so these values can
// be used to resolve an instance of the Bootstrapper (as they are dependencies)
this.bind(keys_1.BootBindings.PROJECT_ROOT).toDynamicValue(() => this.projectRoot);
this.bind(keys_1.BootBindings.BOOT_OPTIONS).toDynamicValue(() => { var _a; return (_a = this.bootOptions) !== null && _a !== void 0 ? _a : {}; });
}
/**
* Override to detect and warn about starting without booting.
*/
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
async start() {
await super.start();
if (!this.booted) {
process.emitWarning('App started without booting. Did you forget to call ' +
'`await app.boot()`?', 'LoopBackWarning');
}
}
/**
* Convenience method to call bootstrapper.boot() by resolving bootstrapper
*/
async boot() {
/* eslint-disable @typescript-eslint/ban-ts-comment */
// A workaround to access protected Application methods
const self = this;
if (this.state === 'booting') {
// @ts-ignore
return self.awaitState('booted');
}
// @ts-ignore
self.assertNotInProcess('boot');
// @ts-ignore
self.assertInStates('boot', 'created', 'booted');
if (this.state === 'booted')
return;
// @ts-ignore
self.setState('booting');
// Get a instance of the BootStrapper
const bootstrapper = await this.get(keys_1.BootBindings.BOOTSTRAPPER_KEY);
await bootstrapper.boot();
// @ts-ignore
this.setState('booted');
this.booted = true;
/* eslint-enable @typescript-eslint/ban-ts-comment */
}
/**
* Given a N number of Booter Classes, this method binds them using the
* prefix and tag expected by the Bootstrapper.
*
* @param booterCls - Booter classes to bind to the Application
*
* @example
* ```ts
* app.booters(MyBooter, MyOtherBooter)
* ```
*/
booters(...booterCls) {
return booterCls.map(cls => bindBooter(this, cls));
}
/**
* Register a booter to boot a sub-application. See
* {@link createComponentApplicationBooterBinding} for more details.
*
* @param subApp - A sub-application with artifacts to be booted
* @param filter - A binding filter to select what bindings from the sub
* application should be added to the main application.
*/
applicationBooter(subApp, filter) {
const binding = (0, component_application_booter_1.createComponentApplicationBooterBinding)(subApp, filter);
this.add(binding);
return binding;
}
/**
* Override to ensure any Booter's on a Component are also mounted.
*
* @param component - The component to add.
*
* @example
* ```ts
*
* export class ProductComponent {
* booters = [ControllerBooter, RepositoryBooter];
* providers = {
* [AUTHENTICATION_STRATEGY]: AuthStrategy,
* [AUTHORIZATION_ROLE]: Role,
* };
* };
*
* app.component(ProductComponent);
* ```
*/
// Unfortunately, TypeScript does not allow overriding methods inherited
// from mapped types. https://github.com/microsoft/TypeScript/issues/38496
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
component(componentCtor, nameOrOptions) {
const binding = super.component(componentCtor, nameOrOptions);
const instance = this.getSync(binding.key);
this.mountComponentBooters(instance);
return binding;
}
/**
* Get an instance of a component and mount all it's
* booters. This function is intended to be used internally
* by component()
*
* @param component - The component to mount booters of
*/
mountComponentBooters(componentInstanceOrClass) {
const componentInstance = resolveComponentInstance(this);
if (componentInstance.booters) {
this.booters(...componentInstance.booters);
}
/**
* Determines if componentInstanceOrClass is an instance of a component,
* or a class that needs to be instantiated from context.
* @param ctx
*/
function resolveComponentInstance(ctx) {
if (typeof componentInstanceOrClass !== 'function') {
return componentInstanceOrClass;
}
// TODO(semver-major) @bajtos: Reminder to remove this on the next major release
const componentName = componentInstanceOrClass.name;
const componentKey = `${core_1.CoreBindings.COMPONENTS}.${componentName}`;
return ctx.getSync(componentKey);
}
}
};
}
exports.BootMixin = BootMixin;
/**
* Method which binds a given Booter to a given Context with the Prefix and
* Tags expected by the Bootstrapper
*
* @param ctx - The Context to bind the Booter Class
* @param booterCls - Booter class to be bound
*/
function bindBooter(ctx, booterCls) {
const binding = (0, core_1.createBindingFromClass)(booterCls, {
namespace: keys_1.BootBindings.BOOTERS,
defaultScope: core_1.BindingScope.SINGLETON,
}).tag(keys_1.BootTags.BOOTER);
ctx.add(binding);
/**
* Set up configuration binding as alias to `BootBindings.BOOT_OPTIONS`
* so that the booter can use `@config`.
*/
if (binding.tagMap.artifactNamespace) {
ctx
.configure(binding.key)
.toAlias(`${keys_1.BootBindings.BOOT_OPTIONS.key}#${binding.tagMap.artifactNamespace}`);
}
return binding;
}
exports.bindBooter = bindBooter;
// eslint-disable-next-line @typescript-eslint/naming-convention
exports._bindBooter = bindBooter; // For backward-compatibility
//# sourceMappingURL=boot.mixin.js.map
;